From be85e20024f3084be2f196e74412e77718d62c17 Mon Sep 17 00:00:00 2001 From: bloeys Date: Sun, 14 Apr 2024 02:50:40 +0400 Subject: [PATCH] Basic directional shadows --- main.go | 255 +++++++++++++++++++++++++------------ materials/material.go | 19 ++- res/shaders/depth-map.glsl | 21 +++ res/shaders/simple.glsl | 34 ++++- 4 files changed, 239 insertions(+), 90 deletions(-) create mode 100755 res/shaders/depth-map.glsl diff --git a/main.go b/main.go index 08fdd9e..aba6b96 100755 --- a/main.go +++ b/main.go @@ -50,6 +50,29 @@ type DirLight struct { SpecularColor gglm.Vec3 } +var ( + dSize float32 = 50 + dNear float32 = 1 + dFar float32 = 50 + dPos = gglm.NewVec3(0, 10, 0) +) + +func (d *DirLight) GetProjViewMat() gglm.Mat4 { + + // Some arbitrary position for the directional light + pos := dPos //gglm.NewVec3(0, 10, 0) + + size := dSize //float32(50) + nearClip := dNear //float32(1) + farClip := dFar //float32(50) + + projMat := gglm.Ortho(-size, size, -size, size, nearClip, farClip).Mat4 + // viewMat := gglm.LookAtRH(pos, gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0)).Mat4 + viewMat := gglm.LookAtRH(pos, pos.Clone().Add(d.Dir.Clone().Scale(10)), gglm.NewVec3(0, 1, 0)).Mat4 + + return *projMat.Mul(&viewMat) +} + // Check https://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation for values type PointLight struct { Pos gglm.Vec3 @@ -99,13 +122,16 @@ var ( yaw float32 = -1.5 cam *camera.Camera - renderToFbo = true - fboRenderDirectly = true - fboScale = gglm.NewVec2(0.25, 0.25) - fboOffset = gglm.NewVec2(0.75, -0.75) - demoFbo buffers.Framebuffer + renderToDemoFbo = true + renderToBackBuffer = true + demoFboScale = gglm.NewVec2(0.25, 0.25) + demoFboOffset = gglm.NewVec2(0.75, -0.75) + demoFbo buffers.Framebuffer - depthMapFbo buffers.Framebuffer + renderToDepthMapFbo = true + depthMapFboScale = gglm.NewVec2(0.25, 0.25) + depthMapFboOffset = gglm.NewVec2(0.75, -0.2) + depthMapFbo buffers.Framebuffer screenQuadVao buffers.VertexArray screenQuadMat *materials.Material @@ -115,6 +141,7 @@ var ( containerMat *materials.Material palleteMat *materials.Material skyboxMat *materials.Material + depthMapMat *materials.Material debugDepthMat *materials.Material cubeMesh *meshes.Mesh @@ -124,8 +151,8 @@ var ( cubeModelMat = gglm.NewTrMatId() - drawSkybox = true - debugDrawDepthBuffer bool + renderSkybox = true + renderDepthBuffer bool skyboxCmap assets.Cubemap @@ -136,9 +163,9 @@ var ( // Lights dirLight = DirLight{ - Dir: *gglm.NewVec3(0, -0.8, 0.2).Normalize(), - DiffuseColor: *gglm.NewVec3(0, 0, 0), - SpecularColor: *gglm.NewVec3(0, 0, 0), + Dir: *gglm.NewVec3(0.57735, -0.57735, 0.57735).Normalize(), + DiffuseColor: *gglm.NewVec3(1, 1, 1), + SpecularColor: *gglm.NewVec3(1, 1, 1), } pointLights = [...]PointLight{ { @@ -236,7 +263,8 @@ func (g *Game) handleWindowEvents(e sdl.Event) { g.WinHeight = e.Data2 cam.AspectRatio = float32(g.WinWidth) / float32(g.WinHeight) - updateProjViewMat() + cam.Update() + updateAllProjViewMats(cam.ProjMat, cam.ViewMat) } } } @@ -351,8 +379,8 @@ func (g *Game) Init() { // Create materials and assign any unused texture slots to black // screenQuadMat = materials.NewMaterial("Screen Quad Mat", "./res/shaders/screen-quad.glsl") - screenQuadMat.SetUnifVec2("scale", fboScale) - screenQuadMat.SetUnifVec2("offset", fboOffset) + screenQuadMat.SetUnifVec2("scale", demoFboScale) + screenQuadMat.SetUnifVec2("offset", demoFboOffset) screenQuadMat.SetUnifInt32("material.diffuse", int32(materials.TextureSlot_Diffuse)) unlitMat = materials.NewMaterial("Unlit mat", "./res/shaders/simple-unlit.glsl") @@ -373,6 +401,7 @@ func (g *Game) Init() { whiteMat.SetUnifVec3("dirLight.dir", &dirLight.Dir) whiteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor) whiteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor) + whiteMat.SetUnifInt32("dirLight.shadowMap", int32(materials.TextureSlot_ShadowMap)) containerMat = materials.NewMaterial("Container mat", "./res/shaders/simple.glsl") containerMat.Shininess = 64 @@ -389,6 +418,7 @@ func (g *Game) Init() { containerMat.SetUnifVec3("dirLight.dir", &dirLight.Dir) containerMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor) containerMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor) + containerMat.SetUnifInt32("dirLight.shadowMap", int32(materials.TextureSlot_ShadowMap)) palleteMat = materials.NewMaterial("Pallete mat", "./res/shaders/simple.glsl") palleteMat.Shininess = 64 @@ -404,9 +434,12 @@ func (g *Game) Init() { palleteMat.SetUnifFloat32("material.shininess", palleteMat.Shininess) palleteMat.SetUnifVec3("dirLight.diffuseColor", &dirLight.DiffuseColor) palleteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor) + palleteMat.SetUnifInt32("dirLight.shadowMap", int32(materials.TextureSlot_ShadowMap)) debugDepthMat = materials.NewMaterial("Debug depth mat", "./res/shaders/debug-depth.glsl") + depthMapMat = materials.NewMaterial("Depth Map mat", "./res/shaders/depth-map.glsl") + skyboxMat = materials.NewMaterial("Skybox mat", "./res/shaders/skybox.glsl") skyboxMat.CubemapTex = skyboxCmap.TexID skyboxMat.SetUnifInt32("skybox", int32(materials.TextureSlot_Cubemap)) @@ -425,9 +458,13 @@ func (g *Game) Init() { screenQuadVao = buffers.NewVertexArray() screenQuadVao.AddVertexBuffer(screenQuadVbo) + // Fbos and lights g.initFbos() g.updateLights() - updateProjViewMat() + + // Initial camera update + cam.Update() + updateAllProjViewMats(cam.ProjMat, cam.ViewMat) } func (g *Game) initFbos() { @@ -460,6 +497,12 @@ func (g *Game) initFbos() { func (g *Game) updateLights() { + // Directional light + whiteMat.ShadowMap = depthMapFbo.Attachments[0].Id + containerMat.ShadowMap = depthMapFbo.Attachments[0].Id + palleteMat.ShadowMap = depthMapFbo.Attachments[0].Id + + // Point lights for i := 0; i < len(pointLights); i++ { pl := &pointLights[i] @@ -490,6 +533,7 @@ func (g *Game) updateLights() { palleteMat.SetUnifFloat32(indexString+".quadratic", pl.Quadratic) } + // Spotlights for i := 0; i < len(spotLights); i++ { l := &spotLights[i] @@ -553,10 +597,12 @@ func (g *Game) showDebugWindow() { // Camera imgui.Text("Camera") if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) { - updateProjViewMat() + cam.Update() + updateAllProjViewMats(cam.ProjMat, cam.ViewMat) } if imgui.DragFloat3("Cam Forward", &cam.Forward.Data) { - updateProjViewMat() + cam.Update() + updateAllProjViewMats(cam.ProjMat, cam.ViewMat) } imgui.Spacing() @@ -572,17 +618,6 @@ func (g *Game) showDebugWindow() { imgui.Spacing() - // Specular - imgui.Text("Specular Settings") - - if imgui.DragFloat("Specular Shininess", &whiteMat.Shininess) { - whiteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess) - containerMat.SetUnifFloat32("material.shininess", whiteMat.Shininess) - palleteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess) - } - - imgui.Spacing() - // Directional light imgui.Text("Directional Light") @@ -604,6 +639,22 @@ func (g *Game) showDebugWindow() { palleteMat.SetUnifVec3("dirLight.specularColor", &dirLight.SpecularColor) } + imgui.DragFloat("dSize", &dSize) + imgui.DragFloat("dNear", &dNear) + imgui.DragFloat("dFar", &dFar) + imgui.DragFloat3("dPos", &dPos.Data) + + imgui.Spacing() + + // Specular + imgui.Text("Specular Settings") + + if imgui.DragFloat("Specular Shininess", &whiteMat.Shininess) { + whiteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess) + containerMat.SetUnifFloat32("material.shininess", whiteMat.Shininess) + palleteMat.SetUnifFloat32("material.shininess", whiteMat.Shininess) + } + imgui.Spacing() // Point lights @@ -700,25 +751,24 @@ func (g *Game) showDebugWindow() { imgui.EndListBox() } - // Fbo - imgui.Text("Framebuffer") + // Demo fbo + imgui.Text("Demo Framebuffer") + imgui.Checkbox("Render to demo FBO", &renderToDemoFbo) + imgui.DragFloat2("Scale##0", &demoFboScale.Data) + imgui.DragFloat2("Offset##0", &demoFboOffset.Data) - imgui.Checkbox("Render to FBO", &renderToFbo) - imgui.Checkbox("Render Directly", &fboRenderDirectly) - - if imgui.DragFloat2("Scale", &fboScale.Data) { - screenQuadMat.SetUnifVec2("scale", fboScale) - } - - if imgui.DragFloat2("Offset", &fboOffset.Data) { - screenQuadMat.SetUnifVec2("offset", fboOffset) - } + // Depth map fbo + imgui.Text("Depth Map Framebuffer") + imgui.Checkbox("Render to depth map FBO", &renderToDepthMapFbo) + imgui.DragFloat2("Scale##1", &depthMapFboScale.Data) + imgui.DragFloat2("Offset##1", &depthMapFboOffset.Data) // Other imgui.Text("Other Settings") - imgui.Checkbox("Draw Skybox", &drawSkybox) - imgui.Checkbox("Debug depth buffer", &debugDrawDepthBuffer) + imgui.Checkbox("Render skybox", &renderSkybox) + imgui.Checkbox("Render to back buffer", &renderToBackBuffer) + imgui.Checkbox("Render depth buffer", &renderDepthBuffer) imgui.End() } @@ -746,7 +796,7 @@ func (g *Game) updateCameraLookAround() { // Update cam forward cam.UpdateRotation(pitch, yaw) - updateProjViewMat() + updateAllProjViewMats(cam.ProjMat, cam.ViewMat) } func (g *Game) updateCameraPos() { @@ -777,45 +827,92 @@ func (g *Game) updateCameraPos() { } if update { - updateProjViewMat() + cam.Update() + updateAllProjViewMats(cam.ProjMat, cam.ViewMat) } } func (g *Game) Render() { - if !renderToFbo { - g.RenderScene() - return + dirLightProjViewMat := dirLight.GetProjViewMat() + + // Set some uniforms + whiteMat.SetUnifVec3("camPos", &cam.Pos) + whiteMat.SetUnifMat4("dirLightProjViewMat", &dirLightProjViewMat) + + containerMat.SetUnifVec3("camPos", &cam.Pos) + containerMat.SetUnifMat4("dirLightProjViewMat", &dirLightProjViewMat) + + palleteMat.SetUnifVec3("camPos", &cam.Pos) + palleteMat.SetUnifMat4("dirLightProjViewMat", &dirLightProjViewMat) + + depthMapMat.SetUnifMat4("projViewMat", &dirLightProjViewMat) + + // Render depth map for shadows + depthMapFbo.BindWithViewport() + depthMapFbo.Clear() + g.RenderScene(depthMapMat) + depthMapFbo.UnBindWithViewport(uint32(g.WinWidth), uint32(g.WinHeight)) + + if renderToDepthMapFbo { + screenQuadMat.DiffuseTex = depthMapFbo.Attachments[0].Id + screenQuadMat.SetUnifVec2("offset", depthMapFboOffset) + screenQuadMat.SetUnifVec2("scale", depthMapFboScale) + screenQuadMat.Bind() + window.Rend.DrawVertexArray(screenQuadMat, &screenQuadVao, 0, 6) } - demoFbo.Bind() - demoFbo.Clear() - g.RenderScene() - demoFbo.UnBind() + if renderToBackBuffer { - if fboRenderDirectly { - g.RenderScene() + if renderDepthBuffer { + g.RenderScene(debugDepthMat) + } else { + g.RenderScene(nil) + } } - screenQuadMat.DiffuseTex = demoFbo.Attachments[0].Id - window.Rend.DrawVertexArray(screenQuadMat, &screenQuadVao, 0, 6) + if renderSkybox { + g.DrawSkybox() + } + + if renderToDemoFbo { + + demoFbo.Bind() + demoFbo.Clear() + + if renderDepthBuffer { + g.RenderScene(debugDepthMat) + } else { + g.RenderScene(nil) + } + + if renderSkybox { + g.DrawSkybox() + } + + demoFbo.UnBind() + + screenQuadMat.DiffuseTex = demoFbo.Attachments[0].Id + screenQuadMat.SetUnifVec2("offset", demoFboOffset) + screenQuadMat.SetUnifVec2("scale", demoFboScale) + + window.Rend.DrawVertexArray(screenQuadMat, &screenQuadVao, 0, 6) + } } -func (g *Game) RenderScene() { +func (g *Game) RenderScene(overrideMat *materials.Material) { tempModelMatrix := cubeModelMat.Clone() - whiteMat.SetUnifVec3("camPos", &cam.Pos) - containerMat.SetUnifVec3("camPos", &cam.Pos) - palleteMat.SetUnifVec3("camPos", &cam.Pos) - + // See if we need overrides sunMat := palleteMat chairMat := palleteMat cubeMat := containerMat - if debugDrawDepthBuffer { - sunMat = debugDepthMat - chairMat = debugDepthMat - cubeMat = debugDepthMat + + if overrideMat != nil { + sunMat = overrideMat + chairMat = overrideMat + cubeMat = overrideMat } // Draw dir light @@ -843,10 +940,6 @@ func (g *Game) RenderScene() { } tempModelMatrix.Translate(gglm.NewVec3(float32(rowSize), -1, 0)) } - - if drawSkybox { - g.DrawSkybox() - } } func (g *Game) DrawSkybox() { @@ -867,12 +960,9 @@ func (g *Game) DeInit() { g.Win.Destroy() } -func updateProjViewMat() { +func updateAllProjViewMats(projMat, viewMat gglm.Mat4) { - cam.Update() - - projViewMat := cam.ProjMat.Clone() - projViewMat.Mul(&cam.ViewMat) + projViewMat := projMat.Clone().Mul(&viewMat) unlitMat.SetUnifMat4("projViewMat", projViewMat) whiteMat.SetUnifMat4("projViewMat", projViewMat) @@ -881,14 +971,13 @@ func updateProjViewMat() { debugDepthMat.SetUnifMat4("projViewMat", projViewMat) // Update skybox projViewMat - viewMat := cam.ViewMat.Clone() - viewMat.Set(0, 3, 0) - viewMat.Set(1, 3, 0) - viewMat.Set(2, 3, 0) - viewMat.Set(3, 0, 0) - viewMat.Set(3, 1, 0) - viewMat.Set(3, 2, 0) - viewMat.Set(3, 3, 0) - skyboxMat.SetUnifMat4("projViewMat", cam.ProjMat.Clone().Mul(viewMat)) - + skyboxViewMat := viewMat.Clone() + skyboxViewMat.Set(0, 3, 0) + skyboxViewMat.Set(1, 3, 0) + skyboxViewMat.Set(2, 3, 0) + skyboxViewMat.Set(3, 0, 0) + skyboxViewMat.Set(3, 1, 0) + skyboxViewMat.Set(3, 2, 0) + skyboxViewMat.Set(3, 3, 0) + skyboxMat.SetUnifMat4("projViewMat", projMat.Clone().Mul(skyboxViewMat)) } diff --git a/materials/material.go b/materials/material.go index 21311a6..9b12235 100755 --- a/materials/material.go +++ b/materials/material.go @@ -11,11 +11,12 @@ import ( type TextureSlot uint32 const ( - TextureSlot_Diffuse TextureSlot = 0 - TextureSlot_Specular TextureSlot = 1 - TextureSlot_Normal TextureSlot = 2 - TextureSlot_Emission TextureSlot = 3 - TextureSlot_Cubemap TextureSlot = 10 + TextureSlot_Diffuse TextureSlot = 0 + TextureSlot_Specular TextureSlot = 1 + TextureSlot_Normal TextureSlot = 2 + TextureSlot_Emission TextureSlot = 3 + TextureSlot_Cubemap TextureSlot = 10 + TextureSlot_ShadowMap TextureSlot = 11 ) type Material struct { @@ -36,6 +37,9 @@ type Material struct { // Cubemap CubemapTex uint32 + + // Shadowmaps + ShadowMap uint32 } func (m *Material) Bind() { @@ -66,6 +70,11 @@ func (m *Material) Bind() { gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_Cubemap)) gl.BindTexture(gl.TEXTURE_CUBE_MAP, m.CubemapTex) } + + if m.ShadowMap != 0 { + gl.ActiveTexture(uint32(gl.TEXTURE0 + TextureSlot_ShadowMap)) + gl.BindTexture(gl.TEXTURE_2D, m.ShadowMap) + } } func (m *Material) UnBind() { diff --git a/res/shaders/depth-map.glsl b/res/shaders/depth-map.glsl new file mode 100755 index 0000000..ed97deb --- /dev/null +++ b/res/shaders/depth-map.glsl @@ -0,0 +1,21 @@ +//shader:vertex +#version 410 + +layout(location=0) in vec3 vertPosIn; + +uniform mat4 modelMat; +uniform mat4 projViewMat; + +void main() +{ + gl_Position = projViewMat * modelMat * vec4(vertPosIn, 1); +} + +//shader:fragment +#version 410 + +void main() +{ + // This implicitly writes to the depth buffer with no color operations + // Equivalent: gl_FragDepth = gl_FragCoord.z; +} diff --git a/res/shaders/simple.glsl b/res/shaders/simple.glsl index 6bd1aa0..b98ad79 100755 --- a/res/shaders/simple.glsl +++ b/res/shaders/simple.glsl @@ -10,10 +10,11 @@ out vec3 vertNormal; out vec2 vertUV0; out vec3 vertColor; out vec3 fragPos; +out vec4 fragPosDirLight; -//MVP = Model View Projection uniform mat4 modelMat; uniform mat4 projViewMat; +uniform mat4 dirLightProjViewMat; void main() { @@ -22,12 +23,15 @@ void main() // This produces the normal matrix that multiplies with the model normal to produce the // world space normal. Based on 'One last thing' section from: https://learnopengl.com/Lighting/Basic-Lighting vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn; + vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn; vertUV0 = vertUV0In; vertColor = vertColorIn; vec4 modelVert = modelMat * vec4(vertPosIn, 1); fragPos = modelVert.xyz; + fragPosDirLight = dirLightProjViewMat * vec4(fragPos, 1); + gl_Position = projViewMat * modelVert; } @@ -48,6 +52,7 @@ struct DirLight { vec3 dir; vec3 diffuseColor; vec3 specularColor; + sampler2D shadowMap; }; uniform DirLight dirLight; @@ -83,6 +88,7 @@ in vec3 vertColor; in vec3 vertNormal; in vec2 vertUV0; in vec3 fragPos; +in vec4 fragPosDirLight; out vec4 fragColor; @@ -93,6 +99,27 @@ vec4 emissionTexColor; vec3 normalizedVertNorm; vec3 viewDir; +float CalcShadow(sampler2D shadowMap) +{ + // Move from clip space to NDC + vec3 projCoords = fragPosDirLight.xyz / fragPosDirLight.w; + + // Move from [-1,1] to [0, 1] + projCoords = projCoords * 0.5 + 0.5; + + // currentDepth is the fragment depth from the light's perspective + float currentDepth = projCoords.z; + + // Closest depth is the closest depth value from the light's perspective + float closestDepth = texture(shadowMap, projCoords.xy).r; + + // If our depth is larger than the lights closest depth, + // then there is something closer to the light than us, and so we are in shadow + float shadow = currentDepth > closestDepth ? 1.0 : 0.0; + + return shadow; +} + vec3 CalcDirLight() { vec3 lightDir = normalize(-dirLight.dir); @@ -106,7 +133,10 @@ vec3 CalcDirLight() float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess); vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb; - return finalDiffuse + finalSpecular; + // Shadow + float shadow = CalcShadow(dirLight.shadowMap); + + return (finalDiffuse + finalSpecular) * (1.0 - shadow); } vec3 CalcPointLight(PointLight pointLight)