I am trying to use JOGL and its event listener class to make a shadowMap Shader. I would like it to be 2d (top down), where the view or camera is centered above, looking down. It should then have a point light that faces all 4 directions (x, -x ,y, -y), while its z stays constant and set. When I render an object the object should obstruct the light and have a shadow visible on the x, y plane. I use a object vao to create the depthMap, in my shaders 1rst pass, then on the second I create the shadows, create a diffusing gradient from the point light. Here is my Code. import com.jogamp.opengl.*;import com.sun.prism.impl.BufferUtil;import org.joml.Matrix4f;import org.joml.Vector3f;import java.io.IOException;import java.nio.FloatBuffer;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import static com.jogamp.opengl.GL2.*;public class Event implements GLEventListener { private int shaderProgram; private int shadowFBO, shadowTexture; private int vaoObject, vaoGround; // One for the occluder and one for the ground // Uniform locations private int modelLoc, viewLoc, projLoc, lightSpaceLoc, lightPosLoc, passModeLoc, shadowMapLoc; // Matrices private Matrix4f modelMatrix = new Matrix4f(); private Matrix4f viewMatrix = new Matrix4f(); private Matrix4f projectionMatrix = new Matrix4f(); private Matrix4f lightSpaceMatrix = new Matrix4f(); // Light and camera positions (world space) private Vector3f lightPos = new Vector3f(2.0f, 2.0f, 0.0f); // Light on the XY plane private Vector3f cameraPos = new Vector3f(0.0f, 0.0f, 10.0f); // Top‑down camera // Shadow map settings private int shadowMapWidth = 1024, shadowMapHeight = 1024; // shadowRange determines how far the light covers (used for normalization) private float shadowRange = 10.0f; @Override public void init(GLAutoDrawable drawable) { GL2 gl = drawable.getGL().getGL2(); // Set clear color to white and enable depth testing. gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); gl.glEnable(GL_DEPTH_TEST); // Compile and link the shader program. shaderProgram = createShaderProgram(gl, "shadow.vert", "shadow.frag"); // Bind the sampler uniform "shadowMap" to texture unit 0. gl.glUseProgram(shaderProgram); shaderProgram = createShaderProgram(gl, "res/shadowVertexShader.glsl", "res/shadowFragmentShader.glsl"); gl.glUniform1i(shadowMapLoc, 0); // Bind to texture unit 0 gl.glUseProgram(0); // Retrieve other uniform locations. gl.glUseProgram(shaderProgram); modelLoc = gl.glGetUniformLocation(shaderProgram, "model"); viewLoc = gl.glGetUniformLocation(shaderProgram, "view"); projLoc = gl.glGetUniformLocation(shaderProgram, "projection"); lightSpaceLoc = gl.glGetUniformLocation(shaderProgram, "lightSpace"); lightPosLoc = gl.glGetUniformLocation(shaderProgram, "lightPos"); passModeLoc = gl.glGetUniformLocation(shaderProgram, "passMode"); gl.glUseProgram(0); // Set up geometry: // - The occluding object (a quad from -1 to 1) vaoObject = setupObjectVAO(gl); // - The ground (a larger quad covering the scene) vaoGround = setupGroundVAO(gl); // --- Create Shadow Map FBO and Depth Texture --- int[] fbos = new int[1]; gl.glGenFramebuffers(1, fbos, 0); shadowFBO = fbos[0]; int[] textures = new int[1]; gl.glGenTextures(1, textures, 0); shadowTexture = textures[0]; gl.glBindTexture(GL_TEXTURE_2D, shadowTexture); // Create a depth texture to store normalized depth values. gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, null); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); float[] borderColor = {1, 1, 1, 1}; gl.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor, 0); // IMPORTANT: Disable hardware depth comparison to sample raw depth values. gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); gl.glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO); gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowTexture, 0); gl.glDrawBuffer(GL_NONE); gl.glReadBuffer(GL_NONE); if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { System.err.println("Error: Shadow FBO not complete."); } gl.glBindFramebuffer(GL_FRAMEBUFFER, 0); } @Override public void display(GLAutoDrawable drawable) { GL2 gl = drawable.getGL().getGL2(); // --- Shadow Map Pass --- // Set up the light's view and projection. Matrix4f lightView = new Matrix4f().lookAt(lightPos, new Vector3f(0, 0, 0), new Vector3f(0, 0, 1)); Matrix4f lightProj = new Matrix4f().ortho(-shadowRange, shadowRange, -shadowRange, shadowRange, 1.0f, 20.0f); lightSpaceMatrix = new Matrix4f(); lightProj.mul(lightView, lightSpaceMatrix); // Render the occluding object into the shadow map. gl.glViewport(0, 0, shadowMapWidth, shadowMapHeight); gl.glBindFramebuffer(GL_FRAMEBUFFER, shadowFBO); gl.glClear(GL_DEPTH_BUFFER_BIT); gl.glUseProgram(shaderProgram); gl.glUniform1i(passModeLoc, 0); // passMode 0: shadow map generation modelMatrix.identity(); // Object centered at origin. gl.glUniformMatrix4fv(modelLoc, 1, false, toFloatBuffer(modelMatrix)); gl.glUniformMatrix4fv(viewLoc, 1, false, toFloatBuffer(lightView)); gl.glUniformMatrix4fv(projLoc, 1, false, toFloatBuffer(lightProj)); gl.glUniformMatrix4fv(lightSpaceLoc, 1, false, toFloatBuffer(lightSpaceMatrix)); gl.glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z); renderObject(gl, vaoObject); gl.glBindFramebuffer(GL_FRAMEBUFFER, 0); // --- Scene Pass --- int width = drawable.getSurfaceWidth(); int height = drawable.getSurfaceHeight(); gl.glViewport(0, 0, width, height); gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Set up a top‑down camera view. viewMatrix.identity().lookAt(cameraPos, new Vector3f(0, 0, 0), new Vector3f(0, 1, 0)); projectionMatrix.identity().ortho(-5, 5, -5, 5, 1.0f, 20.0f); gl.glUniform1i(passModeLoc, 1); // passMode 1: scene shading gl.glUniformMatrix4fv(viewLoc, 1, false, toFloatBuffer(viewMatrix)); gl.glUniformMatrix4fv(projLoc, 1, false, toFloatBuffer(projectionMatrix)); gl.glUniformMatrix4fv(lightSpaceLoc, 1, false, toFloatBuffer(lightSpaceMatrix)); gl.glUniform3f(lightPosLoc, lightPos.x, lightPos.y, lightPos.z); // Bind the shadow map texture for sampling. gl.glActiveTexture(GL_TEXTURE0); gl.glBindTexture(GL_TEXTURE_2D, shadowTexture); // Render the ground first. modelMatrix.identity(); // Ground at z = 0. gl.glUniformMatrix4fv(modelLoc, 1, false, toFloatBuffer(modelMatrix)); renderObject(gl, vaoGround); // Then render the occluding object on top with a slight z-offset. modelMatrix.identity().translate(0, 0, 0.01f); gl.glUniformMatrix4fv(modelLoc, 1, false, toFloatBuffer(modelMatrix)); renderObject(gl, vaoObject); gl.glUseProgram(0); } // Helper: Converts a Matrix4f to a FloatBuffer. private FloatBuffer toFloatBuffer(Matrix4f mat) { FloatBuffer fb = BufferUtil.newFloatBuffer(16); mat.get(fb); return fb; } // Sets up a VAO for the occluding object (a quad from -1 to 1). private int setupObjectVAO(GL2 gl) { float[] vertices = { // positions // tex coords (unused, but passed through) -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; int[] vao = new int[1]; int[] vbo = new int[1]; gl.glGenVertexArrays(1, vao, 0); gl.glGenBuffers(1, vbo, 0); gl.glBindVertexArray(vao[0]); gl.glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); FloatBuffer vertexBuffer = BufferUtil.newFloatBuffer(vertices.length); vertexBuffer.put(vertices).flip(); gl.glBufferData(GL_ARRAY_BUFFER, vertices.length * Float.BYTES, vertexBuffer, GL_STATIC_DRAW); // Position attribute (location 0) gl.glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * Float.BYTES, 0); gl.glEnableVertexAttribArray(0); // Tex coord attribute (location 1) gl.glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * Float.BYTES, 3 * Float.BYTES); gl.glEnableVertexAttribArray(1); gl.glBindBuffer(GL_ARRAY_BUFFER, 0); gl.glBindVertexArray(0); return vao[0]; } // Sets up a VAO for the ground (a large quad covering -5 to 5). private int setupGroundVAO(GL2 gl) { float[] vertices = { // positions // tex coords -5.0f, -5.0f, 0.0f, 0.0f, 0.0f, 5.0f, -5.0f, 0.0f, 1.0f, 0.0f, -5.0f, 5.0f, 0.0f, 0.0f, 1.0f, 5.0f, 5.0f, 0.0f, 1.0f, 1.0f, }; int[] vao = new int[1]; int[] vbo = new int[1]; gl.glGenVertexArrays(1, vao, 0); gl.glGenBuffers(1, vbo, 0); gl.glBindVertexArray(vao[0]); gl.glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); FloatBuffer vertexBuffer = BufferUtil.newFloatBuffer(vertices.length); vertexBuffer.put(vertices).flip(); gl.glBufferData(GL_ARRAY_BUFFER, vertices.length * Float.BYTES, vertexBuffer, GL_STATIC_DRAW); gl.glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * Float.BYTES, 0); gl.glEnableVertexAttribArray(0); gl.glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * Float.BYTES, 3 * Float.BYTES); gl.glEnableVertexAttribArray(1); gl.glBindBuffer(GL_ARRAY_BUFFER, 0); gl.glBindVertexArray(0); return vao[0]; } private void renderObject(GL2 gl, int vao) { gl.glBindVertexArray(vao); gl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); gl.glBindVertexArray(0); } // Loads, compiles, and links the vertex and fragment shaders. private int createShaderProgram(GL2 gl, String vertPath, String fragPath) { String vertSource = ""; String fragSource = ""; try { vertSource = new String(Files.readAllBytes(Paths.get(vertPath)), StandardCharsets.UTF_8); fragSource = new String(Files.readAllBytes(Paths.get(fragPath)), StandardCharsets.UTF_8); } catch (IOException e) { System.err.println("Error loading shader files: " + e.getMessage()); } int vertexShader = gl.glCreateShader(GL2.GL_VERTEX_SHADER); gl.glShaderSource(vertexShader, 1, new String[]{vertSource}, null, 0); gl.glCompileShader(vertexShader); int[] compiled = new int[1]; gl.glGetShaderiv(vertexShader, GL2.GL_COMPILE_STATUS, compiled, 0); if(compiled[0] == GL_FALSE) { System.err.println("Vertex shader compilation failed."); } int fragmentShader = gl.glCreateShader(GL2.GL_FRAGMENT_SHADER); gl.glShaderSource(fragmentShader, 1, new String[]{fragSource}, null, 0); gl.glCompileShader(fragmentShader); gl.glGetShaderiv(fragmentShader, GL2.GL_COMPILE_STATUS, compiled, 0); if(compiled[0] == GL_FALSE) { System.err.println("Fragment shader compilation failed."); } int program = gl.glCreateProgram(); gl.glAttachShader(program, vertexShader); gl.glAttachShader(program, fragmentShader); gl.glLinkProgram(program); int[] linked = new int[1]; gl.glGetProgramiv(program, GL2.GL_LINK_STATUS, linked, 0); if(linked[0] == GL_FALSE) { System.err.println("Shader program linking failed."); } return program; } @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { GL2 gl = drawable.getGL().getGL2(); gl.glViewport(0, 0, width, height); } @Override public void dispose(GLAutoDrawable drawable) { // Clean up resources (FBOs, textures, shader program, VAOs) as needed. }}
and here are my shader codes starting with the vertex#version 330 corelayout (location = 0) in vec3 aPos;layout (location = 1) in vec2 aTexCoord;uniform mat4 model;uniform mat4 view;uniform mat4 projection;uniform mat4 lightSpace;out vec4 FragPosLight; // For shadow lookup in the fragment shaderout vec3 FragPos; // World-space positionvoid main() { vec4 worldPos = model * vec4(aPos, 1.0); FragPos = worldPos.xyz; FragPosLight = lightSpace * worldPos; gl_Position = projection * view * worldPos;}
and the fragment#version 330 corein vec4 FragPosLight;in vec3 FragPos;uniform sampler2D shadowMap;uniform vec3 lightPos;uniform int passMode; // 0 = shadow map pass, 1 = scene passout vec4 FragColor;float calculateShadow(vec4 fragPosLight, float currentDepth) { vec3 projCoords = fragPosLight.xyz / fragPosLight.w; vec2 shadowUV = projCoords.xy * 0.1 + 0.5; float closestDepth = texture(shadowMap, shadowUV).r; float bias = 0.02; return currentDepth - bias > closestDepth ? 0.0 : 1.0;}void main() { if(passMode == 0) { // In shadow pass, store the normalized radial distance from the light. float dist = length(FragPos.xy - lightPos.xy); float normalizedDepth = clamp(dist / 5.0, 0.0, 1.0); gl_FragDepth = normalizedDepth; return; } else if(passMode == 1) { // In scene pass, compute the shadow factor. float dist = length(FragPos.xy - lightPos.xy); float currentDepth = clamp(dist / 5.0, 0.0, 1.0); float shadow = calculateShadow(FragPosLight, currentDepth); // The ground (or object) appears white where lit and black where in shadow. vec3 baseColor = vec3(1.0); vec3 litColor = baseColor * shadow; FragColor = texture(shadowMap, projCoords.xy); }}out vec4 FragColor;in vec2 TexCoords;in vec3 FragPos;// Light properties: light is on the XY plane (z = 0)uniform vec3 lightPos;vec3 lightColor = vec3(1.0); // White lightvoid main() { float maxDist = 10.0; // Maximum distance for shadow normalization if(passMode == 0) { // --- PASS 0: Shadow Map Generation --- float xyDist = length(FragPos.xy - lightPos.xy); gl_FragDepth = clamp(xyDist / maxDist, 0.0, 1.0); return; } else if(passMode == 1) { // --- PASS 1: Render Scene with Shadows --- vec2 shadowUV = (FragPos.xy - (lightPos.xy - vec2(maxDist))) / (2.0 * maxDist); float currentDepth = length(FragPos.xy - lightPos.xy) / maxDist; float shadowMapDepth = texture(shadowMap, shadowUV).r; float bias = 0.01; // Small bias to reduce self-shadowing artifacts. float shadow = (currentDepth > shadowMapDepth + bias) ? 1.0 : 0.0; // Use red for debugging shadowed regions. FragColor = vec4((1.0 - shadow) * lightColor, 1.0); } else if(passMode == 2) { // --- PASS 2: Post‑Processing (Render full‑screen quad) --- // For testing, output a constant red color. // In a final implementation you might sample the sceneTexture: FragColor = texture(sceneTexture, TexCoords); }}
I think the depth values of my vao's might be causing issues as well as the translating of the coordinate system I use for the JOGL code (creating the vaos) and the GLSL shaer which uses a coordainte system of 0 → 1 for the x and 0→1 for the y.
Many Thanks