diff --git a/shaders/depth_normals.frag b/shaders/depth_normals.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5a59e0860fbec2a1077d5d5cd8621b0cd9e8adc7
--- /dev/null
+++ b/shaders/depth_normals.frag
@@ -0,0 +1,122 @@
+#version 330 core
+
+in vec2 TexCoords;
+out vec4 FragColor;
+
+// The linear depth texture (0..1 -> zNear..zFar)
+uniform sampler2D uDepthTex;
+
+// The inverse of the projection matrix (pass from CPU)
+uniform mat4 invProjectionMatrix;
+
+// Optional max depth clamp
+uniform float maxDepth; // e.g., 1.0 if 1 means zFar
+
+// Offset in texture coordinates for neighbor sampling
+// Usually (1.0 / screenWidth, 1.0 / screenHeight)
+uniform vec2 uTexelSize;
+
+void main()
+{
+    // 1) Sample center depth
+    float depthLin = texture(uDepthTex, TexCoords).r;
+    // Validate
+    if (depthLin < 0.0 || depthLin > 1.0 || depthLin > maxDepth)
+    {
+        discard;
+    }
+
+    // 2) Convert to clip space
+    // OpenGL clip space: X, Y in [-1,1], Z in [-1,1].
+    // Our linear depth is [0..1], we map it to clip Z by (depthLin * 2 - 1).
+    vec4 clipPos = vec4(
+        TexCoords.x * 2.0 - 1.0,
+        TexCoords.y * 2.0 - 1.0,
+        depthLin      * 2.0 - 1.0,
+        1.0
+    );
+
+    // 3) Inverse-project to eye space
+    vec4 eyePos4 = invProjectionMatrix * clipPos;
+    // perspective divide
+    eyePos4.xyz /= eyePos4.w;
+    vec3 centerEye = eyePos4.xyz; // The actual eye-space position
+
+    // 4) Sample neighbors and reconstruct their eye-space positions
+    // We'll do left-right in x direction
+    vec2 uvLeft  = TexCoords + vec2(-uTexelSize.x, 0.0);
+    vec2 uvRight = TexCoords + vec2( uTexelSize.x, 0.0);
+
+    float depthL = texture(uDepthTex, uvLeft).r;
+    float depthR = texture(uDepthTex, uvRight).r;
+
+    vec3 leftEye  = vec3(0.0);
+    vec3 rightEye = vec3(0.0);
+
+    if (depthL >= 0.0 && depthL <= maxDepth) {
+        vec4 clipL = vec4(uvLeft.x * 2.0 - 1.0, uvLeft.y * 2.0 - 1.0, depthL * 2.0 - 1.0, 1.0);
+        vec4 epL   = invProjectionMatrix * clipL;
+        leftEye    = epL.xyz / epL.w;
+    } else {
+        discard; // or skip partial if invalid
+    }
+
+    if (depthR >= 0.0 && depthR <= maxDepth) {
+        vec4 clipR = vec4(uvRight.x * 2.0 - 1.0, uvRight.y * 2.0 - 1.0, depthR * 2.0 - 1.0, 1.0);
+        vec4 epR   = invProjectionMatrix * clipR;
+        rightEye   = epR.xyz / epR.w;
+    } else {
+        discard;
+    }
+
+    // partial derivative in x
+    vec3 ddx  = rightEye - centerEye;
+    vec3 ddx2 = centerEye - leftEye;
+    if (abs(ddx2.z) < abs(ddx.z)) {
+        ddx = ddx2;
+    }
+
+    // 5) same approach for up-down
+    vec2 uvUp = TexCoords + vec2(0.0,  uTexelSize.y);
+    vec2 uvDn = TexCoords + vec2(0.0, -uTexelSize.y);
+
+    float depthU = texture(uDepthTex, uvUp).r;
+    float depthD = texture(uDepthTex, uvDn).r;
+
+    vec3 upEye  = vec3(0.0);
+    vec3 dnEye  = vec3(0.0);
+
+    if (depthU >= 0.0 && depthU <= maxDepth) {
+        vec4 clipU = vec4(uvUp.x * 2.0 - 1.0, uvUp.y * 2.0 - 1.0, depthU * 2.0 - 1.0, 1.0);
+        vec4 epU   = invProjectionMatrix * clipU;
+        upEye      = epU.xyz / epU.w;
+    } else {
+        discard;
+    }
+
+    if (depthD >= 0.0 && depthD <= maxDepth) {
+        vec4 clipD = vec4(uvDn.x * 2.0 - 1.0, uvDn.y * 2.0 - 1.0, depthD * 2.0 - 1.0, 1.0);
+        vec4 epD   = invProjectionMatrix * clipD;
+        dnEye      = epD.xyz / epD.w;
+    } else {
+        discard;
+    }
+
+    vec3 ddy  = upEye - centerEye;
+    vec3 ddy2 = centerEye - dnEye;
+    if (abs(ddy2.z) < abs(ddy.z)) {
+        ddy = ddy2;
+    }
+
+    // 6) Cross partial derivatives to get normal
+    vec3 N = normalize(cross(ddx, ddy));
+
+    // Optionally flip if needed
+    if (N.z > 0.0) {
+        N = -N;
+    }
+
+    // Shift from [-1..1] to [0..1]
+    vec3 normalColor = 0.5 * (N + vec3(1.0));
+    FragColor = vec4(normalColor, 1.0);
+}
diff --git a/shaders/depth_normals.vert b/shaders/depth_normals.vert
new file mode 100644
index 0000000000000000000000000000000000000000..4c10958de1a3c99460272ce821a2f2b3f69424e2
--- /dev/null
+++ b/shaders/depth_normals.vert
@@ -0,0 +1,12 @@
+#version 330 core
+
+layout(location = 0) in vec3 aPos;
+layout(location = 1) in vec2 aTexCoord;
+
+out vec2 TexCoords;
+
+void main()
+{
+    TexCoords = aTexCoord;
+    gl_Position = vec4(aPos, 1.0);
+}
diff --git a/src/FluidRenderer.cpp b/src/FluidRenderer.cpp
index 825623ffaa3499d23def3e040943df18d28fbb8a..4b57d6bcec29e942f5870b67d0aff3cc22702d5b 100644
--- a/src/FluidRenderer.cpp
+++ b/src/FluidRenderer.cpp
@@ -133,6 +133,7 @@ void FluidRenderer::initShaders() {
     glUseProgram(m_depthRenderProgram.programID);
     std::cout << "Depth Render Shaders initialized successfully." << std::endl;
 
+    // Depth filter program
     m_depthFilterProgram.programID = loadShaders(
         "../shaders/depth_filter.vert",
         "../shaders/depth_filter.frag"
@@ -144,6 +145,18 @@ void FluidRenderer::initShaders() {
     glUseProgram(m_depthFilterProgram.programID);
     std::cout << "Depth Filter Shaders initialized successfully." << std::endl;
 
+    // Depth Normals Program
+    m_depthNormalsProgram.programID = loadShaders(
+        "../shaders/depth_normals.vert",
+        "../shaders/depth_normals.frag"
+    );
+    if (m_depthNormalsProgram.programID == 0) {
+        std::cerr << "Failed to load Depth Normals Shaders." << std::endl;
+        exit(1);
+    }
+    glUseProgram(m_depthNormalsProgram.programID);
+    std::cout << "Depth Normals Shaders initialized successfully." << std::endl;
+
     // =================== Thickness shader stages =========================
     // Thickness Render Shader
     m_thicknessRenderProgram.programID = loadShaders(
@@ -246,6 +259,8 @@ void FluidRenderer::initShaderUniforms()
     setupShaderProgram(m_depthRenderProgram);
     // Depth Filter program:
     setupShaderProgram(m_depthFilterProgram);
+
+    setupShaderProgram(m_depthNormalsProgram);
     
     // =================== Thickness shader stages =========================
     // Thickness buffer program:
@@ -310,7 +325,7 @@ void FluidRenderer::initFBOs() {
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     glBindTexture(GL_TEXTURE_2D, 0);
 
-    // Also create an FBO for it:
+    // Create extra FBO for it:
     glGenFramebuffers(1, &m_depthFilterTempFBO);
     glBindFramebuffer(GL_FRAMEBUFFER, m_depthFilterTempFBO);
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_depthFilterTempTex, 0);
@@ -322,6 +337,9 @@ void FluidRenderer::initFBOs() {
     }
     glBindFramebuffer(GL_FRAMEBUFFER, 0);
 
+    // depth noirmals program
+    initializeFramebuffer(m_depthNormalsProgram, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE);
+
     // =================== Thickness shader stages =========================
     // Initialize Thickness Render Shader FBO with GL_R32F
     initializeFramebuffer(m_thicknessRenderProgram,
@@ -552,7 +570,7 @@ void FluidRenderer::renderDepthFrame(size_t particleCount, size_t boundaryCount)
     glUniformMatrix4fv(m_depthRenderProgram.viewMatrixLoc, 1, GL_TRUE, viewMatrix.m);
 
     if (m_depthRenderProgram.sphereRadiusLoc != -1) {
-        glUniform1f(m_depthRenderProgram.sphereRadiusLoc, 1.0f);
+        glUniform1f(m_depthRenderProgram.sphereRadiusLoc, 0.25f);
     }
     if (m_depthRenderProgram.pointRadiusLoc != -1) {
         glUniform1f(m_depthRenderProgram.pointRadiusLoc, m_pointRadius);
@@ -717,6 +735,54 @@ void FluidRenderer::renderDepthFilterFrame()
     }
 }
 
+void FluidRenderer::renderDepthNormalsFrame()
+{
+    // 1) Bind the Depth Normals FBO
+    glBindFramebuffer(GL_FRAMEBUFFER, m_depthNormalsProgram.fbo);
+    glViewport(0, 0, m_fbWidth, m_fbHeight);
+    glClearColor(0,0,0,1);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glDisable(GL_DEPTH_TEST);
+
+    // 2) Use the depthNormals program
+    glUseProgram(m_depthNormalsProgram.programID);
+
+    // 3) Bind the *filtered depth* texture as input
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, m_depthFilterProgram.fboTexture);
+    glUniform1i(glGetUniformLocation(m_depthNormalsProgram.programID, "uDepthTex"), 0);
+
+    // 4) Set uniforms: zNear, zFar, uTexelSize, maxDepth, etc.
+    //float zNear = m_camera->getNearPlane();
+    //float zFar  = m_camera->getFarPlane();
+    //glUniform1f(glGetUniformLocation(m_depthNormalsProgram.programID, "zNear"), zNear);
+    //glUniform1f(glGetUniformLocation(m_depthNormalsProgram.programID, "zFar"),  zFar);
+    mat4 proj = m_camera->getProjectionMatrix(); // Do we have to transpose our matrix???
+    mat4 invProj = inverse(proj); // or use glm::inverse(...) if using GLM
+
+    GLuint invProjLoc = glGetUniformLocation(m_depthNormalsProgram.programID, "invProjectionMatrix");
+    glUniformMatrix4fv(invProjLoc, 1, GL_FALSE, invProj.m); 
+
+    // For sampling partial derivatives:
+    float texelSizeX = 1.0f / float(m_fbWidth);
+    float texelSizeY = 1.0f / float(m_fbHeight);
+    glUniform2f(glGetUniformLocation(m_depthNormalsProgram.programID, "uTexelSize"),
+                texelSizeX, texelSizeY);
+
+    // optional maxDepth
+    glUniform1f(glGetUniformLocation(m_depthNormalsProgram.programID, "maxDepth"), 1.0f);
+
+    // 5) Render Fullscreen Quad
+    glBindVertexArray(quadVAO);
+    glDrawArrays(GL_TRIANGLES, 0, 6);
+    glBindVertexArray(0);
+
+    // 6) Restore
+    glEnable(GL_DEPTH_TEST);
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
+
 void FluidRenderer::renderThicknessFrame(size_t particleCount, size_t boundaryCount)
 {
     glBindFramebuffer(GL_FRAMEBUFFER, m_thicknessRenderProgram.fbo);
@@ -813,6 +879,8 @@ GLuint FluidRenderer::getTexture(RenderStage stage) const {
             return m_thicknessRenderProgram.fboTexture;
         case RenderStage::FilteredDepth:
             return m_depthFilterProgram.fboTexture;
+        case RenderStage::DepthNormals:
+            return m_depthNormalsProgram.fboTexture;
         default:
             return 0; // Return 0 if no valid stage was selected
     }
diff --git a/src/FluidSimulationApp.cpp b/src/FluidSimulationApp.cpp
index 477d985a0ac350c4d3b1a9b4d60dac6b6f374fac..4e772a5e2d0b85b1bc9971d24d4f2bebcc11baf0 100644
--- a/src/FluidSimulationApp.cpp
+++ b/src/FluidSimulationApp.cpp
@@ -214,44 +214,44 @@ namespace FluidSimulation {
         if (m_updatePhysics)
         {
             m_fluidSimulation->updateSimulation();
-            if (selectedStage == RenderStage::FinalOutput)
-            {
-                m_fluidRenderer->renderNormalFrame(
-                    m_fluidSimulation->getParticleCount(),
-                    m_fluidSimulation->getBoundaryParticleCount()
-                );
-            }
-            else if (selectedStage == RenderStage::DepthBuffer 
-                    || selectedStage == RenderStage::FilteredDepth)
+
+            // Precompute particle counts to avoid multiple function calls
+            size_t particleCount = m_fluidSimulation->getParticleCount();
+            size_t boundaryCount = m_fluidSimulation->getBoundaryParticleCount();
+
+            switch (selectedStage)
             {
-                m_fluidRenderer->renderDepthFrame(
-                    m_fluidSimulation->getParticleCount(),
-                    m_fluidSimulation->getBoundaryParticleCount()
-                );
+                case RenderStage::FinalOutput:
+                    m_fluidRenderer->renderNormalFrame(particleCount, boundaryCount);
+                    break;
 
-                if (selectedStage == RenderStage::FilteredDepth)
-                {
-                    printf("Thicnes stage active \n");
+                case RenderStage::DepthBuffer:
+                    m_fluidRenderer->renderDepthFrame(particleCount, boundaryCount);
+                    m_fluidRenderer->visualizeBuffer(RenderStage::DepthBuffer);
+                    break;
+
+                case RenderStage::FilteredDepth:
+                    m_fluidRenderer->renderDepthFrame(particleCount, boundaryCount);
                     m_fluidRenderer->renderDepthFilterFrame();
-                }
-                
-                // After rendering the depth frame, perform visualization
-                m_fluidRenderer->visualizeBuffer(selectedStage);
-            }
-            else //if (selectedStage == RenderStage::ThicknessBuffer)
-            {
-                printf("Render Thicnness \n");
-                if (selectedStage == RenderStage::ThicknessBuffer)
-                {
-                    printf("Thicnes stagee active \n");
-                }
-                m_fluidRenderer->renderThicknessFrame(
-                    m_fluidSimulation->getParticleCount(),
-                    m_fluidSimulation->getBoundaryParticleCount()
-                );
+                    m_fluidRenderer->visualizeBuffer(RenderStage::FilteredDepth);
+                    break;
 
-                // After rendering the depth frame, perform visualization
-                m_fluidRenderer->visualizeBuffer(selectedStage);
+                case RenderStage::DepthNormals:
+                    m_fluidRenderer->renderDepthFrame(particleCount, boundaryCount);
+                    m_fluidRenderer->renderDepthFilterFrame();
+                    m_fluidRenderer->renderDepthNormalsFrame();
+                    break;
+
+                case RenderStage::ThicknessBuffer:
+                    printf("Render Thickness \n");
+                    printf("Thickness stage active \n");
+                    m_fluidRenderer->renderThicknessFrame(particleCount, boundaryCount);
+                    m_fluidRenderer->visualizeBuffer(RenderStage::ThicknessBuffer);
+                    break;
+
+                default:
+                    std::cerr << "Unknown RenderStage selected." << std::endl;
+                    break;
             }
         }
 
@@ -261,7 +261,8 @@ namespace FluidSimulation {
         ImVec2 availableSize = ImGui::GetContentRegionAvail();
         GLuint textureID = 0;
 
-        if (selectedStage == RenderStage::FinalOutput)
+        if (selectedStage == RenderStage::FinalOutput
+            || selectedStage == RenderStage::DepthNormals)
         {
             textureID = m_fluidRenderer->getTexture(selectedStage); // RGBA8
         }
diff --git a/src/include/FluidRenderer.h b/src/include/FluidRenderer.h
index 4fab8e7673123bd7574deffa0c6ee4a9c261bc87..2742c653b2f203db5fc0ad0503c74b37d530250b 100644
--- a/src/include/FluidRenderer.h
+++ b/src/include/FluidRenderer.h
@@ -66,9 +66,13 @@ class FluidRenderer {
 private:
     // Shader programs
     ShaderProgram m_normalRenderProgram;     // For particle shaders
+    
     ShaderProgram m_depthRenderProgram;      // For depth shaders
     ShaderProgram m_depthFilterProgram;
+    ShaderProgram m_depthNormalsProgram;
+    
     ShaderProgram m_thicknessRenderProgram; // For thickness shaders
+    
     ShaderProgram m_visualizationProgram;    // For visualization shaders
 
     GLuint m_depthFilterTempTex;
@@ -135,9 +139,13 @@ public:
 
     // Separate rendering functions
     void renderNormalFrame(size_t particleCount, size_t boundaryCount); // Render using Normal Shaders
+    
     void renderDepthFrame(size_t particleCount, size_t boundaryCount);  // Render using Depth Shaders
     void renderDepthFilterFrame();
+    void renderDepthNormalsFrame();
+
     void renderThicknessFrame(size_t particleCount, size_t boundaryCount);
+
     void visualizeBuffer(RenderStage stage);                           // Visualize selected render stage
 
     // Methods to retrieve textures based on RenderStage