diff --git a/src/main/java/DirectionalLight.java b/src/main/java/DirectionalLight.java index 048cb53..1aca760 100644 --- a/src/main/java/DirectionalLight.java +++ b/src/main/java/DirectionalLight.java @@ -2,15 +2,19 @@ import org.joml.Vector3f; public class DirectionalLight { public Vector3f direction; - public Vector3f color; - public float intensity; + + public Vector3f ambient; + public Vector3f diffuse; + public Vector3f specular; public DirectionalLight ( Vector3f direction, - Vector3f color, - float intensity) { + Vector3f ambient, + Vector3f diffuse, + Vector3f specular) { this.direction = direction; - this.color = color; - this.intensity = intensity; + this.ambient = ambient; + this.diffuse = diffuse; + this.specular = specular; } } diff --git a/src/main/java/Material.java b/src/main/java/Material.java new file mode 100644 index 0000000..e5c7d9a --- /dev/null +++ b/src/main/java/Material.java @@ -0,0 +1,41 @@ +import org.joml.Vector3f; +import org.joml.Vector4f; + +public class Material { + // Phong + public Vector3f ambient = new Vector3f(1.0f); + public Vector3f diffuse = new Vector3f(1.0f); + public Vector3f specular = new Vector3f(1.0f); + public float shininess = 32.0f; + + public Texture diffuseMap; + public Texture specularMap; + + public boolean hasDiffuseMap; + public boolean hasSpecularMap; + + public Material() { + this.ambient = new Vector3f(1.f, 1.f, 1.f); + this.diffuse = new Vector3f(1.f, 1.f, 1.f); + this.specular = new Vector3f(1.f, 1.f, 1.f); + + diffuseMap = null; + specularMap = null; + + hasDiffuseMap = false; + hasSpecularMap = false; + } + + public Material(Vector3f ambient, Vector3f diffuse, Vector3f specular, float shininess) { + this.ambient = ambient; + this.diffuse = diffuse; + this.specular = specular; + this.shininess = shininess; + + diffuseMap = null; + specularMap = null; + + hasDiffuseMap = false; + hasSpecularMap = false; + } +} diff --git a/src/main/java/Mesh.java b/src/main/java/Mesh.java index adf7027..3e2a7fd 100644 --- a/src/main/java/Mesh.java +++ b/src/main/java/Mesh.java @@ -1,3 +1,4 @@ +import org.lwjgl.BufferUtils; import org.lwjgl.system.MemoryStack; import java.nio.FloatBuffer; @@ -15,11 +16,14 @@ public class Mesh { private int _vbo; private int _ebo; - private int vertexCount; + private final int vertexCount; - public Mesh(float[] p, float[] u, float[] n, int[] indices) { + public int material; + + public Mesh(float[] p, float[] u, float[] n, int[] indices, int material) { vertexCount = indices.length; sendToGPU(Vertex.interleaveVertexData(p, u, n), indices); + this.material = material; } public void render() { @@ -35,29 +39,31 @@ public class Mesh { } private void sendToGPU(float[] vertices, int[] indices) { - try (MemoryStack stack = MemoryStack.stackPush()) { - _vao = glGenVertexArrays(); - glBindVertexArray(_vao); + _vao = glGenVertexArrays(); + glBindVertexArray(_vao); - // pos - _vbo = glGenBuffers(); - glBindBuffer(GL_ARRAY_BUFFER, _vbo); - glBufferData(GL_ARRAY_BUFFER, stack.floats(vertices), GL_STATIC_DRAW); + // pos + _vbo = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(vertices.length); + vertexBuffer.put(vertices).flip(); + glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW); - _ebo = glGenBuffers(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, stack.ints(indices), GL_STATIC_DRAW); + _ebo = glGenBuffers(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo); + IntBuffer indicesBuffer = BufferUtils.createIntBuffer(indices.length); + indicesBuffer.put(indices).flip(); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW); - // set vao positions - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, false, STRIDE, 0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, false, STRIDE, 3 * Float.BYTES); - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 3, GL_FLOAT, false, STRIDE, 5 * Float.BYTES); + // set vao positions + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, STRIDE, 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, false, STRIDE, 3 * Float.BYTES); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 3, GL_FLOAT, false, STRIDE, 5 * Float.BYTES); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - } + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); } } diff --git a/src/main/java/Model.java b/src/main/java/Model.java new file mode 100644 index 0000000..b734d50 --- /dev/null +++ b/src/main/java/Model.java @@ -0,0 +1,47 @@ +import org.joml.Matrix3f; +import org.joml.Matrix4f; + +import java.util.ArrayList; +import java.util.List; + +public class Model { + public List meshes; + public List materials; + + public Matrix4f modelMatrix; + + public Model(Mesh mesh, Material mat) { + meshes = new ArrayList<>(); + meshes.add(mesh); + + materials = new ArrayList<>(); + materials.add(mat); + } + + public Model(List meshes, List materials) { + this.meshes = meshes; + this.materials = materials; + } + + public void render(Shader shader) { + shader.setUniform("model", modelMatrix); + + Matrix3f normalMatrix = new Matrix3f(modelMatrix).invert().transpose(); + shader.setUniform("normalMatrix", normalMatrix); + + for (Mesh m : meshes) { + Material mat = materials.get(m.material); + + // set material uniforms + shader.setUniform("matAmbient", mat.ambient); + shader.setUniform("matDiffuse", mat.diffuse); + shader.setUniform("matSpecular", mat.specular); + shader.setUniform("shininess", mat.shininess); + + shader.setUniform("hasDiffuseMap", false); + shader.setUniform("hasSpecularMap", false); + + m.render(); + } + } +} diff --git a/src/main/java/ModelLoader.java b/src/main/java/ModelLoader.java new file mode 100644 index 0000000..7af9ee7 --- /dev/null +++ b/src/main/java/ModelLoader.java @@ -0,0 +1,117 @@ +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.joml.Vector4f; +import org.lwjgl.PointerBuffer; +import org.lwjgl.assimp.*; + +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; + +import static org.lwjgl.assimp.Assimp.*; + +public class ModelLoader { + + + private static final Logger logger = LogManager.getLogger(ModelLoader.class); + + public static Model loadModel(String resourcePath) { + AIScene scene = aiImportFile(resourcePath, + aiProcess_Triangulate | + aiProcess_FlipUVs | + aiProcess_JoinIdenticalVertices | + aiProcess_GenSmoothNormals | + aiProcess_ConvertToLeftHanded); + + if (scene == null || scene.mRootNode() == null) { + logger.error("Failed to load model %s".formatted(resourcePath)); + return null; + } + + List meshList = new ArrayList<>(); + int numMeshes = scene.mNumMeshes(); + PointerBuffer aiMeshes = scene.mMeshes(); + + for (int i = 0; i < numMeshes; i++) { + AIMesh aiMesh = AIMesh.create(aiMeshes.get(i)); + Mesh mesh = processMesh(aiMesh); + meshList.add(mesh); + } + + int numMaterials = scene.mNumMaterials(); + PointerBuffer aiMaterials = scene.mMaterials(); + List materialList = new ArrayList<>(); + + for (int i = 0; i < numMaterials; i++) { + AIMaterial aiMaterial = AIMaterial.create(aiMaterials.get(i)); + materialList.add(processMaterial(aiMaterial)); + } + + return new Model(meshList, materialList); + } + + private static Mesh processMesh(AIMesh aiMesh) { + int numVertices = aiMesh.mNumVertices(); + float[] positions = new float[numVertices * 3]; + float[] uvs = new float[numVertices * 2]; + float[] normals = new float[numVertices * 3]; + + for (int i = 0; i < numVertices; i++) { + AIVector3D pos = aiMesh.mVertices().get(i); + positions[i * 3] = (pos.x()); + positions[i * 3 + 1] = (pos.y()); + positions[i * 3 + 2] = (pos.z()); + + if (aiMesh.mTextureCoords(0) != null) { + AIVector3D tex = aiMesh.mTextureCoords(0).get(i); + uvs[i * 2] = tex.x(); + uvs[i * 2 + 1] = tex.y(); + } else { + uvs[i * 2] = 0.0f; + uvs[i * 2 + 1] = 0.0f; + } + + AIVector3D norm = aiMesh.mNormals().get(i); + normals[i * 3] = norm.x(); + normals[i * 3 + 1] = norm.y(); + normals[i * 3 + 2] = norm.z(); + } + + List indicesList = new ArrayList<>(); + int numFaces = aiMesh.mNumFaces(); + AIFace.Buffer aiFaces = aiMesh.mFaces(); + for (int i = 0; i < numFaces; i++) { + AIFace aiFace = aiFaces.get(i); + IntBuffer buffer = aiFace.mIndices(); + while (buffer.hasRemaining()) { + indicesList.add(buffer.get()); + } + } + + int[] indices = new int[indicesList.size()]; + for (int i = 0; i < indicesList.size(); i++) { + indices[i] = indicesList.get(i); + } + + return new Mesh(positions, uvs, normals, indices, aiMesh.mMaterialIndex()); + } + + private static Material processMaterial(AIMaterial aiMaterial) { + Material material = new Material(); + AIColor4D color = AIColor4D.create(); + + if (Assimp.aiGetMaterialColor(aiMaterial, Assimp.AI_MATKEY_COLOR_DIFFUSE, 0, 0, color) == Assimp.aiReturn_SUCCESS) { + material.diffuse.set(color.r(), color.g(), color.b()); + } + + if (Assimp.aiGetMaterialColor(aiMaterial, Assimp.AI_MATKEY_COLOR_AMBIENT, 0, 0, color) == Assimp.aiReturn_SUCCESS) { + material.ambient.set(color.r(), color.g(), color.b()); + } + + if (Assimp.aiGetMaterialColor(aiMaterial, Assimp.AI_MATKEY_COLOR_SPECULAR, 0, 0, color) == Assimp.aiReturn_SUCCESS) { + material.specular.set(color.r(), color.g(), color.b()); + } + + return material; + } +} diff --git a/src/main/java/OrthoCamera.java b/src/main/java/OrthoCamera.java index d619edb..63ff56c 100644 --- a/src/main/java/OrthoCamera.java +++ b/src/main/java/OrthoCamera.java @@ -32,4 +32,6 @@ public class OrthoCamera { public Matrix4f getView() { return view; } + + public Vector3f getPosition() { return position; }; } diff --git a/src/main/java/PrimitiveGenerator.java b/src/main/java/PrimitiveGenerator.java index 9e136af..af77fbb 100644 --- a/src/main/java/PrimitiveGenerator.java +++ b/src/main/java/PrimitiveGenerator.java @@ -2,7 +2,7 @@ import org.joml.Vector2f; import org.joml.Vector3f; public class PrimitiveGenerator { - public static Mesh getCube() { + public static Model getCube() { float s = 0.5f; // Positions: 24 vertices (6 faces * 4 vertices) @@ -54,6 +54,9 @@ public class PrimitiveGenerator { indices[idx + 5] = offset + 0; } - return new Mesh(p, u, n, indices); + Mesh mesh = new Mesh(p, u, n, indices, 0); + Material mat = new Material(); + + return new Model(mesh, mat); } } diff --git a/src/main/java/Shader.java b/src/main/java/Shader.java index 84b2c95..c0559e4 100644 --- a/src/main/java/Shader.java +++ b/src/main/java/Shader.java @@ -1,8 +1,11 @@ +import org.joml.Matrix3f; import org.joml.Matrix4f; import org.joml.Vector3f; +import org.joml.Vector4f; import org.lwjgl.system.MemoryStack; import java.nio.FloatBuffer; +import java.nio.IntBuffer; import java.util.HashMap; import java.util.Vector; @@ -74,7 +77,21 @@ public class Shader { logger.error(msg); return; } - glUniform3fv(location, fb.array()); + glUniform3f(location, fb.get(0), fb.get(1), fb.get(2)); + } + } + + public void setUniform(String uniformName, Vector4f data) { + try (MemoryStack stack = MemoryStack.stackPush()) { + FloatBuffer fb = stack.mallocFloat(4); + data.get(fb); + int location = getUniformLocation(uniformName); + if (location < 0) { + var msg = "Failed to find location for uniform name %s".formatted(uniformName); + logger.error(msg); + return; + } + glUniform4f(location, fb.get(0), fb.get(1), fb.get(2), fb.get(3)); } } @@ -92,6 +109,34 @@ public class Shader { } } + public void setUniform(String uniformName, Matrix3f data) { + try (MemoryStack stack = MemoryStack.stackPush()) { + FloatBuffer fb = stack.mallocFloat(9); + data.get(fb); + int location = getUniformLocation(uniformName); + if (location < 0) { + var msg = "Failed to find location for uniform name %s".formatted(uniformName); + logger.error(msg); + return; + } + glUniformMatrix3fv(location, false, fb); + } + } + + public void setUniform(String uniformName, boolean data) { + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer fb = stack.mallocInt(1); + fb.put(data ? 1 : 0); + int location = getUniformLocation(uniformName); + if (location < 0) { + var msg = "Failed to find location for uniform name %s".formatted(uniformName); + logger.error(msg); + return; + } + glUniform1i(location, fb.get(0)); + } + } + private int getUniformLocation(String uniformName) { if (!uniforms.containsKey(uniformName)) { int location = glGetUniformLocation(id, uniformName); diff --git a/src/main/java/Texture.java b/src/main/java/Texture.java new file mode 100644 index 0000000..8088c89 --- /dev/null +++ b/src/main/java/Texture.java @@ -0,0 +1,44 @@ +import java.nio.ByteBuffer; + +import static org.lwjgl.opengl.GL40.*; +import static org.lwjgl.stb.STBImage.*; + +public class Texture { + private final int id; + private final int width; + private final int height; + + public Texture(String filePath) { + int[] w = new int[1]; + int[] h = new int[1]; + int[] channels = new int[1]; + + // Load image pixels + ByteBuffer data = stbi_load(filePath, w, h, channels, 4); + if (data == null) { + throw new RuntimeException("Texture file not found: " + filePath); + } + + this.width = w[0]; + this.height = h[0]; + + // Create OpenGL texture + this.id = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, id); + + // Texture Parameters (Essential for your Isometric view) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Upload data + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + stbi_image_free(data); + } + + public void bind(int unit) { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D, id); + } +} \ No newline at end of file diff --git a/src/main/java/TextureLoader.java b/src/main/java/TextureLoader.java new file mode 100644 index 0000000..2e3f26f --- /dev/null +++ b/src/main/java/TextureLoader.java @@ -0,0 +1,2 @@ +public class TextureLoader { +} diff --git a/src/main/java/Tony.java b/src/main/java/Tony.java index 66c1ac6..f143469 100644 --- a/src/main/java/Tony.java +++ b/src/main/java/Tony.java @@ -1,3 +1,4 @@ +import org.joml.Matrix3f; import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.*; @@ -6,6 +7,7 @@ import org.lwjgl.opengl.*; import org.lwjgl.system.*; import java.nio.*; +import java.util.List; import static org.lwjgl.glfw.Callbacks.*; import static org.lwjgl.glfw.GLFW.*; @@ -19,9 +21,10 @@ import org.apache.logging.log4j.Logger; public class Tony { private long window; private Shader shader; - private Mesh cube; + private Model cube; private OrthoCamera camera; private DirectionalLight sun; + private Model tree; private static final Logger logger = LogManager.getLogger(Tony.class); @@ -35,12 +38,19 @@ public class Tony { shader = new Shader("/shaders/basic.vert", "/shaders/basic.frag"); cube = PrimitiveGenerator.getCube(); + tree = ModelLoader.loadModel("tree.fbx"); + tree.modelMatrix = new Matrix4f() + .translation(new Vector3f(0.f, 0.f, 0.f)) + .rotate((float)Math.toRadians(90), 1, 0, 0) + .scale(.2f); + camera = new OrthoCamera(1024.f / 720); sun = new DirectionalLight( - new Vector3f(-1.0f, -1.0f, -0.5f), // Pointing Down, Left, and slightly Back - new Vector3f(1.0f, 1.0f, 0.9f), // Warm yellowish sun - 1.2f // Brightness + new Vector3f(-3.0f, -3.0f, -0.5f), + new Vector3f(.2f, .2f, .2f), + new Vector3f(0.5f, 0.5f, 0.5f), + new Vector3f(1.f, 1.f, 1.f) ); loop(); @@ -90,34 +100,31 @@ public class Tony { } // the stack frame is popped automatically glfwMakeContextCurrent(window); - GL.createCapabilities(); - - glViewport(0, 0, 1024, 720); - glfwSwapInterval(1); glfwShowWindow(window); + + GL.createCapabilities(); + glViewport(0, 0, 1024, 720); + glEnable(GL_DEPTH_TEST); + glClearColor(0.33f, 0.48f, 0.48f, 1.0f); } private void loop() { - glClearColor(0.33f, 0.48f, 0.48f, 1.0f); - - Matrix4f modelMatrix = new Matrix4f(); - while ( !glfwWindowShouldClose(window) ) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shader.bind(); - modelMatrix.rotateXYZ(0, 0.01f, 0); - shader.setUniform("projection", camera.getProjection()); shader.setUniform("view", camera.getView()); - shader.setUniform("model", modelMatrix); - shader.setUniform("lightDirection", sun.direction); - shader.setUniform("lightColor", sun.color); - shader.setUniform("lightIntensity", sun.intensity); + shader.setUniform("viewPos", camera.getPosition()); - cube.render(); + shader.setUniform("lightDirection", sun.direction); + shader.setUniform("lightAmbient", sun.ambient); + shader.setUniform("lightDiffuse", sun.diffuse); + shader.setUniform("lightSpecular", sun.specular); + + tree.render(shader); shader.unbind(); diff --git a/src/main/resources/shaders/basic.frag b/src/main/resources/shaders/basic.frag index 01068c2..e5a3f50 100644 --- a/src/main/resources/shaders/basic.frag +++ b/src/main/resources/shaders/basic.frag @@ -1,25 +1,59 @@ #version 400 core -out vec4 color; +out vec4 FragColor; -in vec2 texCoord; -in vec3 normal; +in vec3 FragPos; +in vec3 Normal; +in vec2 TexCoords; + +uniform vec3 viewPos; -uniform sampler2D texture_diffuse; uniform vec3 lightDirection; -uniform vec3 lightColor; -uniform float lightIntensity; +uniform vec3 lightAmbient; +uniform vec3 lightDiffuse; +uniform vec3 lightSpecular; -void main() { - vec3 normal = normalize(outNormal); - vec3 lightDir = normalize(-lightDirection); +uniform vec3 matAmbient; +uniform vec3 matDiffuse; +uniform vec3 matSpecular; +uniform float shininess; - float diff = max(dot(normal, lightDir), 0.0); +uniform sampler2D diffuseMap; +uniform sampler2D specularMap; +uniform bool hasDiffuseMap; +uniform bool hasSpecularMap; - float ambient = 0.2; +void main() +{ + vec3 N = normalize(Normal); + vec3 V = normalize(viewPos - FragPos); + vec3 L = normalize(-lightDirection); - vec3 diffuse = diff * lightColor * lightIntensity; - vec4 texColor = texture(texture_diffuse, texCoord); + vec3 texDiffuse = hasDiffuseMap + ? texture(diffuseMap, TexCoords).rgb + : vec3(1.0); - color = vec4(ambient + diffuse, 1.0) * texColor; + vec3 texSpecular = hasSpecularMap + ? texture(specularMap, TexCoords).rgb + : vec3(1.0); + + vec3 albedo = matDiffuse * texDiffuse; + vec3 specColor = matSpecular * texSpecular; + vec3 ambientCol = matAmbient * texDiffuse; + + vec3 ambient = lightAmbient * ambientCol; + + float diff = max(dot(N, L), 0.0); + vec3 diffuse = lightDiffuse * diff * albedo; + + vec3 specular = vec3(0.0); + if (diff > 0.0) { + vec3 H = normalize(L + V); + float spec = pow(max(dot(N, H), 0.0), shininess); + specular = lightSpecular * spec * specColor; + } + + vec3 result = ambient + diffuse + specular; + + FragColor = vec4(result, 1.0); } \ No newline at end of file diff --git a/src/main/resources/shaders/basic.vert b/src/main/resources/shaders/basic.vert index 23410ca..5a8bc7e 100644 --- a/src/main/resources/shaders/basic.vert +++ b/src/main/resources/shaders/basic.vert @@ -1,13 +1,24 @@ #version 400 core -layout (location = 0) in vec3 position; -layout (location = 1) in vec2 uv; -layout (location = 2) in vec3 +layout (location = 0) in vec3 aPosition; +layout (location = 1) in vec2 aTexCoord; +layout (location = 2) in vec3 aNormal; -uniform mat4 model; uniform mat4 view; uniform mat4 projection; +uniform mat4 model; +uniform mat3 normalMatrix; + +out vec3 FragPos; +out vec3 Normal; +out vec2 TexCoords; + void main() { - gl_Position = projection * view * model * vec4(position, 1.0); + vec4 position = projection * view * model * vec4(aPosition, 1.0); + FragPos = vec3(position.x, position.y, position.z); + TexCoords = aTexCoord; + Normal = normalize(normalMatrix * aNormal); + + gl_Position = position; } \ No newline at end of file diff --git a/tree.fbx b/tree.fbx new file mode 100644 index 0000000..f8e16b6 Binary files /dev/null and b/tree.fbx differ