From 9e6fdacb488265ccac65dedea10ae26a02d07f0e Mon Sep 17 00:00:00 2001 From: bloeys Date: Tue, 14 May 2024 09:07:54 +0400 Subject: [PATCH] Much nicer point lights! --- main.go | 168 ++++++++++++++++++++++++++-------------- res/shaders/simple.glsl | 27 +++++-- 2 files changed, 130 insertions(+), 65 deletions(-) diff --git a/main.go b/main.go index e42ad73..794e61d 100755 --- a/main.go +++ b/main.go @@ -38,7 +38,7 @@ import ( - Create VAO struct independent from VBO to support multi-VBO use cases (e.g. instancing) ✅ - Normals maps ✅ - HDR ✅ - - Fix bad point light acne + - Fix bad point light acne ✅ - UBO support - Cascaded shadow mapping - Skeletal animations @@ -60,10 +60,16 @@ type DirLight struct { } var ( + renderDirLightShadows = true + renderPointLightShadows = true + renderSpotLightShadows = true + dirLightSize float32 = 30 dirLightNear float32 = 0.1 dirLightFar float32 = 30 dirLightPos = gglm.NewVec3(0, 10, 0) + + pointLightRadiusToFarPlaneRatio float32 = 1.25 ) func (d *DirLight) GetProjViewMat() gglm.Mat4 { @@ -91,6 +97,25 @@ type PointLight struct { Radius float32 Falloff float32 + // NearPlane is the distance where if the pixel + // is closer to the light than this distance, no shadow will be casted. + // + // This helps not produce shadows from within objects. + // Same idea a camera near plane. + NearPlane float32 + + // MaxBias is the max shadow bias applied for this light. + // A usual value is 0.05 + MaxBias float32 + + // Far plane is the max distance at which shadows from this + // light will show. + // + // This should be a bit bigger than the radius, as an object + // at the edge of the radius should still cast a shadow, and + // so this shadow will be further than the radius. + // + // Something like 'FarPlane=Radius*1.25' might work. FarPlane float32 } @@ -260,39 +285,35 @@ var ( dpiScaling float32 // Light settings - ambientColor = gglm.NewVec3(0, 0, 0) + ambientColor = gglm.NewVec3(20.0/255, 20.0/255, 20.0/255) dirLightDir = gglm.NewVec3(0, -0.5, -0.8) // Lights dirLight = DirLight{ Dir: *dirLightDir.Normalize(), - DiffuseColor: gglm.NewVec3(1, 1, 1), + DiffuseColor: gglm.NewVec3(63.0/255, 63.0/255, 63.0/255), SpecularColor: gglm.NewVec3(1, 1, 1), } pointLights = [...]PointLight{ { - Pos: gglm.NewVec3(0, 2, -2), + Pos: gglm.NewVec3(0, 4, -3), DiffuseColor: gglm.NewVec3(1, 0, 0), SpecularColor: gglm.NewVec3(1, 1, 1), Falloff: 1.0, - Radius: 20, - FarPlane: 25, - }, - { - Pos: gglm.NewVec3(0, -5, 0), - DiffuseColor: gglm.NewVec3(0, 1, 0), - SpecularColor: gglm.NewVec3(1, 1, 1), - Falloff: 1.0, - Radius: 20, - FarPlane: 25, + Radius: 10, + MaxBias: 0.05, + NearPlane: 0.2, + FarPlane: 20 * pointLightRadiusToFarPlaneRatio, }, { Pos: gglm.NewVec3(5, 0, 0), DiffuseColor: gglm.NewVec3(1, 1, 1), SpecularColor: gglm.NewVec3(1, 1, 1), Falloff: 1.0, - Radius: 20, - FarPlane: 25, + Radius: 10, + MaxBias: 0.05, + NearPlane: 0.2, + FarPlane: 20 * pointLightRadiusToFarPlaneRatio, }, { Pos: gglm.NewVec3(-3, 4, 3), @@ -300,7 +321,9 @@ var ( SpecularColor: gglm.NewVec3(1, 1, 1), Falloff: 1.0, Radius: 10, - FarPlane: 25, + MaxBias: 0.05, + NearPlane: 0.2, + FarPlane: 20 * pointLightRadiusToFarPlaneRatio, }, } @@ -452,7 +475,7 @@ func (g *Game) Init() { // Camera winWidth, winHeight := g.Win.SDLWin.GetSize() - camPos := gglm.NewVec3(0, 0, 10) + camPos := gglm.NewVec3(0, 10, 20) camForward := gglm.NewVec3(0, 0, -1) camWorldUp := gglm.NewVec3(0, 1, 0) cam = camera.NewPerspective( @@ -667,7 +690,7 @@ func (g *Game) initFbos() { assert.T(demoFbo.IsComplete(), "Demo fbo is not complete after init") // Depth map fbo - dirLightDepthMapFbo = buffers.NewFramebuffer(2048, 2048) + dirLightDepthMapFbo = buffers.NewFramebuffer(4096, 4096) dirLightDepthMapFbo.SetNoColorBuffer() dirLightDepthMapFbo.NewDepthAttachment( buffers.FramebufferAttachmentType_Texture, @@ -677,7 +700,7 @@ func (g *Game) initFbos() { assert.T(dirLightDepthMapFbo.IsComplete(), "Depth map fbo is not complete after init") // Point light depth map fbo - pointLightDepthMapFbo = buffers.NewFramebuffer(512, 512) + pointLightDepthMapFbo = buffers.NewFramebuffer(1024, 1024) pointLightDepthMapFbo.SetNoColorBuffer() pointLightDepthMapFbo.NewDepthCubemapArrayAttachment( buffers.FramebufferAttachmentDataFormat_DepthF32, @@ -687,7 +710,7 @@ func (g *Game) initFbos() { assert.T(pointLightDepthMapFbo.IsComplete(), "Point light depth map fbo is not complete after init") // Spot light depth map fbo - spotLightDepthMapFbo = buffers.NewFramebuffer(512, 512) + spotLightDepthMapFbo = buffers.NewFramebuffer(1024, 1024) spotLightDepthMapFbo.SetNoColorBuffer() spotLightDepthMapFbo.NewDepthTextureArrayAttachment( buffers.FramebufferAttachmentDataFormat_DepthF32, @@ -725,35 +748,53 @@ func (g *Game) updateLights() { p := &pointLights[i] indexString := "pointLights[" + strconv.Itoa(i) + "]" - whiteMat.SetUnifVec3(indexString+".pos", &p.Pos) - containerMat.SetUnifVec3(indexString+".pos", &p.Pos) - groundMat.SetUnifVec3(indexString+".pos", &p.Pos) - palleteMat.SetUnifVec3(indexString+".pos", &p.Pos) + posStr := indexString + ".pos" + whiteMat.SetUnifVec3(posStr, &p.Pos) + containerMat.SetUnifVec3(posStr, &p.Pos) + groundMat.SetUnifVec3(posStr, &p.Pos) + palleteMat.SetUnifVec3(posStr, &p.Pos) - whiteMat.SetUnifVec3(indexString+".diffuseColor", &p.DiffuseColor) - containerMat.SetUnifVec3(indexString+".diffuseColor", &p.DiffuseColor) - groundMat.SetUnifVec3(indexString+".diffuseColor", &p.DiffuseColor) - palleteMat.SetUnifVec3(indexString+".diffuseColor", &p.DiffuseColor) + diffuseStr := indexString + ".diffuseColor" + whiteMat.SetUnifVec3(diffuseStr, &p.DiffuseColor) + containerMat.SetUnifVec3(diffuseStr, &p.DiffuseColor) + groundMat.SetUnifVec3(diffuseStr, &p.DiffuseColor) + palleteMat.SetUnifVec3(diffuseStr, &p.DiffuseColor) - whiteMat.SetUnifVec3(indexString+".specularColor", &p.SpecularColor) - containerMat.SetUnifVec3(indexString+".specularColor", &p.SpecularColor) - groundMat.SetUnifVec3(indexString+".specularColor", &p.SpecularColor) - palleteMat.SetUnifVec3(indexString+".specularColor", &p.SpecularColor) + specularStr := indexString + ".specularColor" + whiteMat.SetUnifVec3(specularStr, &p.SpecularColor) + containerMat.SetUnifVec3(specularStr, &p.SpecularColor) + groundMat.SetUnifVec3(specularStr, &p.SpecularColor) + palleteMat.SetUnifVec3(specularStr, &p.SpecularColor) - whiteMat.SetUnifFloat32(indexString+".falloff", p.Falloff) - containerMat.SetUnifFloat32(indexString+".falloff", p.Falloff) - groundMat.SetUnifFloat32(indexString+".falloff", p.Falloff) - palleteMat.SetUnifFloat32(indexString+".falloff", p.Falloff) + falloffStr := indexString + ".falloff" + whiteMat.SetUnifFloat32(falloffStr, p.Falloff) + containerMat.SetUnifFloat32(falloffStr, p.Falloff) + groundMat.SetUnifFloat32(falloffStr, p.Falloff) + palleteMat.SetUnifFloat32(falloffStr, p.Falloff) - whiteMat.SetUnifFloat32(indexString+".radius", p.Radius) - containerMat.SetUnifFloat32(indexString+".radius", p.Radius) - groundMat.SetUnifFloat32(indexString+".radius", p.Radius) - palleteMat.SetUnifFloat32(indexString+".radius", p.Radius) + radiusStr := indexString + ".radius" + whiteMat.SetUnifFloat32(radiusStr, p.Radius) + containerMat.SetUnifFloat32(radiusStr, p.Radius) + groundMat.SetUnifFloat32(radiusStr, p.Radius) + palleteMat.SetUnifFloat32(radiusStr, p.Radius) - whiteMat.SetUnifFloat32(indexString+".farPlane", p.FarPlane) - containerMat.SetUnifFloat32(indexString+".farPlane", p.FarPlane) - groundMat.SetUnifFloat32(indexString+".farPlane", p.FarPlane) - palleteMat.SetUnifFloat32(indexString+".farPlane", p.FarPlane) + maxBiasStr := indexString + ".maxBias" + whiteMat.SetUnifFloat32(maxBiasStr, p.MaxBias) + containerMat.SetUnifFloat32(maxBiasStr, p.MaxBias) + groundMat.SetUnifFloat32(maxBiasStr, p.MaxBias) + palleteMat.SetUnifFloat32(maxBiasStr, p.MaxBias) + + nearPlaneStr := indexString + ".nearPlane" + whiteMat.SetUnifFloat32(nearPlaneStr, p.NearPlane) + containerMat.SetUnifFloat32(nearPlaneStr, p.NearPlane) + groundMat.SetUnifFloat32(nearPlaneStr, p.NearPlane) + palleteMat.SetUnifFloat32(nearPlaneStr, p.NearPlane) + + farPlaneStr := indexString + ".farPlane" + whiteMat.SetUnifFloat32(farPlaneStr, p.FarPlane) + containerMat.SetUnifFloat32(farPlaneStr, p.FarPlane) + groundMat.SetUnifFloat32(farPlaneStr, p.FarPlane) + palleteMat.SetUnifFloat32(farPlaneStr, p.FarPlane) } whiteMat.CubemapArrayTex = pointLightDepthMapFbo.Attachments[0].Id @@ -840,7 +881,7 @@ func (g *Game) showDebugWindow() { } } - imgui.PlotLinesFloatPtrV("Frame Times", frameTimesMs, int32(len(frameTimesMs)), 0, "", 0, 16, imgui.Vec2{Y: 200}, 4) + imgui.PlotLinesFloatPtrV("Frame Times", frameTimesMs, int32(len(frameTimesMs)), 0, "", 0, 16, imgui.Vec2{Y: 50}, 4) imgui.Spacing() @@ -859,7 +900,7 @@ func (g *Game) showDebugWindow() { imgui.Text("HDR") imgui.Checkbox("Enable HDR", &hdrRendering) - if imgui.DragFloat("Exposure", &hdrExposure) { + if imgui.DragFloatV("Exposure", &hdrExposure, 0.1, -10, 100, "%.3f", imgui.SliderFlagsNone) { tonemappedScreenQuadMat.SetUnifFloat32("exposure", hdrExposure) } @@ -967,7 +1008,7 @@ func (g *Game) showDebugWindow() { palleteMat.SetUnifVec3(specularStr, &pl.SpecularColor) } - if imgui.DragFloatV("Falloff", &pl.Falloff, 0.1, 0.001, 100, "%.3f", imgui.SliderFlagsNone) { + if imgui.DragFloatV("Falloff", &pl.Falloff, 0.1, 0, 100, "%.3f", imgui.SliderFlagsNone) { falloffStr := indexString + ".falloff" @@ -979,12 +1020,29 @@ func (g *Game) showDebugWindow() { if imgui.DragFloatV("Radius", &pl.Radius, 0.2, 0, 500, "%.3f", imgui.SliderFlagsNone) { - falloffStr := indexString + ".radius" + radiusStr := indexString + ".radius" - whiteMat.SetUnifFloat32(falloffStr, pl.Radius) - containerMat.SetUnifFloat32(falloffStr, pl.Radius) - groundMat.SetUnifFloat32(falloffStr, pl.Radius) - palleteMat.SetUnifFloat32(falloffStr, pl.Radius) + whiteMat.SetUnifFloat32(radiusStr, pl.Radius) + containerMat.SetUnifFloat32(radiusStr, pl.Radius) + groundMat.SetUnifFloat32(radiusStr, pl.Radius) + palleteMat.SetUnifFloat32(radiusStr, pl.Radius) + + farPlaneStr := indexString + ".farPlane" + pl.FarPlane = pl.Radius * pointLightRadiusToFarPlaneRatio + + whiteMat.SetUnifFloat32(farPlaneStr, pl.FarPlane) + containerMat.SetUnifFloat32(farPlaneStr, pl.FarPlane) + groundMat.SetUnifFloat32(farPlaneStr, pl.FarPlane) + palleteMat.SetUnifFloat32(farPlaneStr, pl.FarPlane) + } + + if imgui.DragFloatV("Max Bias", &pl.MaxBias, 0.01, 0, 10, "%.3f", imgui.SliderFlagsNone) { + + maxBiasStr := indexString + ".maxBias" + whiteMat.SetUnifFloat32(maxBiasStr, pl.MaxBias) + containerMat.SetUnifFloat32(maxBiasStr, pl.MaxBias) + groundMat.SetUnifFloat32(maxBiasStr, pl.MaxBias) + palleteMat.SetUnifFloat32(maxBiasStr, pl.MaxBias) } imgui.TreePop() @@ -1158,10 +1216,6 @@ func (g *Game) updateCameraPos() { } var ( - renderDirLightShadows = true - renderPointLightShadows = true - renderSpotLightShadows = true - rotatingCubeSpeedDeg1 float32 = 45 rotatingCubeSpeedDeg2 float32 = 120 rotatingCubeSpeedDeg3 float32 = 120 diff --git a/res/shaders/simple.glsl b/res/shaders/simple.glsl index a0b44a7..f066ec4 100755 --- a/res/shaders/simple.glsl +++ b/res/shaders/simple.glsl @@ -37,6 +37,8 @@ struct PointLight { vec3 specularColor; float falloff; float radius; + float maxBias; + float nearPlane; float farPlane; }; uniform PointLight pointLights[NUM_POINT_LIGHTS]; @@ -163,6 +165,8 @@ struct PointLight { vec3 specularColor; float falloff; float radius; + float maxBias; + float nearPlane; float farPlane; }; uniform PointLight pointLights[NUM_POINT_LIGHTS]; @@ -251,21 +255,25 @@ vec3 CalcDirLight() return (finalDiffuse + finalSpecular) * (1 - shadow); } -float CalcPointShadow(int lightIndex, vec3 worldLightPos, vec3 tangentLightDir, float farPlane) { +float CalcPointShadow(int lightIndex, vec3 worldLightPos, vec3 tangentLightDir, float maxBias, float nearPlane, float farPlane) { vec3 lightToFrag = fragPos - worldLightPos; + // Get depth of current fragment + float currentDepth = length(lightToFrag); + + if (currentDepth < nearPlane) { + return 0; + } + float closestDepth = texture(pointLightCubeShadowMaps, vec4(lightToFrag, lightIndex)).r; // We stored depth in the cubemap in the range [0, 1], so now we move back to [0, farPlane] closestDepth *= farPlane; - // Get depth of current fragment - float currentDepth = length(lightToFrag); + float bias = max(maxBias * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005); - float bias = max(0.05 * (1 - dot(normalizedVertNorm, tangentLightDir)), 0.005); - - float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; + float shadow = currentDepth - bias > closestDepth ? 1 : 0; return shadow; } @@ -344,7 +352,7 @@ vec3 CalcPointLight(PointLight pointLight, int lightIndex) float attenuation = AttenuateNoCusp(distToLight, pointLight.radius, pointLight.falloff); // Shadow - float shadow = CalcPointShadow(lightIndex, pointLight.pos, tangentLightDir, pointLight.farPlane); + float shadow = CalcPointShadow(lightIndex, pointLight.pos, tangentLightDir, pointLight.maxBias, pointLight.nearPlane, pointLight.farPlane); return (finalDiffuse + finalSpecular) * attenuation * (1 - shadow); } @@ -391,7 +399,10 @@ float CalcSpotShadow(vec3 tangentLightDir, int lightIndex) vec3 CalcSpotLight(SpotLight light, int lightIndex) { - if (light.innerCutoff == 0) + // The inner/outer cutoffs are cosine values, + // which means a value of 1 is mainly produced when the input + // is 0 degrees or radians. cos(180) will also be 1, but that's too much :) + if (light.innerCutoff == 1) return vec3(0); vec3 tangentLightDir = tangentSpotLightDirections[lightIndex];