From a0086020c197639cfeef2fda5220afb9f260f2cd Mon Sep 17 00:00:00 2001 From: Atdunbg Date: Sun, 1 Mar 2026 17:03:40 +0800 Subject: [PATCH] add infiniteGrid, rewrote the shadow , now the shadow is a simple hardware shadow --- Editor/assets/editor/StopButton.png | Bin 0 -> 115 bytes Editor/assets/scenes/FPSDemo.scene | 215 +----- Editor/assets/scenes/demo.scene | 63 ++ Editor/assets/shaders/Grid.glsl | 45 -- Editor/assets/shaders/InfiniteGrid.glsl | 226 +++++++ Editor/assets/shaders/PBRShader_Anim.glsl | 692 ++++++-------------- Editor/assets/shaders/PBRShader_Static.glsl | 480 +++----------- Prism/src/Prism/Core/ImGui/ImGui.h | 21 + Prism/src/Prism/Renderer/FrameBuffer.h | 2 +- Prism/src/Prism/Renderer/Material.h | 2 +- Prism/src/Prism/Renderer/SceneRenderer.cpp | 494 ++++++++------ Prism/src/Prism/Renderer/SceneRenderer.h | 2 +- 12 files changed, 923 insertions(+), 1319 deletions(-) create mode 100644 Editor/assets/editor/StopButton.png create mode 100644 Editor/assets/scenes/demo.scene delete mode 100644 Editor/assets/shaders/Grid.glsl create mode 100644 Editor/assets/shaders/InfiniteGrid.glsl diff --git a/Editor/assets/editor/StopButton.png b/Editor/assets/editor/StopButton.png new file mode 100644 index 0000000000000000000000000000000000000000..ce99b3caeda820dfa808bfd2334f50ba25ee5d57 GIT binary patch literal 115 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzD^C~4kcif|7d8qWFyLV}H2VEs zr+lAJg6R*|j^r(||9=}B>)YhMf4BWFP%#`dlzA61yxqiNl5!{QJxJ8k)z4*}Q$iB} D{k9^Y literal 0 HcmV?d00001 diff --git a/Editor/assets/scenes/FPSDemo.scene b/Editor/assets/scenes/FPSDemo.scene index e6c3266..35231c6 100644 --- a/Editor/assets/scenes/FPSDemo.scene +++ b/Editor/assets/scenes/FPSDemo.scene @@ -1,202 +1,49 @@ Scene: Scene Name Environment: - AssetPath: assets\env\birchwood_4k.hdr + AssetHandle: 10549690553241162923 Light: - Direction: [-0.787, -0.733, 1] - Radiance: [1, 1, 1] - Multiplier: 0.515 + Direction: [-0.314, -0.941, -0.209] + Radiance: [0, 0, 0] + Multiplier: 1 Entities: - - Entity: 3247025703490125974 + - Entity: 17803125207910630398 + Parent: 0 + Children: + [] TagComponent: - Tag: Player + Tag: Directional Light TransformComponent: - Position: [2.8080375, 1.5, 0] - Rotation: [0, 0, 0] - Scale: [2, 2, 2] - ScriptComponent: - ModuleName: FPSExample.FPSPlayer - StoredFields: - - Name: WalkingSpeed - Type: 1 - Data: 4 - - Name: RunSpeed - Type: 1 - Data: 10 - - Name: JumpForce - Type: 1 - Data: 1 - - Name: MouseSensitivity - Type: 1 - Data: 10 - - Name: Distance - Type: 1 - Data: 0 - MeshComponent: - AssetPath: assets/meshes/Capsule.fbx - RigidBodyComponent: - BodyType: 1 - Mass: 1 - IsKinematic: false - Layer: 1 - Constraints: - LockPositionX: false - LockPositionY: false - LockPositionZ: false - LockRotationX: true - LockRotationY: true - LockRotationZ: true - PhysicsMaterialComponent: - StaticFriction: 0.1 - DynamicFriction: 0.1 - Bounciness: 0.1 - MeshColliderComponent: - AssetPath: assets/meshes/Capsule.fbx - IsConvex: true - IsTrigger: false - - Entity: 11149966982516343187 + Position: [0, 0, 0] + Rotation: [-0.4810984, -0.20606127, 2.9545484] + Scale: [1.0000023, 1.0000007, 0.9999998] + DirectionalLightComponent: + Radiance: [1, 1, 1] + CastShadows: true + SoftShadows: true + LightSize: 0.5 + - Entity: 4315886439647742331 + Parent: 0 + Children: + [] TagComponent: - Tag: Mesh Collider + Tag: Cube TransformComponent: - Position: [-2.6045518, 1, -0.0017139912] + Position: [0, 2.048974, 0] Rotation: [0, 0, 0] Scale: [1, 1, 1] MeshComponent: - AssetPath: assets/meshes/Sphere1m.fbx - RigidBodyComponent: - BodyType: 1 - Mass: 0.1 - IsKinematic: false - Layer: 1 - Constraints: - LockPositionX: false - LockPositionY: false - LockPositionZ: false - LockRotationX: false - LockRotationY: false - LockRotationZ: false - PhysicsMaterialComponent: - StaticFriction: 1 - DynamicFriction: 1 - Bounciness: 0.1 - MeshColliderComponent: - AssetPath: assets/meshes/Sphere1m.fbx - IsConvex: true - IsTrigger: false - - Entity: 10169503531257462571 + AssetID: 3580169978473467053 + - Entity: 16992665426857995732 + Parent: 0 + Children: + [] TagComponent: - Tag: Box - TransformComponent: - Position: [0, 1.5, 0] - Rotation: [0, 0, 0] - Scale: [2, 2, 2] - MeshComponent: - AssetPath: assets/meshes/Cube1m.fbx - RigidBodyComponent: - BodyType: 1 - Mass: 0.5 - IsKinematic: false - Layer: 1 - Constraints: - LockPositionX: false - LockPositionY: false - LockPositionZ: false - LockRotationX: false - LockRotationY: false - LockRotationZ: false - PhysicsMaterialComponent: - StaticFriction: 1 - DynamicFriction: 1 - Bounciness: 0 - BoxColliderComponent: - Offset: [0, 0, 0] - Size: [1, 1, 1] - IsTrigger: false - - Entity: 14057422478420564497 - TagComponent: - Tag: Sphere - TransformComponent: - Position: [-3.9876995, 1, -1.9669533e-06] - Rotation: [0, 0, 0] - Scale: [1, 1, 1] - MeshComponent: - AssetPath: assets/meshes/Sphere1m.fbx - RigidBodyComponent: - BodyType: 1 - Mass: 1 - IsKinematic: false - Layer: 1 - Constraints: - LockPositionX: false - LockPositionY: false - LockPositionZ: false - LockRotationX: false - LockRotationY: false - LockRotationZ: false - PhysicsMaterialComponent: - StaticFriction: 0.1 - DynamicFriction: 0.1 - Bounciness: 0.1 - SphereColliderComponent: - Radius: 0.5 - IsTrigger: false - - Entity: 5178862374589434728 - TagComponent: - Tag: Camera - TransformComponent: - Position: [2.808, 2.25, 0] - Rotation: [0, 0, 0] - Scale: [1, 1, 1] - CameraComponent: - Camera: - ProjectionType: 0 - PerspectiveFOV: 65 - PerspectiveNear: 0.100000001 - PerspectiveFar: 1000 - OrthographicSize: 10 - OrthographicNear: -1 - OrthographicFar: 1 - Primary: true - - Entity: 18306113171518048249 - TagComponent: - Tag: Box + Tag: Cube TransformComponent: Position: [0, 0, 0] Rotation: [0, 0, 0] Scale: [50, 1, 50] MeshComponent: - AssetPath: assets/meshes/Cube1m.fbx - RigidBodyComponent: - BodyType: 0 - Mass: 1 - IsKinematic: false - Layer: 1 - Constraints: - LockPositionX: false - LockPositionY: false - LockPositionZ: false - LockRotationX: false - LockRotationY: false - LockRotationZ: false - PhysicsMaterialComponent: - StaticFriction: 1 - DynamicFriction: 1 - Bounciness: 0 - BoxColliderComponent: - Offset: [0, 0, 0] - Size: [1, 1, 1] - IsTrigger: false - - Entity: 2025484417758554619 - TagComponent: - Tag: Sky Light - TransformComponent: - Position: [0, 0, 0] - Rotation: [0, 0, 0] - Scale: [1, 1, 1] - SkyLightComponent: - EnvironmentAssetPath: assets/env/birchwood_4k.hdr - Intensity: 1 - Angle: 0 + AssetID: 3580169978473467053 PhysicsLayers: - - Name: Box - CollidesWith: - - Name: Default \ No newline at end of file + [] \ No newline at end of file diff --git a/Editor/assets/scenes/demo.scene b/Editor/assets/scenes/demo.scene new file mode 100644 index 0000000..0fec9e8 --- /dev/null +++ b/Editor/assets/scenes/demo.scene @@ -0,0 +1,63 @@ +Scene: Scene Name +Environment: + AssetHandle: 1249421934001634765 + Light: + Direction: [-0.314, -0.941, -0.209] + Radiance: [0, 0, 0] + Multiplier: 0 +Entities: + - Entity: 18090260616187152572 + Parent: 0 + Children: + [] + TagComponent: + Tag: Directional Light + TransformComponent: + Position: [0, 0, 0] + Rotation: [0, 0, -2.7340124] + Scale: [0.9999992, 0.9999992, 1] + DirectionalLightComponent: + Radiance: [1, 1, 1] + CastShadows: true + SoftShadows: true + LightSize: 0.5 + - Entity: 18154648535203342017 + Parent: 0 + Children: + [] + TagComponent: + Tag: Cube + TransformComponent: + Position: [0, 6.282704, 0] + Rotation: [0, 0, 0] + Scale: [1, 1, 1] + MeshComponent: + AssetID: 133168951455480052 + - Entity: 386484027514287028 + Parent: 0 + Children: + [] + TagComponent: + Tag: Cube + TransformComponent: + Position: [0, 0, 0] + Rotation: [0, 0, 0] + Scale: [50, 1, 50] + MeshComponent: + AssetID: 133168951455480052 + - Entity: 6827989772960183355 + Parent: 0 + Children: + [] + TagComponent: + Tag: Sky Light + TransformComponent: + Position: [0, 0, 0] + Rotation: [0, 0, 0] + Scale: [1, 1, 1] + SkyLightComponent: + EnvironmentMap: 1249421934001634765 + Intensity: 1 + Angle: 0 +PhysicsLayers: + [] \ No newline at end of file diff --git a/Editor/assets/shaders/Grid.glsl b/Editor/assets/shaders/Grid.glsl deleted file mode 100644 index adf6b1d..0000000 --- a/Editor/assets/shaders/Grid.glsl +++ /dev/null @@ -1,45 +0,0 @@ -// Grid Shader - -#type vertex -#version 430 - -layout(location = 0) in vec3 a_Position; -layout(location = 1) in vec2 a_TexCoord; - -uniform mat4 u_ViewProjection; -uniform mat4 u_Transform; - -out vec2 v_TexCoord; - -void main() -{ - vec4 position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0); - gl_Position = position; - - v_TexCoord = a_TexCoord; -} - -#type fragment -#version 430 - -layout(location = 0) out vec4 color; - -uniform float u_Scale; -uniform float u_Res; - -in vec2 v_TexCoord; - -float grid(vec2 st, float res) -{ - vec2 grid = fract(st); - return step(res, grid.x) * step(res, grid.y); -} - -void main() -{ - float scale = u_Scale; - float resolution = u_Res; - - float x = grid(v_TexCoord * scale, resolution); - color = vec4(vec3(0.2), 0.5) * (1.0 - x); -} diff --git a/Editor/assets/shaders/InfiniteGrid.glsl b/Editor/assets/shaders/InfiniteGrid.glsl new file mode 100644 index 0000000..b18de1a --- /dev/null +++ b/Editor/assets/shaders/InfiniteGrid.glsl @@ -0,0 +1,226 @@ +// Infinite Grid Shader +// Based on "The Best Darn Grid Shader (Yet)" by Ben Golus + +#type vertex +#version 450 core + +layout(location = 0) in vec3 a_Position; + +// camera +uniform mat4 u_View; +uniform mat4 u_Projection; +uniform vec3 u_CameraPosition; + +out CameraData{ + mat4 View; + mat4 Projection; + vec3 CameraPosition; +}CameraOutput; + +out vec3 v_NearPoint; +out vec3 v_FarPoint; + +vec3 unprojectPoint(float x, float y, float z) { + mat4 viewInv = inverse(u_View); + mat4 projInv = inverse(u_Projection); + vec4 unprojectedPoint = viewInv * projInv * vec4(x, y, z, 1.0); + return unprojectedPoint.xyz / unprojectedPoint.w; +} + +void main() { + v_NearPoint = unprojectPoint(a_Position.x, a_Position.y, 0.0); + v_FarPoint = unprojectPoint(a_Position.x, a_Position.y, 1.0); + + CameraOutput.View = u_View; + CameraOutput.Projection = u_Projection; + CameraOutput.CameraPosition = u_CameraPosition; + + gl_Position = vec4(a_Position, 1.0); +} + +#type fragment +#version 450 core + +layout(location = 0) out vec4 o_Color; + +in vec3 v_NearPoint; +in vec3 v_FarPoint; + +in CameraData{ + mat4 View; + mat4 Projection; + vec3 CameraPosition; +}CameraInput; + +// Grid plane: 0 = XZ (Y up), 1 = XY (Z forward), 2 = YZ (X right) +uniform int u_GridPlane = 0; +uniform float u_GridScale = 1.0; +uniform vec4 u_GridColorThin = vec4(0.5, 0.5, 0.5, 0.4); +uniform vec4 u_GridColorThick = vec4(0.5, 0.5, 0.5, 0.6); +uniform vec4 u_AxisColorX = vec4(0.9, 0.2, 0.2, 1.0); +uniform vec4 u_AxisColorZ = vec4(0.2, 0.2, 0.9, 1.0); +uniform float u_FadeDistance = 500.0; + +float computeDepth(vec3 pos) { + vec4 clipSpacePos = CameraInput.Projection * CameraInput.View * vec4(pos, 1.0); + return clipSpacePos.z / clipSpacePos.w; +} + +// Get the plane normal based on grid plane type +vec3 getPlaneNormal() { + if (u_GridPlane == 1) return vec3(0.0, 0.0, 1.0); // XY plane, Z normal + if (u_GridPlane == 2) return vec3(1.0, 0.0, 0.0); // YZ plane, X normal + return vec3(0.0, 1.0, 0.0); // XZ plane, Y normal (default) +} + +// Get 2D coordinates on the plane +vec2 getPlaneCoords(vec3 pos) { + if (u_GridPlane == 1) return pos.xy; // XY plane + if (u_GridPlane == 2) return pos.yz; // YZ plane + return pos.xz; // XZ plane (default) +} + +// Get the component perpendicular to the plane (for axis drawing) +vec2 getAxisCoords(vec3 pos) { + // Returns the two coordinates used for drawing axis lines + // First component -> first axis color, Second component -> second axis color + if (u_GridPlane == 1) return vec2(pos.x, pos.y); // XY: X-axis and Y-axis + if (u_GridPlane == 2) return vec2(pos.y, pos.z); // YZ: Y-axis and Z-axis + return vec2(pos.x, pos.z); // XZ: X-axis and Z-axis +} + +// Calculate t for ray-plane intersection +float rayPlaneIntersection(vec3 nearPoint, vec3 farPoint) { + vec3 rayDir = farPoint - nearPoint; + + if (u_GridPlane == 1) { + // XY plane (z = 0) + if (abs(rayDir.z) < 0.0001) return -1.0; + return -nearPoint.z / rayDir.z; + } + if (u_GridPlane == 2) { + // YZ plane (x = 0) + if (abs(rayDir.x) < 0.0001) return -1.0; + return -nearPoint.x / rayDir.x; + } + // XZ plane (y = 0) - default + if (abs(rayDir.y) < 0.0001) return -1.0; + return -nearPoint.y / rayDir.y; +} + +// Get view angle component for normal fade +float getViewAngleComponent(vec3 viewDir) { + if (u_GridPlane == 1) return abs(viewDir.z); // XY plane + if (u_GridPlane == 2) return abs(viewDir.x); // YZ plane + return abs(viewDir.y); // XZ plane +} + +// Pristine grid - single pixel line with proper AA +float pristineGridLine(vec2 uv) { + vec2 dudv = fwidth(uv); + vec2 uvMod = fract(uv); + vec2 uvDist = min(uvMod, 1.0 - uvMod); + vec2 distInPixels = uvDist / dudv; + vec2 lineAlpha = 1.0 - smoothstep(0.0, 1.0, distInPixels); + float alpha = max(lineAlpha.x, lineAlpha.y); + float density = max(dudv.x, dudv.y); + float densityFade = 1.0 - smoothstep(0.5, 1.0, density); + return alpha * densityFade; +} + +// Axis line - single pixel wide +float axisLineAA(float coord, float dudv) { + float distInPixels = abs(coord) / dudv; + return 1.0 - smoothstep(0.0, 1.5, distInPixels); +} + +void main() { + + + float t = rayPlaneIntersection(v_NearPoint, v_FarPoint); + + if (t < 0.0) { + discard; + } + + vec3 fragPos3D = v_NearPoint + t * (v_FarPoint - v_NearPoint); + float depth = computeDepth(fragPos3D); + + if (depth > 1.0 || depth < -1.0) { + discard; + } + + vec2 worldPos = getPlaneCoords(fragPos3D); + + // === Fading === + + // Radial fade + float dist = length(fragPos3D - CameraInput.CameraPosition); + float radialFade = 1.0 - smoothstep(u_FadeDistance * 0.3, u_FadeDistance, dist); + + // Normal fade (view angle) + vec3 viewDir = normalize(fragPos3D - CameraInput.CameraPosition); + float viewAngle = getViewAngleComponent(viewDir); + float normalFade = smoothstep(0.0, 0.15, viewAngle); + + float fadeFactor = radialFade * normalFade; + + if (fadeFactor < 0.001) { + discard; + } + + // === Grid calculation === + + vec2 gridCoord1 = worldPos / u_GridScale; + vec2 gridCoord10 = worldPos / (u_GridScale * 10.0); + + float grid1 = pristineGridLine(gridCoord1); + float grid10 = pristineGridLine(gridCoord10); + + // LOD blend + vec2 deriv1 = fwidth(gridCoord1); + float lodFactor = smoothstep(0.3, 0.6, max(deriv1.x, deriv1.y)); + + // Combine grids + float gridIntensity = mix(max(grid1, grid10 * 0.7), grid10, lodFactor); + + // Grid color + vec3 gridColor = mix(u_GridColorThin.rgb, u_GridColorThick.rgb, lodFactor); + float baseAlpha = mix(u_GridColorThin.a, u_GridColorThick.a, lodFactor); + float gridAlpha = baseAlpha * gridIntensity * fadeFactor; + + // === Axis lines === + + vec2 axisCoords = getAxisCoords(fragPos3D); + vec2 worldDeriv = fwidth(worldPos); + + // First axis (uses AxisColorX - typically red) + float axis1Alpha = axisLineAA(axisCoords.y, worldDeriv.y) * fadeFactor; + // Second axis (uses AxisColorZ - typically blue) + float axis2Alpha = axisLineAA(axisCoords.x, worldDeriv.x) * fadeFactor; + + // === Final composition === + + vec3 finalColor = gridColor; + float finalAlpha = gridAlpha; + + // Blend axis colors + if (axis2Alpha > 0.001) { + float blend = axis2Alpha * u_AxisColorZ.a; + finalColor = mix(finalColor, u_AxisColorZ.rgb, blend); + finalAlpha = max(finalAlpha, blend); + } + + if (axis1Alpha > 0.001) { + float blend = axis1Alpha * u_AxisColorX.a; + finalColor = mix(finalColor, u_AxisColorX.rgb, blend); + finalAlpha = max(finalAlpha, blend); + } + + if (finalAlpha < 0.001) { + discard; + } + + gl_FragDepth = depth * 0.5 + 0.5; + o_Color = vec4(finalColor, finalAlpha); +} diff --git a/Editor/assets/shaders/PBRShader_Anim.glsl b/Editor/assets/shaders/PBRShader_Anim.glsl index 50fdadd..a4bef82 100644 --- a/Editor/assets/shaders/PBRShader_Anim.glsl +++ b/Editor/assets/shaders/PBRShader_Anim.glsl @@ -1,5 +1,5 @@ // ----------------------------- -// -- Hazel Engine PBR shader -- +// -- Based on Hazel PBR shader -- // ----------------------------- // Note: this shader is still very much in progress. There are likely many bugs and future additions that will go in. // Currently heavily updated. @@ -21,53 +21,48 @@ layout(location = 4) in vec2 a_TexCoord; layout(location = 5) in ivec4 a_BoneIndices; layout(location = 6) in vec4 a_BoneWeights; +const int MAX_BONES = 100; +uniform mat4 u_BoneTransforms[100]; + uniform mat4 u_ViewProjectionMatrix; uniform mat4 u_ViewMatrix; uniform mat4 u_Transform; -uniform mat4 u_LightMatrixCascade0; -uniform mat4 u_LightMatrixCascade1; -uniform mat4 u_LightMatrixCascade2; -uniform mat4 u_LightMatrixCascade3; - -const int MAX_BONES = 100; -uniform mat4 u_BoneTransforms[100]; +uniform mat4 u_LightSpaceMatrix; out VertexOutput { - vec3 WorldPosition; - vec3 Normal; - vec2 TexCoord; - mat3 WorldNormals; - mat3 WorldTransform; - vec3 Binormal; - vec4 ShadowMapCoords[4]; - vec3 ViewPosition; + vec3 WorldPosition; + vec3 Normal; + vec2 TexCoord; + mat3 WorldNormals; + mat3 WorldTransform; + vec3 Binormal; + vec3 ViewPosition; + vec4 FragPosLightSpace; } vs_Output; void main() { - mat4 boneTransform = u_BoneTransforms[a_BoneIndices[0]] * a_BoneWeights[0]; - boneTransform += u_BoneTransforms[a_BoneIndices[1]] * a_BoneWeights[1]; - boneTransform += u_BoneTransforms[a_BoneIndices[2]] * a_BoneWeights[2]; - boneTransform += u_BoneTransforms[a_BoneIndices[3]] * a_BoneWeights[3]; + mat4 boneTransform = u_BoneTransforms[a_BoneIndices[0]] * a_BoneWeights[0]; + boneTransform += u_BoneTransforms[a_BoneIndices[1]] * a_BoneWeights[1]; + boneTransform += u_BoneTransforms[a_BoneIndices[2]] * a_BoneWeights[2]; + boneTransform += u_BoneTransforms[a_BoneIndices[3]] * a_BoneWeights[3]; - vec4 localPosition = boneTransform * vec4(a_Position, 1.0); + vec4 localPosition = boneTransform * vec4(a_Position, 1.0); - vs_Output.WorldPosition = vec3(u_Transform * boneTransform * vec4(a_Position, 1.0)); - vs_Output.Normal = mat3(u_Transform) * mat3(boneTransform) * a_Normal; - vs_Output.TexCoord = vec2(a_TexCoord.x, 1.0 - a_TexCoord.y); - vs_Output.WorldNormals = mat3(u_Transform) * mat3(a_Tangent, a_Binormal, a_Normal); - vs_Output.WorldTransform = mat3(u_Transform); - vs_Output.Binormal = a_Binormal; + vs_Output.WorldPosition = vec3(u_Transform * boneTransform * vec4(a_Position, 1.0)); + vs_Output.Normal = mat3(u_Transform) * mat3(boneTransform) * a_Normal; + vs_Output.TexCoord = vec2(a_TexCoord.x, 1.0 - a_TexCoord.y); + vs_Output.WorldNormals = mat3(u_Transform) * mat3(a_Tangent, a_Binormal, a_Normal); + vs_Output.WorldTransform = mat3(u_Transform); + vs_Output.Binormal = a_Binormal; - vs_Output.ShadowMapCoords[0] = u_LightMatrixCascade0 * vec4(vs_Output.WorldPosition, 1.0); - vs_Output.ShadowMapCoords[1] = u_LightMatrixCascade1 * vec4(vs_Output.WorldPosition, 1.0); - vs_Output.ShadowMapCoords[2] = u_LightMatrixCascade2 * vec4(vs_Output.WorldPosition, 1.0); - vs_Output.ShadowMapCoords[3] = u_LightMatrixCascade3 * vec4(vs_Output.WorldPosition, 1.0); - vs_Output.ViewPosition = vec3(u_ViewMatrix * vec4(vs_Output.WorldPosition, 1.0)); + vs_Output.FragPosLightSpace = u_LightSpaceMatrix * u_Transform * vec4(a_Position, 1.0); - gl_Position = u_ViewProjectionMatrix * u_Transform * localPosition; + vs_Output.ViewPosition = vec3(u_ViewMatrix * vec4(vs_Output.WorldPosition, 1.0)); + + gl_Position = u_ViewProjectionMatrix * u_Transform * vec4(a_Position, 1.0); } #type fragment @@ -77,27 +72,25 @@ const float PI = 3.141592; const float Epsilon = 0.00001; const int LightCount = 1; - -// Constant normal incidence Fresnel factor for all dielectrics. const vec3 Fdielectric = vec3(0.04); struct DirectionalLight { - vec3 Direction; - vec3 Radiance; - float Multiplier; + vec3 Direction; + vec3 Radiance; + float Multiplier; }; in VertexOutput { - vec3 WorldPosition; - vec3 Normal; - vec2 TexCoord; - mat3 WorldNormals; - mat3 WorldTransform; - vec3 Binormal; - vec4 ShadowMapCoords[4]; - vec3 ViewPosition; + vec3 WorldPosition; + vec3 Normal; + vec2 TexCoord; + mat3 WorldNormals; + mat3 WorldTransform; + vec3 Binormal; + vec3 ViewPosition; + vec4 FragPosLightSpace; } vs_Input; layout(location = 0) out vec4 color; @@ -106,533 +99,248 @@ layout(location = 1) out vec4 o_BloomColor; uniform DirectionalLight u_DirectionalLights; uniform vec3 u_CameraPosition; -// PBR texture inputs +// PBR uniform sampler2D u_AlbedoTexture; uniform sampler2D u_NormalTexture; uniform sampler2D u_MetalnessTexture; uniform sampler2D u_RoughnessTexture; -// Environment maps +// environment uniform samplerCube u_EnvRadianceTex; uniform samplerCube u_EnvIrradianceTex; // BRDF LUT uniform sampler2D u_BRDFLUTTexture; -// PCSS -uniform sampler2D u_ShadowMapTexture[4]; -uniform mat4 u_LightView; -uniform bool u_ShowCascades; -uniform bool u_SoftShadows; -uniform float u_LightSize; -uniform float u_MaxShadowDistance; -uniform float u_ShadowFade; -uniform bool u_CascadeFading; -uniform float u_CascadeTransitionFade; - -uniform vec4 u_CascadeSplits; - uniform float u_IBLContribution; - uniform float u_BloomThreshold; +uniform float u_EnvMapRotation; -//////////////////////////////////////// - -uniform vec3 u_AlbedoColor; +// baseColor +uniform vec3 u_AlbedoColor; uniform float u_Metalness; uniform float u_Roughness; -uniform float u_EnvMapRotation; - -// Toggles +// textureToggle uniform float u_AlbedoTexToggle; uniform float u_NormalTexToggle; uniform float u_MetalnessTexToggle; uniform float u_RoughnessTexToggle; +// shadow +uniform sampler2D u_ShadowMap; +uniform float u_ShadowBias; +uniform float u_ShadowSoftness; +uniform float u_ShadowIntensity; +uniform int u_ShadowEnabled; + struct PBRParameters { - vec3 Albedo; - float Roughness; - float Metalness; - - vec3 Normal; - vec3 View; - float NdotV; + vec3 Albedo; + float Roughness; + float Metalness; + vec3 Normal; + vec3 View; + float NdotV; }; PBRParameters m_Params; -// GGX/Towbridge-Reitz normal distribution function. -// Uses Disney's reparametrization of alpha = roughness^2 +// ---------- PBR param func ---------- float ndfGGX(float cosLh, float roughness) { - float alpha = roughness * roughness; - float alphaSq = alpha * alpha; - - float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0; - return alphaSq / (PI * denom * denom); -} - -// Single term for separable Schlick-GGX below. -float gaSchlickG1(float cosTheta, float k) -{ - return cosTheta / (cosTheta * (1.0 - k) + k); -} - -// Schlick-GGX approximation of geometric attenuation function using Smith's method. -float gaSchlickGGX(float cosLi, float NdotV, float roughness) -{ - float r = roughness + 1.0; - float k = (r * r) / 8.0; // Epic suggests using this roughness remapping for analytic lights. - return gaSchlickG1(cosLi, k) * gaSchlickG1(NdotV, k); + float alpha = roughness * roughness; + float alphaSq = alpha * alpha; + float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0; + return alphaSq / (PI * denom * denom); } float GeometrySchlickGGX(float NdotV, float roughness) { - float r = (roughness + 1.0); - float k = (r*r) / 8.0; - - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return nom / denom; + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + return nom / denom; } float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - - return ggx1 * ggx2; + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + return ggx1 * ggx2; } -// Shlick's approximation of the Fresnel factor. vec3 fresnelSchlick(vec3 F0, float cosTheta) { - return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); } vec3 fresnelSchlickRoughness(vec3 F0, float cosTheta, float roughness) { - return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); -} - -// --------------------------------------------------------------------------------------------------- -// The following code (from Unreal Engine 4's paper) shows how to filter the environment map -// for different roughnesses. This is mean to be computed offline and stored in cube map mips, -// so turning this on online will cause poor performance -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} - -vec2 Hammersley(uint i, uint N) -{ - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); -} - -vec3 ImportanceSampleGGX(vec2 Xi, float Roughness, vec3 N) -{ - float a = Roughness * Roughness; - float Phi = 2 * PI * Xi.x; - float CosTheta = sqrt( (1 - Xi.y) / ( 1 + (a*a - 1) * Xi.y ) ); - float SinTheta = sqrt( 1 - CosTheta * CosTheta ); - vec3 H; - H.x = SinTheta * cos( Phi ); - H.y = SinTheta * sin( Phi ); - H.z = CosTheta; - vec3 UpVector = abs(N.z) < 0.999 ? vec3(0,0,1) : vec3(1,0,0); - vec3 TangentX = normalize( cross( UpVector, N ) ); - vec3 TangentY = cross( N, TangentX ); - // Tangent to world space - return TangentX * H.x + TangentY * H.y + N * H.z; -} - -float TotalWeight = 0.0; - -vec3 PrefilterEnvMap(float Roughness, vec3 R) -{ - vec3 N = R; - vec3 V = R; - vec3 PrefilteredColor = vec3(0.0); - int NumSamples = 1024; - for(int i = 0; i < NumSamples; i++) - { - vec2 Xi = Hammersley(i, NumSamples); - vec3 H = ImportanceSampleGGX(Xi, Roughness, N); - vec3 L = 2 * dot(V, H) * H - V; - float NoL = clamp(dot(N, L), 0.0, 1.0); - if (NoL > 0) - { - PrefilteredColor += texture(u_EnvRadianceTex, L).rgb * NoL; - TotalWeight += NoL; - } - } - return PrefilteredColor / TotalWeight; -} - -// --------------------------------------------------------------------------------------------------- - -vec3 RotateVectorAboutY(float angle, vec3 vec) -{ - angle = radians(angle); - mat3x3 rotationMatrix ={vec3(cos(angle),0.0,sin(angle)), - vec3(0.0,1.0,0.0), - vec3(-sin(angle),0.0,cos(angle))}; - return rotationMatrix * vec; + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); } +// ---------- direction light ---------- vec3 Lighting(vec3 F0) { - vec3 result = vec3(0.0); - for(int i = 0; i < LightCount; i++) - { - vec3 Li = u_DirectionalLights.Direction; - vec3 Lradiance = u_DirectionalLights.Radiance * u_DirectionalLights.Multiplier; - vec3 Lh = normalize(Li + m_Params.View); + vec3 result = vec3(0.0); + for(int i = 0; i < LightCount; i++) + { + vec3 Li = u_DirectionalLights.Direction; + vec3 Lradiance = u_DirectionalLights.Radiance * u_DirectionalLights.Multiplier; + vec3 Lh = normalize(Li + m_Params.View); - // Calculate angles between surface normal and various light vectors. - float cosLi = max(0.0, dot(m_Params.Normal, Li)); - float cosLh = max(0.0, dot(m_Params.Normal, Lh)); + float cosLi = max(0.0, dot(m_Params.Normal, Li)); + float cosLh = max(0.0, dot(m_Params.Normal, Lh)); - vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, m_Params.View))); - float D = ndfGGX(cosLh, m_Params.Roughness); - float G = gaSchlickGGX(cosLi, m_Params.NdotV, m_Params.Roughness); + vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, m_Params.View))); + float D = ndfGGX(cosLh, m_Params.Roughness); + float G = GeometrySmith(m_Params.Normal, m_Params.View, Li, m_Params.Roughness); - vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness); - vec3 diffuseBRDF = kd * m_Params.Albedo; + vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness); + vec3 diffuseBRDF = kd * m_Params.Albedo; - // Cook-Torrance - vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * m_Params.NdotV); + vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * m_Params.NdotV); - result += (diffuseBRDF + specularBRDF) * Lradiance * cosLi; - } - return result; + result += (diffuseBRDF + specularBRDF) * Lradiance * cosLi; + } + return result; +} + +// ---------- IBL ---------- +vec3 RotateVectorAboutY(float angle, vec3 vec) +{ + angle = radians(angle); + mat3 rotationMatrix = mat3( + vec3(cos(angle), 0.0, sin(angle)), + vec3(0.0, 1.0, 0.0), + vec3(-sin(angle), 0.0, cos(angle)) + ); + return rotationMatrix * vec; } vec3 IBL(vec3 F0, vec3 Lr) { - vec3 irradiance = texture(u_EnvIrradianceTex, m_Params.Normal).rgb; - vec3 F = fresnelSchlickRoughness(F0, m_Params.NdotV, m_Params.Roughness); - vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness); - vec3 diffuseIBL = m_Params.Albedo * irradiance; + vec3 irradiance = texture(u_EnvIrradianceTex, m_Params.Normal).rgb; + vec3 F = fresnelSchlickRoughness(F0, m_Params.NdotV, m_Params.Roughness); + vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness); + vec3 diffuseIBL = m_Params.Albedo * irradiance; - int u_EnvRadianceTexLevels = textureQueryLevels(u_EnvRadianceTex); - float NoV = clamp(m_Params.NdotV, 0.0, 1.0); - vec3 R = 2.0 * dot(m_Params.View, m_Params.Normal) * m_Params.Normal - m_Params.View; - vec3 specularIrradiance = textureLod(u_EnvRadianceTex, RotateVectorAboutY(u_EnvMapRotation, Lr), (m_Params.Roughness) * u_EnvRadianceTexLevels).rgb; + int u_EnvRadianceTexLevels = textureQueryLevels(u_EnvRadianceTex); + vec3 specularIrradiance = textureLod( + u_EnvRadianceTex, + RotateVectorAboutY(u_EnvMapRotation, Lr), + m_Params.Roughness * u_EnvRadianceTexLevels + ).rgb; - // Sample BRDF Lut, 1.0 - roughness for y-coord because texture was generated (in Sparky) for gloss model - vec2 specularBRDF = texture(u_BRDFLUTTexture, vec2(m_Params.NdotV, 1.0 - m_Params.Roughness)).rg; - vec3 specularIBL = specularIrradiance * (F * specularBRDF.x + specularBRDF.y); + vec2 specularBRDF = texture(u_BRDFLUTTexture, vec2(m_Params.NdotV, 1.0 - m_Params.Roughness)).rg; + vec3 specularIBL = specularIrradiance * (F * specularBRDF.x + specularBRDF.y); - return kd * diffuseIBL + specularIBL; + return kd * diffuseIBL + specularIBL; } -///////////////////////////////////////////// -// PCSS -///////////////////////////////////////////// +// shadow -uint CascadeIndex = 0; -float ShadowFade = 1.0; - -float GetShadowBias() +float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) { - const float MINIMUM_SHADOW_BIAS = 0.002; - float bias = max(MINIMUM_SHADOW_BIAS * (1.0 - dot(m_Params.Normal, u_DirectionalLights.Direction)), MINIMUM_SHADOW_BIAS); - return bias; + // Perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + + // Transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + + // If outside shadow map bounds, assume no shadow + if(projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0) + return 0.0; + + // Get closest depth value from light's perspective + float closestDepth = texture(u_ShadowMap, projCoords.xy).r; + float currentDepth = projCoords.z; + + // Calculate bias based on surface angle + float bias = max(u_ShadowBias * (1.0 - dot(normal, lightDir)), u_ShadowBias * 0.1); + + // PCF (Percentage Closer Filtering) for soft shadows + float shadow = 0.0; + vec2 texelSize = 1.0 / textureSize(u_ShadowMap, 0); + int pcfRange = int(u_ShadowSoftness); + int sampleCount = 0; + + for(int x = -pcfRange; x <= pcfRange; ++x) + { + for(int y = -pcfRange; y <= pcfRange; ++y) + { + float pcfDepth = texture(u_ShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; + sampleCount++; + } + } + shadow /= float(sampleCount); + + return shadow; } -float HardShadows_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords) + +float ComputeShadow(vec4 fragPosLightSpace, float NdotL) { - float bias = GetShadowBias(); - float z = texture(shadowMap, shadowCoords.xy).x; - return 1.0 - step(z + bias, shadowCoords.z) * ShadowFade; + if (u_ShadowEnabled == 0) return 1.0; + + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + projCoords = projCoords * 0.5 + 0.5; + + if (projCoords.x < 0.0 || projCoords.x > 1.0 || + projCoords.y < 0.0 || projCoords.y > 1.0 || + projCoords.z > 1.0) return 1.0; + + float closestDepth = texture(u_ShadowMap, projCoords.xy).r; + float currentDepth = projCoords.z; + + float bias = max(u_ShadowBias * (1.0 - NdotL), u_ShadowBias * 0.5); + + float shadow = (currentDepth - bias) > closestDepth ? 1.0 : 0.0; + return mix(1.0, 1.0 - u_ShadowIntensity, shadow); } -// Penumbra - -// this search area estimation comes from the following article: -// http://developer.download.nvidia.com/whitepapers/2008/PCSS_DirectionalLight_Integration.pdf -float SearchWidth(float uvLightSize, float receiverDistance) -{ - const float NEAR = 0.1; - return uvLightSize * (receiverDistance - NEAR) / u_CameraPosition.z; -} - -float u_light_zNear = 0.0; // 0.01 gives artifacts? maybe because of ortho proj? -float u_light_zFar = 10000.0; -vec2 u_lightRadiusUV = vec2(0.05); - -vec2 searchRegionRadiusUV(float zWorld) -{ - return u_lightRadiusUV * (zWorld - u_light_zNear) / zWorld; -} - -const vec2 PoissonDistribution[64] = vec2[]( -vec2(-0.884081, 0.124488), -vec2(-0.714377, 0.027940), -vec2(-0.747945, 0.227922), -vec2(-0.939609, 0.243634), -vec2(-0.985465, 0.045534), -vec2(-0.861367, -0.136222), -vec2(-0.881934, 0.396908), -vec2(-0.466938, 0.014526), -vec2(-0.558207, 0.212662), -vec2(-0.578447, -0.095822), -vec2(-0.740266, -0.095631), -vec2(-0.751681, 0.472604), -vec2(-0.553147, -0.243177), -vec2(-0.674762, -0.330730), -vec2(-0.402765, -0.122087), -vec2(-0.319776, -0.312166), -vec2(-0.413923, -0.439757), -vec2(-0.979153, -0.201245), -vec2(-0.865579, -0.288695), -vec2(-0.243704, -0.186378), -vec2(-0.294920, -0.055748), -vec2(-0.604452, -0.544251), -vec2(-0.418056, -0.587679), -vec2(-0.549156, -0.415877), -vec2(-0.238080, -0.611761), -vec2(-0.267004, -0.459702), -vec2(-0.100006, -0.229116), -vec2(-0.101928, -0.380382), -vec2(-0.681467, -0.700773), -vec2(-0.763488, -0.543386), -vec2(-0.549030, -0.750749), -vec2(-0.809045, -0.408738), -vec2(-0.388134, -0.773448), -vec2(-0.429392, -0.894892), -vec2(-0.131597, 0.065058), -vec2(-0.275002, 0.102922), -vec2(-0.106117, -0.068327), -vec2(-0.294586, -0.891515), -vec2(-0.629418, 0.379387), -vec2(-0.407257, 0.339748), -vec2(0.071650, -0.384284), -vec2(0.022018, -0.263793), -vec2(0.003879, -0.136073), -vec2(-0.137533, -0.767844), -vec2(-0.050874, -0.906068), -vec2(0.114133, -0.070053), -vec2(0.163314, -0.217231), -vec2(-0.100262, -0.587992), -vec2(-0.004942, 0.125368), -vec2(0.035302, -0.619310), -vec2(0.195646, -0.459022), -vec2(0.303969, -0.346362), -vec2(-0.678118, 0.685099), -vec2(-0.628418, 0.507978), -vec2(-0.508473, 0.458753), -vec2(0.032134, -0.782030), -vec2(0.122595, 0.280353), -vec2(-0.043643, 0.312119), -vec2(0.132993, 0.085170), -vec2(-0.192106, 0.285848), -vec2(0.183621, -0.713242), -vec2(0.265220, -0.596716), -vec2(-0.009628, -0.483058), -vec2(-0.018516, 0.435703) -); - -vec2 SamplePoisson(int index) -{ - return PoissonDistribution[index % 64]; -} - -float FindBlockerDistance_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords, float uvLightSize) -{ - float bias = GetShadowBias(); - - int numBlockerSearchSamples = 64; - int blockers = 0; - float avgBlockerDistance = 0; - - float zEye = -(u_LightView * vec4(vs_Input.WorldPosition, 1.0)).z; - vec2 searchWidth = searchRegionRadiusUV(zEye); - for (int i = 0; i < numBlockerSearchSamples; i++) - { - float z = texture(shadowMap, shadowCoords.xy + SamplePoisson(i) * searchWidth).r; - if (z < (shadowCoords.z - bias)) - { - blockers++; - avgBlockerDistance += z; - } - } - - if (blockers > 0) - return avgBlockerDistance / float(blockers); - - return -1; -} - -float PenumbraWidth(sampler2D shadowMap, vec3 shadowCoords, float uvLightSize) -{ - float blockerDistance = FindBlockerDistance_DirectionalLight(shadowMap, shadowCoords, uvLightSize); - if (blockerDistance == -1) - return -1; - - return (shadowCoords.z - blockerDistance) / blockerDistance; -} - -float PCF_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords, float uvRadius) -{ - float bias = GetShadowBias(); - int numPCFSamples = 64; - float sum = 0; - for (int i = 0; i < numPCFSamples; i++) - { - float z = texture(shadowMap, shadowCoords.xy + SamplePoisson(i) * uvRadius).r; - sum += (z < (shadowCoords.z - bias)) ? 1 : 0; - } - return sum / numPCFSamples; -} - -float PCSS_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords, float uvLightSize) -{ - float blockerDistance = FindBlockerDistance_DirectionalLight(shadowMap, shadowCoords, uvLightSize); - if (blockerDistance == -1) - return 1; - - float penumbraWidth = (shadowCoords.z - blockerDistance) / blockerDistance; - - float NEAR = 0.01; // Should this value be tweakable? - float uvRadius = penumbraWidth * uvLightSize * NEAR / shadowCoords.z; - return 1.0 - PCF_DirectionalLight(shadowMap, shadowCoords, uvRadius) * ShadowFade; -} - -///////////////////////////////////////////// void main() { - // Standard PBR inputs - m_Params.Albedo = u_AlbedoTexToggle > 0.5 ? texture(u_AlbedoTexture, vs_Input.TexCoord).rgb : u_AlbedoColor; - m_Params.Metalness = u_MetalnessTexToggle > 0.5 ? texture(u_MetalnessTexture, vs_Input.TexCoord).r : u_Metalness; - m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness; - m_Params.Roughness = max(m_Params.Roughness, 0.05); // Minimum roughness of 0.05 to keep specular highlight + m_Params.Albedo = u_AlbedoTexToggle > 0.5 ? texture(u_AlbedoTexture, vs_Input.TexCoord).rgb : u_AlbedoColor; + m_Params.Metalness = u_MetalnessTexToggle > 0.5 ? texture(u_MetalnessTexture, vs_Input.TexCoord).r : u_Metalness; + m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness; + m_Params.Roughness = max(m_Params.Roughness, 0.05); - // Normals (either from vertex or map) - m_Params.Normal = normalize(vs_Input.Normal); - if (u_NormalTexToggle > 0.5) - { - m_Params.Normal = normalize(2.0 * texture(u_NormalTexture, vs_Input.TexCoord).rgb - 1.0); - m_Params.Normal = normalize(vs_Input.WorldNormals * m_Params.Normal); - } + // normal + m_Params.Normal = normalize(vs_Input.Normal); + if (u_NormalTexToggle > 0.5) + { + m_Params.Normal = normalize(2.0 * texture(u_NormalTexture, vs_Input.TexCoord).rgb - 1.0); + m_Params.Normal = normalize(vs_Input.WorldNormals * m_Params.Normal); + } - m_Params.View = normalize(u_CameraPosition - vs_Input.WorldPosition); - m_Params.NdotV = max(dot(m_Params.Normal, m_Params.View), 0.0); + m_Params.View = normalize(u_CameraPosition - vs_Input.WorldPosition); + m_Params.NdotV = max(dot(m_Params.Normal, m_Params.View), 0.0); - // Specular reflection vector - vec3 Lr = 2.0 * m_Params.NdotV * m_Params.Normal - m_Params.View; + vec3 Lr = 2.0 * m_Params.NdotV * m_Params.Normal - m_Params.View; - // Fresnel reflectance, metals use albedo - vec3 F0 = mix(Fdielectric, m_Params.Albedo, m_Params.Metalness); + vec3 F0 = mix(Fdielectric, m_Params.Albedo, m_Params.Metalness); - const uint SHADOW_MAP_CASCADE_COUNT = 4; - for(uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; i++) - { - if(vs_Input.ViewPosition.z < u_CascadeSplits[i]) - CascadeIndex = i + 1; - } + float shadowFactor = 1.0; + if (u_ShadowEnabled > 0.5) { + float shadow = calculateShadow(vs_Input.FragPosLightSpace, m_Params.Normal, u_DirectionalLights.Direction); + shadowFactor = 1.0 - shadow; + } - float shadowDistance = u_MaxShadowDistance;//u_CascadeSplits[3]; - float transitionDistance = u_ShadowFade; - float distance = length(vs_Input.ViewPosition); - ShadowFade = distance - (shadowDistance - transitionDistance); - ShadowFade /= transitionDistance; - ShadowFade = clamp(1.0 - ShadowFade, 0.0, 1.0); + // directional light with shadow + vec3 lightContribution = u_DirectionalLights.Multiplier > 0.0 ? Lighting(F0) * shadowFactor : vec3(0.0); - bool fadeCascades = u_CascadeFading; - float shadowAmount = 1.0; - if (fadeCascades) - { - float cascadeTransitionFade = u_CascadeTransitionFade; + vec3 iblContribution = IBL(F0, Lr) * u_IBLContribution; - float c0 = smoothstep(u_CascadeSplits[0] + cascadeTransitionFade * 0.5f, u_CascadeSplits[0] - cascadeTransitionFade * 0.5f, vs_Input.ViewPosition.z); - float c1 = smoothstep(u_CascadeSplits[1] + cascadeTransitionFade * 0.5f, u_CascadeSplits[1] - cascadeTransitionFade * 0.5f, vs_Input.ViewPosition.z); - float c2 = smoothstep(u_CascadeSplits[2] + cascadeTransitionFade * 0.5f, u_CascadeSplits[2] - cascadeTransitionFade * 0.5f, vs_Input.ViewPosition.z); - if (c0 > 0.0 && c0 < 1.0) - { - // Sample 0 & 1 - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[0].xyz / vs_Input.ShadowMapCoords[0].w); - float shadowAmount0 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[0], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[0], shadowMapCoords); - shadowMapCoords = (vs_Input.ShadowMapCoords[1].xyz / vs_Input.ShadowMapCoords[1].w); - float shadowAmount1 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords); + color = vec4(lightContribution + iblContribution, 1.0); - shadowAmount = mix(shadowAmount0, shadowAmount1, c0); - } - else if (c1 > 0.0 && c1 < 1.0) - { - // Sample 1 & 2 - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[1].xyz / vs_Input.ShadowMapCoords[1].w); - float shadowAmount1 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords); - shadowMapCoords = (vs_Input.ShadowMapCoords[2].xyz / vs_Input.ShadowMapCoords[2].w); - float shadowAmount2 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords); - - shadowAmount = mix(shadowAmount1, shadowAmount2, c1); - } - else if (c2 > 0.0 && c2 < 1.0) - { - // Sample 2 & 3 - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[2].xyz / vs_Input.ShadowMapCoords[2].w); - float shadowAmount2 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords); - shadowMapCoords = (vs_Input.ShadowMapCoords[3].xyz / vs_Input.ShadowMapCoords[3].w); - float shadowAmount3 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[3], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[3], shadowMapCoords); - - shadowAmount = mix(shadowAmount2, shadowAmount3, c2); - } - else - { - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[CascadeIndex].xyz / vs_Input.ShadowMapCoords[CascadeIndex].w); - shadowAmount = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords); - } - } - else - { - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[CascadeIndex].xyz / vs_Input.ShadowMapCoords[CascadeIndex].w); - shadowAmount = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords); - } - - float NdotL = dot(m_Params.Normal, u_DirectionalLights.Direction); - NdotL = smoothstep(0.0, 0.4, NdotL + 0.2); - shadowAmount *= (NdotL * 1.0); - - vec3 iblContribution = IBL(F0, Lr) * u_IBLContribution; - vec3 lightContribution = u_DirectionalLights.Multiplier > 0.0f ? (Lighting(F0) * shadowAmount) : vec3(0.0f); - - color = vec4(lightContribution + iblContribution, 1.0); - - // Bloom - float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); - o_BloomColor = vec4(0.0, 0.0, 0.0, 1.0); - if (brightness > u_BloomThreshold) - o_BloomColor = color; - - if (u_ShowCascades) - { - switch(CascadeIndex) - { - case 0: - color.rgb *= vec3(1.0f, 0.25f, 0.25f); - break; - case 1: - color.rgb *= vec3(0.25f, 1.0f, 0.25f); - break; - case 2: - color.rgb *= vec3(0.25f, 0.25f, 1.0f); - break; - case 3: - color.rgb *= vec3(1.0f, 1.0f, 0.25f); - break; - } - } + // Bloom + float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); + o_BloomColor = brightness > u_BloomThreshold ? color : vec4(0.0, 0.0, 0.0, 1.0); } diff --git a/Editor/assets/shaders/PBRShader_Static.glsl b/Editor/assets/shaders/PBRShader_Static.glsl index 7434ea0..f1bde27 100644 --- a/Editor/assets/shaders/PBRShader_Static.glsl +++ b/Editor/assets/shaders/PBRShader_Static.glsl @@ -1,5 +1,5 @@ // ----------------------------- -// -- Hazel Engine PBR shader -- +// -- Based on Hazel PBR shader -- // ----------------------------- // Note: this shader is still very much in progress. There are likely many bugs and future additions that will go in. // Currently heavily updated. @@ -22,10 +22,7 @@ uniform mat4 u_ViewProjectionMatrix; uniform mat4 u_ViewMatrix; uniform mat4 u_Transform; -uniform mat4 u_LightMatrixCascade0; -uniform mat4 u_LightMatrixCascade1; -uniform mat4 u_LightMatrixCascade2; -uniform mat4 u_LightMatrixCascade3; +uniform mat4 u_LightSpaceMatrix; out VertexOutput { @@ -35,8 +32,8 @@ out VertexOutput mat3 WorldNormals; mat3 WorldTransform; vec3 Binormal; - vec4 ShadowMapCoords[4]; vec3 ViewPosition; + vec4 FragPosLightSpace; } vs_Output; void main() @@ -48,10 +45,8 @@ void main() vs_Output.WorldTransform = mat3(u_Transform); vs_Output.Binormal = a_Binormal; - vs_Output.ShadowMapCoords[0] = u_LightMatrixCascade0 * vec4(vs_Output.WorldPosition, 1.0); - vs_Output.ShadowMapCoords[1] = u_LightMatrixCascade1 * vec4(vs_Output.WorldPosition, 1.0); - vs_Output.ShadowMapCoords[2] = u_LightMatrixCascade2 * vec4(vs_Output.WorldPosition, 1.0); - vs_Output.ShadowMapCoords[3] = u_LightMatrixCascade3 * vec4(vs_Output.WorldPosition, 1.0); + vs_Output.FragPosLightSpace = u_LightSpaceMatrix * u_Transform * vec4(a_Position, 1.0); + vs_Output.ViewPosition = vec3(u_ViewMatrix * vec4(vs_Output.WorldPosition, 1.0)); gl_Position = u_ViewProjectionMatrix * u_Transform * vec4(a_Position, 1.0); @@ -64,8 +59,6 @@ const float PI = 3.141592; const float Epsilon = 0.00001; const int LightCount = 1; - -// Constant normal incidence Fresnel factor for all dielectrics. const vec3 Fdielectric = vec3(0.04); struct DirectionalLight @@ -83,8 +76,8 @@ in VertexOutput mat3 WorldNormals; mat3 WorldTransform; vec3 Binormal; - vec4 ShadowMapCoords[4]; vec3 ViewPosition; + vec4 FragPosLightSpace; } vs_Input; layout(location = 0) out vec4 color; @@ -93,56 +86,46 @@ layout(location = 1) out vec4 o_BloomColor; uniform DirectionalLight u_DirectionalLights; uniform vec3 u_CameraPosition; -// PBR texture inputs +// PBR uniform sampler2D u_AlbedoTexture; uniform sampler2D u_NormalTexture; uniform sampler2D u_MetalnessTexture; uniform sampler2D u_RoughnessTexture; -// Environment maps +// environment uniform samplerCube u_EnvRadianceTex; uniform samplerCube u_EnvIrradianceTex; // BRDF LUT uniform sampler2D u_BRDFLUTTexture; -// PCSS -uniform sampler2D u_ShadowMapTexture[4]; -uniform mat4 u_LightView; -uniform bool u_ShowCascades; -uniform bool u_SoftShadows; -uniform float u_LightSize; -uniform float u_MaxShadowDistance; -uniform float u_ShadowFade; -uniform bool u_CascadeFading; -uniform float u_CascadeTransitionFade; - -uniform vec4 u_CascadeSplits; - uniform float u_IBLContribution; - uniform float u_BloomThreshold; +uniform float u_EnvMapRotation; -//////////////////////////////////////// - -uniform vec3 u_AlbedoColor; +// baseColor +uniform vec3 u_AlbedoColor; uniform float u_Metalness; uniform float u_Roughness; -uniform float u_EnvMapRotation; - -// Toggles +// textureToggle uniform float u_AlbedoTexToggle; uniform float u_NormalTexToggle; uniform float u_MetalnessTexToggle; uniform float u_RoughnessTexToggle; +// shadow +uniform sampler2D u_ShadowMap; +uniform float u_ShadowBias; +uniform float u_ShadowSoftness; +uniform float u_ShadowIntensity; +uniform int u_ShadowEnabled; + struct PBRParameters { vec3 Albedo; float Roughness; float Metalness; - vec3 Normal; vec3 View; float NdotV; @@ -150,39 +133,21 @@ struct PBRParameters PBRParameters m_Params; -// GGX/Towbridge-Reitz normal distribution function. -// Uses Disney's reparametrization of alpha = roughness^2 +// ---------- PBR param func ---------- float ndfGGX(float cosLh, float roughness) { float alpha = roughness * roughness; float alphaSq = alpha * alpha; - float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0; return alphaSq / (PI * denom * denom); } -// Single term for separable Schlick-GGX below. -float gaSchlickG1(float cosTheta, float k) -{ - return cosTheta / (cosTheta * (1.0 - k) + k); -} - -// Schlick-GGX approximation of geometric attenuation function using Smith's method. -float gaSchlickGGX(float cosLi, float NdotV, float roughness) -{ - float r = roughness + 1.0; - float k = (r * r) / 8.0; // Epic suggests using this roughness remapping for analytic lights. - return gaSchlickG1(cosLi, k) * gaSchlickG1(NdotV, k); -} - float GeometrySchlickGGX(float NdotV, float roughness) { float r = (roughness + 1.0); - float k = (r*r) / 8.0; - - float nom = NdotV; + float k = (r * r) / 8.0; + float nom = NdotV; float denom = NdotV * (1.0 - k) + k; - return nom / denom; } @@ -192,11 +157,9 @@ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) float NdotL = max(dot(N, L), 0.0); float ggx2 = GeometrySchlickGGX(NdotV, roughness); float ggx1 = GeometrySchlickGGX(NdotL, roughness); - return ggx1 * ggx2; } -// Shlick's approximation of the Fresnel factor. vec3 fresnelSchlick(vec3 F0, float cosTheta) { return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); @@ -207,76 +170,7 @@ vec3 fresnelSchlickRoughness(vec3 F0, float cosTheta, float roughness) return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0); } -// --------------------------------------------------------------------------------------------------- -// The following code (from Unreal Engine 4's paper) shows how to filter the environment map -// for different roughnesses. This is mean to be computed offline and stored in cube map mips, -// so turning this on online will cause poor performance -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} - -vec2 Hammersley(uint i, uint N) -{ - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); -} - -vec3 ImportanceSampleGGX(vec2 Xi, float Roughness, vec3 N) -{ - float a = Roughness * Roughness; - float Phi = 2 * PI * Xi.x; - float CosTheta = sqrt( (1 - Xi.y) / ( 1 + (a*a - 1) * Xi.y ) ); - float SinTheta = sqrt( 1 - CosTheta * CosTheta ); - vec3 H; - H.x = SinTheta * cos( Phi ); - H.y = SinTheta * sin( Phi ); - H.z = CosTheta; - vec3 UpVector = abs(N.z) < 0.999 ? vec3(0,0,1) : vec3(1,0,0); - vec3 TangentX = normalize( cross( UpVector, N ) ); - vec3 TangentY = cross( N, TangentX ); - // Tangent to world space - return TangentX * H.x + TangentY * H.y + N * H.z; -} - -float TotalWeight = 0.0; - -vec3 PrefilterEnvMap(float Roughness, vec3 R) -{ - vec3 N = R; - vec3 V = R; - vec3 PrefilteredColor = vec3(0.0); - int NumSamples = 1024; - for(int i = 0; i < NumSamples; i++) - { - vec2 Xi = Hammersley(i, NumSamples); - vec3 H = ImportanceSampleGGX(Xi, Roughness, N); - vec3 L = 2 * dot(V, H) * H - V; - float NoL = clamp(dot(N, L), 0.0, 1.0); - if (NoL > 0) - { - PrefilteredColor += texture(u_EnvRadianceTex, L).rgb * NoL; - TotalWeight += NoL; - } - } - return PrefilteredColor / TotalWeight; -} - -// --------------------------------------------------------------------------------------------------- - -vec3 RotateVectorAboutY(float angle, vec3 vec) -{ - angle = radians(angle); - mat3x3 rotationMatrix ={vec3(cos(angle),0.0,sin(angle)), - vec3(0.0,1.0,0.0), - vec3(-sin(angle),0.0,cos(angle))}; - return rotationMatrix * vec; -} - +// ---------- direction light ---------- vec3 Lighting(vec3 F0) { vec3 result = vec3(0.0); @@ -286,18 +180,16 @@ vec3 Lighting(vec3 F0) vec3 Lradiance = u_DirectionalLights.Radiance * u_DirectionalLights.Multiplier; vec3 Lh = normalize(Li + m_Params.View); - // Calculate angles between surface normal and various light vectors. float cosLi = max(0.0, dot(m_Params.Normal, Li)); float cosLh = max(0.0, dot(m_Params.Normal, Lh)); vec3 F = fresnelSchlick(F0, max(0.0, dot(Lh, m_Params.View))); float D = ndfGGX(cosLh, m_Params.Roughness); - float G = gaSchlickGGX(cosLi, m_Params.NdotV, m_Params.Roughness); + float G = GeometrySmith(m_Params.Normal, m_Params.View, Li, m_Params.Roughness); vec3 kd = (1.0 - F) * (1.0 - m_Params.Metalness); vec3 diffuseBRDF = kd * m_Params.Albedo; - // Cook-Torrance vec3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * m_Params.NdotV); result += (diffuseBRDF + specularBRDF) * Lradiance * cosLi; @@ -305,6 +197,18 @@ vec3 Lighting(vec3 F0) return result; } +// ---------- IBL ---------- +vec3 RotateVectorAboutY(float angle, vec3 vec) +{ + angle = radians(angle); + mat3 rotationMatrix = mat3( + vec3(cos(angle), 0.0, sin(angle)), + vec3(0.0, 1.0, 0.0), + vec3(-sin(angle), 0.0, cos(angle)) + ); + return rotationMatrix * vec; +} + vec3 IBL(vec3 F0, vec3 Lr) { vec3 irradiance = texture(u_EnvIrradianceTex, m_Params.Normal).rgb; @@ -313,201 +217,89 @@ vec3 IBL(vec3 F0, vec3 Lr) vec3 diffuseIBL = m_Params.Albedo * irradiance; int u_EnvRadianceTexLevels = textureQueryLevels(u_EnvRadianceTex); - float NoV = clamp(m_Params.NdotV, 0.0, 1.0); - vec3 R = 2.0 * dot(m_Params.View, m_Params.Normal) * m_Params.Normal - m_Params.View; - vec3 specularIrradiance = textureLod(u_EnvRadianceTex, RotateVectorAboutY(u_EnvMapRotation, Lr), (m_Params.Roughness) * u_EnvRadianceTexLevels).rgb; + vec3 specularIrradiance = textureLod( + u_EnvRadianceTex, + RotateVectorAboutY(u_EnvMapRotation, Lr), + m_Params.Roughness * u_EnvRadianceTexLevels + ).rgb; - // Sample BRDF Lut, 1.0 - roughness for y-coord because texture was generated (in Sparky) for gloss model vec2 specularBRDF = texture(u_BRDFLUTTexture, vec2(m_Params.NdotV, 1.0 - m_Params.Roughness)).rg; vec3 specularIBL = specularIrradiance * (F * specularBRDF.x + specularBRDF.y); return kd * diffuseIBL + specularIBL; } -///////////////////////////////////////////// -// PCSS -///////////////////////////////////////////// +// shadow -uint CascadeIndex = 0; -float ShadowFade = 1.0; - -float GetShadowBias() +float calculateShadow(vec4 fragPosLightSpace, vec3 normal, vec3 lightDir) { - const float MINIMUM_SHADOW_BIAS = 0.002; - float bias = max(MINIMUM_SHADOW_BIAS * (1.0 - dot(m_Params.Normal, u_DirectionalLights.Direction)), MINIMUM_SHADOW_BIAS); - return bias; -} + // Perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; -float HardShadows_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords) -{ - float bias = GetShadowBias(); - float z = texture(shadowMap, shadowCoords.xy).x; - return 1.0 - step(z + bias, shadowCoords.z) * ShadowFade; -} + // Transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; -// Penumbra + // If outside shadow map bounds, assume no shadow + if(projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0) + return 0.0; -// this search area estimation comes from the following article: -// http://developer.download.nvidia.com/whitepapers/2008/PCSS_DirectionalLight_Integration.pdf -float SearchWidth(float uvLightSize, float receiverDistance) -{ - const float NEAR = 0.1; - return uvLightSize * (receiverDistance - NEAR) / u_CameraPosition.z; -} + // Get closest depth value from light's perspective + float closestDepth = texture(u_ShadowMap, projCoords.xy).r; + float currentDepth = projCoords.z; -float u_light_zNear = 0.0; // 0.01 gives artifacts? maybe because of ortho proj? -float u_light_zFar = 10000.0; -vec2 u_lightRadiusUV = vec2(0.05); + // Calculate bias based on surface angle + float bias = max(u_ShadowBias * (1.0 - dot(normal, lightDir)), u_ShadowBias * 0.1); -vec2 searchRegionRadiusUV(float zWorld) -{ - return u_lightRadiusUV * (zWorld - u_light_zNear) / zWorld; -} + // PCF (Percentage Closer Filtering) for soft shadows + float shadow = 0.0; + vec2 texelSize = 1.0 / textureSize(u_ShadowMap, 0); + int pcfRange = int(u_ShadowSoftness); + int sampleCount = 0; -const vec2 PoissonDistribution[64] = vec2[]( -vec2(-0.884081, 0.124488), -vec2(-0.714377, 0.027940), -vec2(-0.747945, 0.227922), -vec2(-0.939609, 0.243634), -vec2(-0.985465, 0.045534), -vec2(-0.861367, -0.136222), -vec2(-0.881934, 0.396908), -vec2(-0.466938, 0.014526), -vec2(-0.558207, 0.212662), -vec2(-0.578447, -0.095822), -vec2(-0.740266, -0.095631), -vec2(-0.751681, 0.472604), -vec2(-0.553147, -0.243177), -vec2(-0.674762, -0.330730), -vec2(-0.402765, -0.122087), -vec2(-0.319776, -0.312166), -vec2(-0.413923, -0.439757), -vec2(-0.979153, -0.201245), -vec2(-0.865579, -0.288695), -vec2(-0.243704, -0.186378), -vec2(-0.294920, -0.055748), -vec2(-0.604452, -0.544251), -vec2(-0.418056, -0.587679), -vec2(-0.549156, -0.415877), -vec2(-0.238080, -0.611761), -vec2(-0.267004, -0.459702), -vec2(-0.100006, -0.229116), -vec2(-0.101928, -0.380382), -vec2(-0.681467, -0.700773), -vec2(-0.763488, -0.543386), -vec2(-0.549030, -0.750749), -vec2(-0.809045, -0.408738), -vec2(-0.388134, -0.773448), -vec2(-0.429392, -0.894892), -vec2(-0.131597, 0.065058), -vec2(-0.275002, 0.102922), -vec2(-0.106117, -0.068327), -vec2(-0.294586, -0.891515), -vec2(-0.629418, 0.379387), -vec2(-0.407257, 0.339748), -vec2(0.071650, -0.384284), -vec2(0.022018, -0.263793), -vec2(0.003879, -0.136073), -vec2(-0.137533, -0.767844), -vec2(-0.050874, -0.906068), -vec2(0.114133, -0.070053), -vec2(0.163314, -0.217231), -vec2(-0.100262, -0.587992), -vec2(-0.004942, 0.125368), -vec2(0.035302, -0.619310), -vec2(0.195646, -0.459022), -vec2(0.303969, -0.346362), -vec2(-0.678118, 0.685099), -vec2(-0.628418, 0.507978), -vec2(-0.508473, 0.458753), -vec2(0.032134, -0.782030), -vec2(0.122595, 0.280353), -vec2(-0.043643, 0.312119), -vec2(0.132993, 0.085170), -vec2(-0.192106, 0.285848), -vec2(0.183621, -0.713242), -vec2(0.265220, -0.596716), -vec2(-0.009628, -0.483058), -vec2(-0.018516, 0.435703) -); - -vec2 SamplePoisson(int index) -{ - return PoissonDistribution[index % 64]; -} - -float FindBlockerDistance_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords, float uvLightSize) -{ - float bias = GetShadowBias(); - - int numBlockerSearchSamples = 64; - int blockers = 0; - float avgBlockerDistance = 0; - - float zEye = -(u_LightView * vec4(vs_Input.WorldPosition, 1.0)).z; - vec2 searchWidth = searchRegionRadiusUV(zEye); - for (int i = 0; i < numBlockerSearchSamples; i++) + for(int x = -pcfRange; x <= pcfRange; ++x) { - float z = texture(shadowMap, shadowCoords.xy + SamplePoisson(i) * searchWidth).r; - if (z < (shadowCoords.z - bias)) + for(int y = -pcfRange; y <= pcfRange; ++y) { - blockers++; - avgBlockerDistance += z; + float pcfDepth = texture(u_ShadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; + sampleCount++; } } + shadow /= float(sampleCount); - if (blockers > 0) - return avgBlockerDistance / float(blockers); - - return -1; + return shadow; } -float PenumbraWidth(sampler2D shadowMap, vec3 shadowCoords, float uvLightSize) + +float ComputeShadow(vec4 fragPosLightSpace, float NdotL) { - float blockerDistance = FindBlockerDistance_DirectionalLight(shadowMap, shadowCoords, uvLightSize); - if (blockerDistance == -1) - return -1; + if (u_ShadowEnabled == 0) return 1.0; - return (shadowCoords.z - blockerDistance) / blockerDistance; + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + projCoords = projCoords * 0.5 + 0.5; + + if (projCoords.x < 0.0 || projCoords.x > 1.0 || + projCoords.y < 0.0 || projCoords.y > 1.0 || + projCoords.z > 1.0) return 1.0; + + float closestDepth = texture(u_ShadowMap, projCoords.xy).r; + float currentDepth = projCoords.z; + + float bias = max(u_ShadowBias * (1.0 - NdotL), u_ShadowBias * 0.5); + + float shadow = (currentDepth - bias) > closestDepth ? 1.0 : 0.0; + return mix(1.0, 1.0 - u_ShadowIntensity, shadow); } -float PCF_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords, float uvRadius) -{ - float bias = GetShadowBias(); - int numPCFSamples = 64; - float sum = 0; - for (int i = 0; i < numPCFSamples; i++) - { - float z = texture(shadowMap, shadowCoords.xy + SamplePoisson(i) * uvRadius).r; - sum += (z < (shadowCoords.z - bias)) ? 1 : 0; - } - return sum / numPCFSamples; -} - -float PCSS_DirectionalLight(sampler2D shadowMap, vec3 shadowCoords, float uvLightSize) -{ - float blockerDistance = FindBlockerDistance_DirectionalLight(shadowMap, shadowCoords, uvLightSize); - if (blockerDistance == -1) - return 1; - - float penumbraWidth = (shadowCoords.z - blockerDistance) / blockerDistance; - - float NEAR = 0.01; // Should this value be tweakable? - float uvRadius = penumbraWidth * uvLightSize * NEAR / shadowCoords.z; - return 1.0 - PCF_DirectionalLight(shadowMap, shadowCoords, uvRadius) * ShadowFade; -} - -///////////////////////////////////////////// void main() { - // Standard PBR inputs m_Params.Albedo = u_AlbedoTexToggle > 0.5 ? texture(u_AlbedoTexture, vs_Input.TexCoord).rgb : u_AlbedoColor; m_Params.Metalness = u_MetalnessTexToggle > 0.5 ? texture(u_MetalnessTexture, vs_Input.TexCoord).r : u_Metalness; - m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness; - m_Params.Roughness = max(m_Params.Roughness, 0.05); // Minimum roughness of 0.05 to keep specular highlight + m_Params.Roughness = u_RoughnessTexToggle > 0.5 ? texture(u_RoughnessTexture, vs_Input.TexCoord).r : u_Roughness; + m_Params.Roughness = max(m_Params.Roughness, 0.05); - // Normals (either from vertex or map) + // normal m_Params.Normal = normalize(vs_Input.Normal); if (u_NormalTexToggle > 0.5) { @@ -518,108 +310,24 @@ void main() m_Params.View = normalize(u_CameraPosition - vs_Input.WorldPosition); m_Params.NdotV = max(dot(m_Params.Normal, m_Params.View), 0.0); - // Specular reflection vector vec3 Lr = 2.0 * m_Params.NdotV * m_Params.Normal - m_Params.View; - // Fresnel reflectance, metals use albedo vec3 F0 = mix(Fdielectric, m_Params.Albedo, m_Params.Metalness); - const uint SHADOW_MAP_CASCADE_COUNT = 4; - for(uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; i++) - { - if(vs_Input.ViewPosition.z < u_CascadeSplits[i]) - CascadeIndex = i + 1; + float shadowFactor = 1.0; + if (u_ShadowEnabled > 0.5) { + float shadow = calculateShadow(vs_Input.FragPosLightSpace, m_Params.Normal, u_DirectionalLights.Direction); + shadowFactor = 1.0 - shadow; } - float shadowDistance = u_MaxShadowDistance;//u_CascadeSplits[3]; - float transitionDistance = u_ShadowFade; - float distance = length(vs_Input.ViewPosition); - ShadowFade = distance - (shadowDistance - transitionDistance); - ShadowFade /= transitionDistance; - ShadowFade = clamp(1.0 - ShadowFade, 0.0, 1.0); - - bool fadeCascades = u_CascadeFading; - float shadowAmount = 1.0; - if (fadeCascades) - { - float cascadeTransitionFade = u_CascadeTransitionFade; - - float c0 = smoothstep(u_CascadeSplits[0] + cascadeTransitionFade * 0.5f, u_CascadeSplits[0] - cascadeTransitionFade * 0.5f, vs_Input.ViewPosition.z); - float c1 = smoothstep(u_CascadeSplits[1] + cascadeTransitionFade * 0.5f, u_CascadeSplits[1] - cascadeTransitionFade * 0.5f, vs_Input.ViewPosition.z); - float c2 = smoothstep(u_CascadeSplits[2] + cascadeTransitionFade * 0.5f, u_CascadeSplits[2] - cascadeTransitionFade * 0.5f, vs_Input.ViewPosition.z); - if (c0 > 0.0 && c0 < 1.0) - { - // Sample 0 & 1 - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[0].xyz / vs_Input.ShadowMapCoords[0].w); - float shadowAmount0 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[0], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[0], shadowMapCoords); - shadowMapCoords = (vs_Input.ShadowMapCoords[1].xyz / vs_Input.ShadowMapCoords[1].w); - float shadowAmount1 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords); - - shadowAmount = mix(shadowAmount0, shadowAmount1, c0); - } - else if (c1 > 0.0 && c1 < 1.0) - { - // Sample 1 & 2 - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[1].xyz / vs_Input.ShadowMapCoords[1].w); - float shadowAmount1 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[1], shadowMapCoords); - shadowMapCoords = (vs_Input.ShadowMapCoords[2].xyz / vs_Input.ShadowMapCoords[2].w); - float shadowAmount2 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords); - - shadowAmount = mix(shadowAmount1, shadowAmount2, c1); - } - else if (c2 > 0.0 && c2 < 1.0) - { - // Sample 2 & 3 - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[2].xyz / vs_Input.ShadowMapCoords[2].w); - float shadowAmount2 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[2], shadowMapCoords); - shadowMapCoords = (vs_Input.ShadowMapCoords[3].xyz / vs_Input.ShadowMapCoords[3].w); - float shadowAmount3 = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[3], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[3], shadowMapCoords); - - shadowAmount = mix(shadowAmount2, shadowAmount3, c2); - } - else - { - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[CascadeIndex].xyz / vs_Input.ShadowMapCoords[CascadeIndex].w); - shadowAmount = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords); - } - } - else - { - vec3 shadowMapCoords = (vs_Input.ShadowMapCoords[CascadeIndex].xyz / vs_Input.ShadowMapCoords[CascadeIndex].w); - shadowAmount = u_SoftShadows ? PCSS_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords, u_LightSize) : HardShadows_DirectionalLight(u_ShadowMapTexture[CascadeIndex], shadowMapCoords); - } - - float NdotL = dot(m_Params.Normal, u_DirectionalLights.Direction); - NdotL = smoothstep(0.0, 0.4, NdotL + 0.2); - shadowAmount *= (NdotL * 1.0); + // directional light with with shadow + vec3 lightContribution = u_DirectionalLights.Multiplier > 0.0 ? Lighting(F0) * shadowFactor : vec3(0.0); vec3 iblContribution = IBL(F0, Lr) * u_IBLContribution; - vec3 lightContribution = u_DirectionalLights.Multiplier > 0.0f ? (Lighting(F0) * shadowAmount) : vec3(0.0f); color = vec4(lightContribution + iblContribution, 1.0); // Bloom float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); - o_BloomColor = vec4(0.0, 0.0, 0.0, 1.0); - if (brightness > u_BloomThreshold) - o_BloomColor = color; - - if (u_ShowCascades) - { - switch(CascadeIndex) - { - case 0: - color.rgb *= vec3(1.0f, 0.25f, 0.25f); - break; - case 1: - color.rgb *= vec3(0.25f, 1.0f, 0.25f); - break; - case 2: - color.rgb *= vec3(0.25f, 0.25f, 1.0f); - break; - case 3: - color.rgb *= vec3(1.0f, 1.0f, 0.25f); - break; - } - } + o_BloomColor = brightness > u_BloomThreshold ? color : vec4(0.0, 0.0, 0.0, 1.0); } diff --git a/Prism/src/Prism/Core/ImGui/ImGui.h b/Prism/src/Prism/Core/ImGui/ImGui.h index 68a1847..4f3b289 100644 --- a/Prism/src/Prism/Core/ImGui/ImGui.h +++ b/Prism/src/Prism/Core/ImGui/ImGui.h @@ -222,6 +222,27 @@ namespace Prism::UI { return modified; } + static bool PropertyColor(const char* label, glm::vec4& value) + { + bool modified = false; + + ImGui::Text("%s", label); + ImGui::NextColumn(); + ImGui::PushItemWidth(-1); + + s_IDBuffer[0] = '#'; + s_IDBuffer[1] = '#'; + memset(s_IDBuffer + 2, 0, 14); + snprintf(s_IDBuffer + 2, 14, "%x", s_Counter++); + if (ImGui::ColorEdit4(s_IDBuffer, glm::value_ptr(value))) + modified = true; + + ImGui::PopItemWidth(); + ImGui::NextColumn(); + + return modified; + } + static bool Property(const char* label, glm::vec3& value, const float delta = 0.1f) { bool modified = false; diff --git a/Prism/src/Prism/Renderer/FrameBuffer.h b/Prism/src/Prism/Renderer/FrameBuffer.h index 65899ed..ea03c8c 100644 --- a/Prism/src/Prism/Renderer/FrameBuffer.h +++ b/Prism/src/Prism/Renderer/FrameBuffer.h @@ -53,7 +53,7 @@ namespace Prism uint32_t Height = 720; glm::vec4 ClearColor; FramebufferAttachmentSpecification Attachments; - uint32_t Samples = 1; // multisampling + int Samples = 1; // multisampling // TODO: Temp, needs scale bool NoResize = false; diff --git a/Prism/src/Prism/Renderer/Material.h b/Prism/src/Prism/Renderer/Material.h index 27cd18d..7b73cad 100644 --- a/Prism/src/Prism/Renderer/Material.h +++ b/Prism/src/Prism/Renderer/Material.h @@ -158,7 +158,7 @@ namespace Prism void Set(const std::string& name, const T& value) { const auto decl = m_Material->FindUniformDeclaration(name); - if (!decl) PM_CORE_ERROR("Could not find uniform with name {0}", name); + if (!decl) PM_CORE_ERROR("Could not find uniform with name '{0}'", name); const auto& buffer = GetUniformBufferTarget(decl); buffer.Write(&value, decl->GetSize(), decl->GetOffset()); diff --git a/Prism/src/Prism/Renderer/SceneRenderer.cpp b/Prism/src/Prism/Renderer/SceneRenderer.cpp index 1a91905..880cbe8 100644 --- a/Prism/src/Prism/Renderer/SceneRenderer.cpp +++ b/Prism/src/Prism/Renderer/SceneRenderer.cpp @@ -42,24 +42,26 @@ namespace Prism Ref CompositePass; Ref BloomBlurPass[2]; Ref BloomBlendPass; - Ref LuminancePass; // Auto exposure - struct AutoExposure + struct AutoExposureData { + Ref LuminancePass; + float CurrentExposure = 1.0f; bool EnableAutoExposure = true; float Key = 0.18f; // middle gray - float AdaptationSpeed = 1.0f; // stops per second + float AdaptationSpeed = 5.0f; // stops per second + Timer ExposureTimer; float MaxExposure = 5.0f; - }AutoExposure; + }AutoExposureData; Ref ShadowMapShader, ShadowMapAnimShader; - Ref ShadowMapRenderPass[4]; + Ref ShadowMapRenderPass; float ShadowMapSize = 20.0f; float LightDistance = 0.1f; - glm::mat4 LightMatrices[4]; + glm::mat4 LightMatrices; glm::mat4 LightViewMatrix; float CascadeSplitLambda = 0.91f; glm::vec4 CascadeSplits; @@ -79,6 +81,11 @@ namespace Prism RendererID ShadowMapSampler; + bool ShadowEnabled = true; + float ShadowBias = 0.001f; + float ShadowIntensity = 0.0f; + int ShadowSoftness = 0; + struct DrawCommand { Ref mesh; @@ -90,11 +97,23 @@ namespace Prism std::vector ColliderDrawList; std::vector ShadowPassDrawList; - // Grid - Ref GridMaterial; Ref OutlineMaterial, OutlineAnimMaterial; Ref ColliderMaterial; + // Grid + struct GridData + { + Ref GridMaterial; + + int GridPlane = 0; + float GridScale = 1.0f; + glm::vec4 GridColorThin = glm::vec4(0.5f, 0.5f, 0.5f, 0.4f); + glm::vec4 GridColorThick = glm::vec4(0.5f, 0.5f, 0.5f, 0.6f); + glm::vec4 AxisColorX = glm::vec4(0.9f, 0.2f, 0.2f, 1.0f); + glm::vec4 AxisColorZ = glm::vec4(0.2f, 0.2f, 0.9f, 1.0f); + float FadeDistance = 500.0f; + }GridData; + SceneRendererOptions Options; }; @@ -114,92 +133,6 @@ namespace Prism static SceneRendererData s_Data; static SceneRendererStats s_Stats; - static void ComputeAutoExposure() - { - if (!s_Data.AutoExposure.EnableAutoExposure) - return; - - auto srcFB = s_Data.GeoPass->GetSpecification().TargetFramebuffer; - auto dstFB = s_Data.LuminancePass->GetSpecification().TargetFramebuffer; - if (!srcFB || !dstFB) - return; - - const uint32_t srcID = srcFB->GetColorAttachmentRendererID(); - const uint32_t dstID = dstFB->GetColorAttachmentRendererID(); - const uint32_t width = dstFB->GetWidth(); - const uint32_t height = dstFB->GetHeight(); - - Renderer::Submit([srcID, dstID, width, height, srcFB]() mutable - { - // Use framebuffer blit to resolve multisampled source into non-multisampled luminance target. - GLuint srcFBO = srcFB->GetRendererID(); - GLuint dstFBO = s_Data.LuminancePass->GetSpecification().TargetFramebuffer->GetRendererID(); - - // Bind read/draw FBOs and blit color attachment 0 - glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFBO); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFBO); - glReadBuffer(GL_COLOR_ATTACHMENT0); - glDrawBuffer(GL_COLOR_ATTACHMENT0); - - // Source size — try to use srcFB dimensions if available - int srcWidth = srcFB->GetWidth(); - int srcHeight = srcFB->GetHeight(); - - glBlitFramebuffer(0, 0, srcWidth, srcHeight, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - - // Unbind - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - // Generate mipmaps so the smallest mip is the average color - glGenerateTextureMipmap(dstID); - - // Determine highest mip level - int maxLevel = (int)std::floor(std::log2((float)std::max(width, height))); - if (maxLevel < 0) maxLevel = 0; - - float pixel[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - glGetTextureImage(dstID, maxLevel, GL_RGBA, GL_FLOAT, sizeof(pixel), pixel); - - // Sanitize pixel values (handle NaN/Inf or negative values coming from GPU) - for (int i = 0; i < 3; ++i) - { - if (!std::isfinite(pixel[i]) || pixel[i] < 0.0f) - pixel[i] = 0.0f; - } - - // Convert to luminance - float lum = 0.2126f * pixel[0] + 0.7152f * pixel[1] + 0.0722f * pixel[2]; - if (!std::isfinite(lum) || lum <= 0.0f) - lum = 1e-6f; // fallback minimum luminance - - // Compute desired exposure (simple key/avg approach) - const float key = s_Data.AutoExposure.Key; - constexpr float minLum = 1e-6f; - float desiredExposure = key / std::max(lum, minLum); - desiredExposure = std::clamp(desiredExposure, 0.0001f, s_Data.AutoExposure.MaxExposure); - - - // Adapt exposure over time (exponential) - const float dt = s_Data.AutoExposure.ExposureTimer.ElapsedMillis() / 1000.0f; - s_Data.AutoExposure.ExposureTimer.Reset(); - const float tau = s_Data.AutoExposure.AdaptationSpeed; - const float adaptFactor = 1.0f - std::exp(-tau * dt); - s_Data.AutoExposure.CurrentExposure = s_Data.AutoExposure.CurrentExposure + (desiredExposure - s_Data.AutoExposure.CurrentExposure) * adaptFactor; - s_Data.AutoExposure.CurrentExposure = std::clamp(s_Data.AutoExposure.CurrentExposure, 0.0001f, s_Data.AutoExposure.MaxExposure); - - // Write exposure directly into composite shader program uniform so the subsequent composite pass uses it - /* - if (const GLuint prog = s_Data.CompositeShader->GetRendererID()) - { - const GLint loc = glGetUniformLocation(prog, "u_Exposure"); - if (loc >= 0) - glProgramUniform1f(prog, loc, s_Data.CurrentExposure); - } - */ - }); - - } void SceneRenderer::Init() { @@ -250,23 +183,17 @@ namespace Prism RenderPassSpecification luminanceRenderPassSpec; luminanceRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(luminanceFramebufferSpec); - s_Data.LuminancePass = RenderPass::Create(luminanceRenderPassSpec); - - // Auto exposure defaults - s_Data.AutoExposure.CurrentExposure = 1.0f; - s_Data.AutoExposure.EnableAutoExposure = true; - s_Data.AutoExposure.Key = 0.18f; - s_Data.AutoExposure.AdaptationSpeed = 1.0f; - s_Data.AutoExposure.ExposureTimer.Reset(); + s_Data.AutoExposureData.LuminancePass = RenderPass::Create(luminanceRenderPassSpec); // Grid - const auto gridShader = Shader::Create("assets/shaders/Grid.glsl"); - s_Data.GridMaterial = MaterialInstance::Create(Material::Create(gridShader)); - constexpr float gridScale = 16.025f; - constexpr float gridSize = 0.025f; - s_Data.GridMaterial->Set("u_Scale", gridScale); - s_Data.GridMaterial->Set("u_Res", gridSize); - s_Data.GridMaterial->SetFlag(MaterialFlag::TwoSided, true); + // const auto gridShader = Shader::Create("assets/shaders/Grid.glsl"); + const auto gridShader = Shader::Create("assets/shaders/InfiniteGrid.glsl"); + s_Data.GridData.GridMaterial = MaterialInstance::Create(Material::Create(gridShader)); + // constexpr float gridScale = 16.025f; + // constexpr float gridSize = 0.025f; + // s_Data.GridMaterial->Set("u_Scale", gridScale); + // s_Data.GridMaterial->Set("u_Res", gridSize); + s_Data.GridData.GridMaterial->SetFlag(MaterialFlag::TwoSided, true); // outline const auto outlineShader = Shader::Create("assets/shaders/Outline.glsl"); @@ -293,13 +220,9 @@ namespace Prism shadowMapFramebufferSpec.ClearColor = { 0.0f, 0.0f, 0.0f, 0.0f }; shadowMapFramebufferSpec.NoResize = true; - // 4 cascades - for (int i = 0; i < 4; i++) - { RenderPassSpecification shadowMapRenderPassSpec; shadowMapRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(shadowMapFramebufferSpec); - s_Data.ShadowMapRenderPass[i] = RenderPass::Create(shadowMapRenderPassSpec); - } + s_Data.ShadowMapRenderPass = RenderPass::Create(shadowMapRenderPassSpec); Renderer::Submit([]() { @@ -318,8 +241,8 @@ namespace Prism { s_Data.GeoPass->GetSpecification().TargetFramebuffer->Resize(width, height); s_Data.CompositePass->GetSpecification().TargetFramebuffer->Resize(width, height); - if (s_Data.LuminancePass) - s_Data.LuminancePass->GetSpecification().TargetFramebuffer->Resize(width, height); + if (s_Data.AutoExposureData.LuminancePass) + s_Data.AutoExposureData.LuminancePass->GetSpecification().TargetFramebuffer->Resize(width, height); } void SceneRenderer::BeginScene(const Scene* scene, const SceneRendererCamera& camera) @@ -495,7 +418,7 @@ namespace Prism // Compute average luminance and update exposure (GPU-copy + mipmap -> read 1 texel) { Renderer::Submit([]() { s_Stats.AutoExposurePassTimer.Reset(); }); - ComputeAutoExposure(); + AutoExposurePass(); Renderer::Submit([] { s_Stats.AutoExposurePass = s_Stats.AutoExposurePassTimer.ElapsedMillis(); }); } @@ -503,7 +426,7 @@ namespace Prism Renderer::Submit([]() { s_Stats.CompositePassTimer.Reset(); }); CompositePass(); Renderer::Submit([] { s_Stats.CompositePass = s_Stats.CompositePassTimer.ElapsedMillis(); }); - BloomBlurPass(); + // BloomBlurPass(); } s_Data.DrawList.clear(); @@ -538,13 +461,15 @@ namespace Prism const auto& sceneCamera = s_Data.SceneData.SceneCamera; - const auto viewProjection = sceneCamera.Camera.GetProjectionMatrix() * sceneCamera.ViewMatrix; + const auto cameraProjection = sceneCamera.Camera.GetProjectionMatrix(); + const auto cameraView = sceneCamera.ViewMatrix; + const auto cameraViewProjection = cameraProjection * cameraView; const glm::vec3 cameraPosition = glm::inverse(s_Data.SceneData.SceneCamera.ViewMatrix)[3]; // TODO: Negate instead // Skybox auto skyboxShader = s_Data.SceneData.SkyboxMaterial->GetShader(); - s_Data.SceneData.SkyboxMaterial->Set("u_InverseVP", glm::inverse(viewProjection)); + s_Data.SceneData.SkyboxMaterial->Set("u_InverseVP", glm::inverse(cameraViewProjection)); s_Data.SceneData.SkyboxMaterial->Set("u_SkyIntensity", s_Data.SceneData.SceneEnvironmentIntensity); // s_Data.SceneInfo.EnvironmentIrradianceMap->Bind(0); Renderer::SubmitFullscreenQuad(s_Data.SceneData.SkyboxMaterial); @@ -556,22 +481,22 @@ namespace Prism for (auto& dc : s_Data.DrawList) { auto baseMaterial = dc.mesh->GetMaterial(); - baseMaterial->Set("u_ViewProjectionMatrix", viewProjection); + baseMaterial->Set("u_ViewProjectionMatrix", cameraViewProjection); baseMaterial->Set("u_ViewMatrix", sceneCamera.ViewMatrix); baseMaterial->Set("u_CameraPosition", cameraPosition); - baseMaterial->Set("u_LightMatrixCascade0", s_Data.LightMatrices[0]); - baseMaterial->Set("u_LightMatrixCascade1", s_Data.LightMatrices[1]); - baseMaterial->Set("u_LightMatrixCascade2", s_Data.LightMatrices[2]); - baseMaterial->Set("u_LightMatrixCascade3", s_Data.LightMatrices[3]); - baseMaterial->Set("u_ShowCascades", s_Data.ShowCascades); - baseMaterial->Set("u_LightView", s_Data.LightViewMatrix); - baseMaterial->Set("u_CascadeSplits", s_Data.CascadeSplits); - baseMaterial->Set("u_SoftShadows", s_Data.SoftShadows); - baseMaterial->Set("u_LightSize", s_Data.LightSize); - baseMaterial->Set("u_MaxShadowDistance", s_Data.MaxShadowDistance); - baseMaterial->Set("u_ShadowFade", s_Data.ShadowFade); - baseMaterial->Set("u_CascadeFading", s_Data.CascadeFading); - baseMaterial->Set("u_CascadeTransitionFade", s_Data.CascadeTransitionFade); + // baseMaterial->Set("u_LightMatrixCascade0", s_Data.LightMatrices[0]); + // baseMaterial->Set("u_LightMatrixCascade1", s_Data.LightMatrices[1]); + // baseMaterial->Set("u_LightMatrixCascade2", s_Data.LightMatrices[2]); + // baseMaterial->Set("u_LightMatrixCascade3", s_Data.LightMatrices[3]); + // baseMaterial->Set("u_ShowCascades", s_Data.ShowCascades); + // baseMaterial->Set("u_LightView", s_Data.LightViewMatrix); + // baseMaterial->Set("u_CascadeSplits", s_Data.CascadeSplits); + // baseMaterial->Set("u_SoftShadows", s_Data.SoftShadows); + // baseMaterial->Set("u_LightSize", s_Data.LightSize); + // baseMaterial->Set("u_MaxShadowDistance", s_Data.MaxShadowDistance); + // baseMaterial->Set("u_ShadowFade", s_Data.ShadowFade); + // baseMaterial->Set("u_CascadeFading", s_Data.CascadeFading); + // baseMaterial->Set("u_CascadeTransitionFade", s_Data.CascadeTransitionFade); baseMaterial->Set("u_IBLContribution", s_Data.SceneData.SceneEnvironmentIntensity); // Environment (TODO: don't do this per mesh) @@ -580,9 +505,30 @@ namespace Prism baseMaterial->Set("u_BRDFLUTTexture", s_Data.BRDFLUT); // Set lights (TODO: move to light environment and don't do per mesh) - auto directionalLight = s_Data.SceneData.SceneLightEnvironment.DirectionalLights[0]; - baseMaterial->Set("u_DirectionalLights", directionalLight); + baseMaterial->Set("u_DirectionalLights", s_Data.SceneData.SceneLightEnvironment.DirectionalLights[0]); + // baseMaterial->Set("u_DirectionalLights", s_Data.SceneData.ActiveLight); + + // shadow + baseMaterial->Set("u_LightSpaceMatrix", s_Data.LightMatrices); + baseMaterial->Set("u_ShadowEnabled", s_Data.ShadowEnabled); + baseMaterial->Set("u_ShadowBias", s_Data.ShadowBias); + baseMaterial->Set("u_ShadowIntensity", s_Data.ShadowIntensity); + + auto rd = baseMaterial->FindResourceDeclaration("u_ShadowMap"); + if (rd) + { + auto reg = rd->GetRegister(); + auto tex = s_Data.ShadowMapRenderPass->GetSpecification().TargetFramebuffer->GetDepthAttachmentRendererID(); + + Renderer::Submit([reg, tex]() mutable + { + glBindTextureUnit(reg, tex); + glBindSampler(reg++, s_Data.ShadowMapSampler); + }); + } + + /* auto rd = baseMaterial->FindResourceDeclaration("u_ShadowMapTexture"); if (rd) { @@ -609,6 +555,7 @@ namespace Prism glBindSampler(reg++, s_Data.ShadowMapSampler); }); } + */ constexpr auto overrideMaterial = nullptr; // dc.Material; @@ -626,32 +573,55 @@ namespace Prism for (auto& dc : s_Data.SelectedMeshDrawList) { auto baseMaterial = dc.mesh->GetMaterial(); - baseMaterial->Set("u_ViewProjectionMatrix", viewProjection); + baseMaterial->Set("u_ViewProjectionMatrix", cameraViewProjection); baseMaterial->Set("u_ViewMatrix", sceneCamera.ViewMatrix); baseMaterial->Set("u_CameraPosition", cameraPosition); - baseMaterial->Set("u_CascadeSplits", s_Data.CascadeSplits); - baseMaterial->Set("u_ShowCascades", s_Data.ShowCascades); - baseMaterial->Set("u_SoftShadows", s_Data.SoftShadows); - baseMaterial->Set("u_LightSize", s_Data.LightSize); - baseMaterial->Set("u_MaxShadowDistance", s_Data.MaxShadowDistance); - baseMaterial->Set("u_ShadowFade", s_Data.ShadowFade); - baseMaterial->Set("u_CascadeFading", s_Data.CascadeFading); - baseMaterial->Set("u_CascadeTransitionFade", s_Data.CascadeTransitionFade); + // baseMaterial->Set("u_CascadeSplits", s_Data.CascadeSplits); + // baseMaterial->Set("u_ShowCascades", s_Data.ShowCascades); + // baseMaterial->Set("u_SoftShadows", s_Data.SoftShadows); + // baseMaterial->Set("u_LightSize", s_Data.LightSize); + // baseMaterial->Set("u_MaxShadowDistance", s_Data.MaxShadowDistance); + // baseMaterial->Set("u_ShadowFade", s_Data.ShadowFade); + // baseMaterial->Set("u_CascadeFading", s_Data.CascadeFading); + // baseMaterial->Set("u_CascadeTransitionFade", s_Data.CascadeTransitionFade); baseMaterial->Set("u_IBLContribution", s_Data.SceneData.SceneEnvironmentIntensity); + // Environment (TODO: don't do this per mesh) baseMaterial->Set("u_EnvRadianceTex", s_Data.SceneData.SceneEnvironment->RadianceMap); baseMaterial->Set("u_EnvIrradianceTex", s_Data.SceneData.SceneEnvironment->IrradianceMap); baseMaterial->Set("u_BRDFLUTTexture", s_Data.BRDFLUT); - baseMaterial->Set("u_LightMatrixCascade0", s_Data.LightMatrices[0]); - baseMaterial->Set("u_LightMatrixCascade1", s_Data.LightMatrices[1]); - baseMaterial->Set("u_LightMatrixCascade2", s_Data.LightMatrices[2]); - baseMaterial->Set("u_LightMatrixCascade3", s_Data.LightMatrices[3]); + // baseMaterial->Set("u_LightMatrixCascade0", s_Data.LightMatrices[0]); + // baseMaterial->Set("u_LightMatrixCascade1", s_Data.LightMatrices[1]); + // baseMaterial->Set("u_LightMatrixCascade2", s_Data.LightMatrices[2]); + // baseMaterial->Set("u_LightMatrixCascade3", s_Data.LightMatrices[3]); // Set lights (TODO: move to light environment and don't do per mesh) baseMaterial->Set("u_DirectionalLights", s_Data.SceneData.SceneLightEnvironment.DirectionalLights[0]); + // baseMaterial->Set("u_DirectionalLights", s_Data.SceneData.ActiveLight); + + // shadow + baseMaterial->Set("u_LightSpaceMatrix", s_Data.LightMatrices); + baseMaterial->Set("u_ShadowEnabled", s_Data.ShadowEnabled); + baseMaterial->Set("u_ShadowBias", s_Data.ShadowBias); + baseMaterial->Set("u_ShadowIntensity", s_Data.ShadowIntensity); + + + auto rd = baseMaterial->FindResourceDeclaration("u_ShadowMap"); + if (rd) + { + auto reg = rd->GetRegister(); + auto tex = s_Data.ShadowMapRenderPass->GetSpecification().TargetFramebuffer->GetDepthAttachmentRendererID(); + + Renderer::Submit([reg, tex]() mutable + { + glBindTextureUnit(reg, tex); + glBindSampler(reg, s_Data.ShadowMapSampler); + }); + } + /* auto rd = baseMaterial->FindResourceDeclaration("u_ShadowMapTexture"); if (rd) { @@ -678,6 +648,7 @@ namespace Prism glBindSampler(reg++, s_Data.ShadowMapSampler); }); } + */ constexpr auto overrideMaterial = nullptr; // dc.Material; Renderer::SubmitMesh(dc.mesh, dc.Transform, overrideMaterial); @@ -698,8 +669,8 @@ namespace Prism }); // Draw outline here - s_Data.OutlineMaterial->Set("u_ViewProjection", viewProjection); - s_Data.OutlineAnimMaterial->Set("u_ViewProjection", viewProjection); + s_Data.OutlineMaterial->Set("u_ViewProjection", cameraViewProjection); + s_Data.OutlineAnimMaterial->Set("u_ViewProjection", cameraViewProjection); for (auto& dc : s_Data.SelectedMeshDrawList) { // Renderer::SubmitMesh(dc.mesh, dc.Transform, s_Data.OutlineMaterial); @@ -739,7 +710,7 @@ namespace Prism glDisable(GL_DEPTH_TEST); }); - s_Data.ColliderMaterial->Set("u_ViewProjection", viewProjection); + s_Data.ColliderMaterial->Set("u_ViewProjection", cameraViewProjection); for (auto& dc : s_Data.ColliderDrawList) { if (dc.mesh) @@ -772,13 +743,25 @@ namespace Prism const auto option = GetOptions(); if (option.ShowGrid) { - s_Data.GridMaterial->Set("u_ViewProjection", viewProjection); - Renderer::SubmitQuad(s_Data.GridMaterial, glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)) * glm::scale(glm::mat4(1.0f), glm::vec3(16.0f))); + s_Data.GridData.GridMaterial->Set("u_View", sceneCamera.ViewMatrix); + s_Data.GridData.GridMaterial->Set("u_Projection", cameraProjection); + s_Data.GridData.GridMaterial->Set("u_CameraPosition", cameraPosition); + + // grid config + s_Data.GridData.GridMaterial->Set("u_GridPlane", s_Data.GridData.GridPlane); + s_Data.GridData.GridMaterial->Set("u_GridScale", s_Data.GridData.GridScale); + s_Data.GridData.GridMaterial->Set("u_GridColorThin", s_Data.GridData.GridColorThin); + s_Data.GridData.GridMaterial->Set("u_GridColorThick", s_Data.GridData.GridColorThick); + s_Data.GridData.GridMaterial->Set("u_AxisColorX", s_Data.GridData.AxisColorX); + s_Data.GridData.GridMaterial->Set("u_AxisColorZ", s_Data.GridData.AxisColorZ); + s_Data.GridData.GridMaterial->Set("u_FadeDistance", s_Data.GridData.FadeDistance); + + Renderer::SubmitFullscreenQuad(s_Data.GridData.GridMaterial); } if (option.ShowBoundingBoxes) { - Renderer2D::BeginScene(viewProjection); + Renderer2D::BeginScene(cameraViewProjection); for (auto& dc : s_Data.DrawList) Renderer::DrawAABB(dc.mesh, dc.Transform); Renderer2D::EndScene(); @@ -787,13 +770,108 @@ namespace Prism Renderer::EndRenderPass(); } + void SceneRenderer::AutoExposurePass() + { + if (!s_Data.AutoExposureData.EnableAutoExposure) + return; + + auto srcFB = s_Data.GeoPass->GetSpecification().TargetFramebuffer; + auto dstFB = s_Data.AutoExposureData.LuminancePass->GetSpecification().TargetFramebuffer; + if (!srcFB || !dstFB) + return; + + const uint32_t dstID = dstFB->GetColorAttachmentRendererID(); + const uint32_t width = dstFB->GetWidth(); + const uint32_t height = dstFB->GetHeight(); + + Renderer::Submit([dstID, width, height, srcFB]() mutable + { + // Use framebuffer blit to resolve multisampled source into non-multisampled luminance target. + const GLuint srcFBO = srcFB->GetRendererID(); + const GLuint dstFBO = s_Data.AutoExposureData.LuminancePass->GetSpecification().TargetFramebuffer->GetRendererID(); + + // Bind read/draw FBOs and blit color attachment 0 + glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFBO); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFBO); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + + // Source size — try to use srcFB dimensions if available + const int srcWidth = srcFB->GetWidth(); + const int srcHeight = srcFB->GetHeight(); + + glBlitFramebuffer(0, 0, srcWidth, srcHeight, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // Unbind + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + // Generate mipmaps so the smallest mip is the average color + glGenerateTextureMipmap(dstID); + + // Determine highest mip level + int maxLevel = (int)std::floor(std::log2((float)std::max(width, height))); + if (maxLevel < 0) maxLevel = 0; + + + GLint levelWidth = 0, levelHeight = 0; + glGetTextureLevelParameteriv(dstID, maxLevel, GL_TEXTURE_WIDTH, &levelWidth); + glGetTextureLevelParameteriv(dstID, maxLevel, GL_TEXTURE_HEIGHT, &levelHeight); + + if (levelWidth == 0 || levelHeight == 0) return; + + const int bufSize = levelWidth * levelHeight * 4 * sizeof(float); + std::vector pixelData(bufSize / sizeof(float)); + + glGetTextureImage(dstID, maxLevel, GL_RGBA, GL_FLOAT, bufSize, pixelData.data()); + + // Sanitize pixel values (handle NaN/Inf or negative values coming from GPU) + for (int i = 0; i < 3; ++i) + { + if (!std::isfinite(pixelData[i]) || pixelData[i] < 0.0f) + pixelData[i] = 0.0f; + } + + // Convert to luminance + float lum = 0.2126f * pixelData[0] + 0.7152f * pixelData[1] + 0.0722f * pixelData[2]; + if (!std::isfinite(lum) || lum <= 0.0f) + lum = 1e-6f; // fallback minimum luminance + + // Compute desired exposure (simple key/avg approach) + const float key = s_Data.AutoExposureData.Key; + constexpr float minLum = 1e-6f; + float desiredExposure = key / std::max(lum, minLum); + desiredExposure = std::clamp(desiredExposure, 0.0001f, s_Data.AutoExposureData.MaxExposure); + + + // Adapt exposure over time (exponential) + const float dt = s_Data.AutoExposureData.ExposureTimer.ElapsedMillis() / 1000.0f; + s_Data.AutoExposureData.ExposureTimer.Reset(); + const float tau = s_Data.AutoExposureData.AdaptationSpeed; + const float adaptFactor = 1.0f - std::exp(-tau * dt); + s_Data.AutoExposureData.CurrentExposure = s_Data.AutoExposureData.CurrentExposure + (desiredExposure - s_Data.AutoExposureData.CurrentExposure) * adaptFactor; + s_Data.AutoExposureData.CurrentExposure = std::clamp(s_Data.AutoExposureData.CurrentExposure, 0.0001f, s_Data.AutoExposureData.MaxExposure); + + // Write exposure directly into composite shader program uniform so the subsequent composite pass uses it + /* + if (const GLuint prog = s_Data.CompositeShader->GetRendererID()) + { + const GLint loc = glGetUniformLocation(prog, "u_Exposure"); + if (loc >= 0) + glProgramUniform1f(prog, loc, s_Data.CurrentExposure); + } + */ + }); + + } + void SceneRenderer::CompositePass() { auto& compositeBuffer = s_Data.CompositePass->GetSpecification().TargetFramebuffer; Renderer::BeginRenderPass(s_Data.CompositePass); s_Data.CompositeShader->Bind(); - s_Data.CompositeShader->SetFloat("u_Exposure", s_Data.AutoExposure.EnableAutoExposure ? s_Data.AutoExposure.CurrentExposure : s_Data.SceneData.SceneCamera.Camera.GetExposure()); + s_Data.CompositeShader->SetFloat("u_Exposure", s_Data.AutoExposureData.EnableAutoExposure ? s_Data.AutoExposureData.CurrentExposure : s_Data.SceneData.SceneCamera.Camera.GetExposure()); s_Data.CompositeShader->SetInt("u_TextureSamples", s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetSpecification().Samples); // s_Data.CompositeShader->SetFloat2("u_ViewportSize", glm::vec2(compositeBuffer->GetWidth(), compositeBuffer->GetHeight())); // s_Data.CompositeShader->SetFloat2("u_FocusPoint", s_Data.FocusPoint); @@ -851,7 +929,7 @@ namespace Prism s_Data.BloomBlendShader->Bind(); auto & adad =s_Data; // s_Data.BloomBlendShader->SetFloat("u_Exposure", s_Data.SceneData.SceneCamera.Camera.GetExposure()); - s_Data.CompositeShader->SetFloat("u_Exposure", s_Data.AutoExposure.EnableAutoExposure ? s_Data.AutoExposure.CurrentExposure : s_Data.SceneData.SceneCamera.Camera.GetExposure()); + s_Data.CompositeShader->SetFloat("u_Exposure", s_Data.AutoExposureData.EnableAutoExposure ? s_Data.AutoExposureData.CurrentExposure : s_Data.SceneData.SceneCamera.Camera.GetExposure()); s_Data.BloomBlendShader->SetBool("u_EnableBloom", s_Data.EnableBloom); s_Data.CompositePass->GetSpecification().TargetFramebuffer->BindTexture(0); @@ -874,6 +952,7 @@ namespace Prism float SplitDepth; }; + static void CalculateCascades(CascadeData* cascades, const glm::vec3& lightDirection) { // FrustumBounds frustumBounds[3]; @@ -915,7 +994,7 @@ namespace Prism // cascadeSplits[3] = 1.0f; // Calculate orthographic projection matrix for each cascade - float lastSplitDist = 0.0; + float lastSplitDist = 1.0; for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { float splitDist = cascadeSplits[i]; @@ -1000,15 +1079,21 @@ namespace Prism for (int i = 0; i < 4; i++) { // Clear shadow maps - Renderer::BeginRenderPass(s_Data.ShadowMapRenderPass[i]); + Renderer::BeginRenderPass(s_Data.ShadowMapRenderPass); Renderer::EndRenderPass(); } return; } - CascadeData cascades[4]; - CalculateCascades(cascades, directionalLights[0].Direction); - s_Data.LightViewMatrix = cascades[0].View; + glm::vec3 lightDir = glm::normalize(directionalLights[0].Direction); // 光线方向(从光源指向场景) + glm::vec3 lightPos = lightDir * 10.0f; + + glm::mat4 lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + + float orthoSize = 100.0f; + float nearPlane = 0.01f, farPlane = 1000.0f; + glm::mat4 lightProjection = glm::ortho(-orthoSize, orthoSize, -orthoSize, orthoSize, nearPlane, farPlane); + glm::mat4 lightSpaceMatrix = lightProjection * lightView; Renderer::Submit([]() { @@ -1016,26 +1101,18 @@ namespace Prism glCullFace(GL_BACK); }); - for (int i = 0; i < 4; i++) { - s_Data.CascadeSplits[i] = cascades[i].SplitDepth; + Renderer::BeginRenderPass(s_Data.ShadowMapRenderPass); - Renderer::BeginRenderPass(s_Data.ShadowMapRenderPass[i]); - - glm::mat4 shadowMapVP = cascades[i].ViewProj; - // s_Data.ShadowMapShader->SetMat4("u_ViewProjection", shadowMapVP); - - static glm::mat4 scaleBiasMatrix = glm::scale(glm::mat4(1.0f), { 0.5f, 0.5f, 0.5f }) * glm::translate(glm::mat4(1.0f), { 1, 1, 1 }); - s_Data.LightMatrices[i] = scaleBiasMatrix * cascades[i].ViewProj; + s_Data.LightMatrices = lightSpaceMatrix; // Render entities for (auto& dc : s_Data.ShadowPassDrawList) { Ref shader = dc.mesh->IsAnimated() ? s_Data.ShadowMapAnimShader : s_Data.ShadowMapShader; - shader->SetMat4("u_ViewProjection", shadowMapVP); + shader->SetMat4("u_ViewProjection", lightSpaceMatrix); Renderer::SubmitMeshWithShader(dc.mesh, dc.Transform, shader); - // Renderer::SubmitMeshWithShader(dc.mesh, dc.Transform, s_Data.ShadowMapShader); } Renderer::EndRenderPass(); @@ -1046,40 +1123,41 @@ namespace Prism { ImGui::Begin("Scene Renderer"); - if (UI::BeginTreeNode("Shadows")) - { - UI::Property("Composite Pass time: %.2fs", s_Stats.CompositePass); - UI::Property("Geometry Pass time: %.2fs", s_Stats.GeometryPass); - UI::Property("Shadow Pass time: %.2fs", s_Stats.ShadowPass); + UI::Property("Composite Pass time", s_Stats.CompositePass); + UI::Property("Geometry Pass time", s_Stats.GeometryPass); + UI::Property("Shadow Pass time", s_Stats.ShadowPass); + UI::Property("AutoExposure Pass time", s_Stats.AutoExposurePass); + if (UI::BeginTreeNode("Grid Config", false)) + { UI::BeginPropertyGrid(); - UI::Property("Soft Shadows", s_Data.SoftShadows); - UI::Property("Light Size", s_Data.LightSize, 0.01f); - UI::Property("Max Shadow Distance", s_Data.MaxShadowDistance, 1.0f); - UI::Property("Shadow Fade", s_Data.ShadowFade, 5.0f); + UI::PropertySlider("GridPlane", s_Data.GridData.GridPlane, 0, 2); + UI::Property("GridScale", s_Data.GridData.GridScale); + UI::PropertyColor("GridColorThin", s_Data.GridData.GridColorThin); + UI::PropertyColor("GridColorThick", s_Data.GridData.GridColorThick); + UI::PropertyColor("AxisColorX", s_Data.GridData.AxisColorX); + UI::PropertyColor("AxisColorZ", s_Data.GridData.AxisColorZ); + UI::Property("FadeDistance ", s_Data.GridData.FadeDistance); UI::EndPropertyGrid(); - if (UI::BeginTreeNode("Cascade Settings")) - { - UI::BeginPropertyGrid(); - UI::Property("Show Cascades", s_Data.ShowCascades); - UI::Property("Cascade Fading", s_Data.CascadeFading); - UI::Property("Cascade Transition Fade", s_Data.CascadeTransitionFade, 0.05f, 0.0f, FLT_MAX); - UI::Property("Cascade Split", s_Data.CascadeSplitLambda, 0.01f); - UI::Property("CascadeNearPlaneOffset", s_Data.CascadeNearPlaneOffset, 0.1f, -FLT_MAX, 0.0f); - UI::Property("CascadeFarPlaneOffset", s_Data.CascadeFarPlaneOffset, 0.1f, 0.0f, FLT_MAX); - UI::EndPropertyGrid(); - UI::EndTreeNode(); - } + UI::EndTreeNode(); + } + + if (UI::BeginTreeNode("Shadows", false)) + { if (UI::BeginTreeNode("Shadow Map", false)) { - static int cascadeIndex = 0; - auto fb = s_Data.ShadowMapRenderPass[cascadeIndex]->GetSpecification().TargetFramebuffer; + + UI::BeginPropertyGrid(); + UI::Property("EnableMap", s_Data.ShadowEnabled); + UI::Property("ShadowBias", s_Data.ShadowBias, 0.01f); + UI::Property("ShadowSoftness", s_Data.ShadowSoftness); + UI::Property("ShadowIntensity", s_Data.ShadowIntensity, 0.01f); + UI::EndPropertyGrid(); + + auto fb = s_Data.ShadowMapRenderPass->GetSpecification().TargetFramebuffer; auto id = fb->GetDepthAttachmentRendererID(); float size = ImGui::GetContentRegionAvail().x; // (float)fb->GetWidth() * 0.5f, (float)fb->GetHeight() * 0.5f - UI::BeginPropertyGrid(); - UI::PropertySlider("Cascade Index", cascadeIndex, 0, 3); - UI::EndPropertyGrid(); ImGui::Image((ImTextureID)id, { size, size }, { 0, 1 }, { 1, 0 }); UI::EndTreeNode(); } @@ -1087,7 +1165,7 @@ namespace Prism UI::EndTreeNode(); } - if (UI::BeginTreeNode("Bloom")) + if (UI::BeginTreeNode("Bloom", false)) { UI::BeginPropertyGrid(); UI::Property("Bloom", s_Data.EnableBloom); @@ -1095,24 +1173,22 @@ namespace Prism UI::EndPropertyGrid(); auto fb = s_Data.BloomBlurPass[0]->GetSpecification().TargetFramebuffer; - auto id = fb->GetColorAttachmentRendererID(); + const auto id = fb->GetColorAttachmentRendererID(); - float size = ImGui::GetContentRegionAvail().x; // (float)fb->GetWidth() * 0.5f, (float)fb->GetHeight() * 0.5f + const float size = ImGui::GetContentRegionAvail().x; // (float)fb->GetWidth() * 0.5f, (float)fb->GetHeight() * 0.5f float w = size; float h = w / ((float)fb->GetWidth() / (float)fb->GetHeight()); ImGui::Image((ImTextureID)id, { w, h }, { 0, 1 }, { 1, 0 }); UI::EndTreeNode(); } - if (UI::BeginTreeNode("Auto Exposure")) + if (UI::BeginTreeNode("Auto Exposure", false)) { - UI::Property("Auto Exposure Pass time: %.2fs", s_Stats.AutoExposurePass); - UI::BeginPropertyGrid(); - UI::Property("Enable Auto Exposure", s_Data.AutoExposure.EnableAutoExposure); - UI::Property("Key (middle gray)", s_Data.AutoExposure.Key, 0.001f, 0.001f, 2.5f); - UI::Property("Adaptation Speed", s_Data.AutoExposure.AdaptationSpeed, 0.01f, 0.001f, 5.0f); - UI::Property("Current Exposure", s_Data.AutoExposure.CurrentExposure, 0.01f, 0.0f, 0.0f, true); + UI::Property("Enable Auto Exposure", s_Data.AutoExposureData.EnableAutoExposure); + UI::Property("Key (middle gray)", s_Data.AutoExposureData.Key, 0.001f, 0.001f, 2.5f); + UI::Property("Adaptation Speed", s_Data.AutoExposureData.AdaptationSpeed, 0.01f, 0.001f, 5.0f); + UI::Property("Current Exposure", s_Data.AutoExposureData.CurrentExposure, 0.01f, 0.0f, 0.0f, true); UI::EndPropertyGrid(); UI::EndTreeNode(); diff --git a/Prism/src/Prism/Renderer/SceneRenderer.h b/Prism/src/Prism/Renderer/SceneRenderer.h index 1e91147..ec41c7b 100644 --- a/Prism/src/Prism/Renderer/SceneRenderer.h +++ b/Prism/src/Prism/Renderer/SceneRenderer.h @@ -32,7 +32,6 @@ namespace Prism { public: static void Init(); - void InitAutoExposure(); static void SetViewportSize(uint32_t width, uint32_t height); @@ -62,6 +61,7 @@ namespace Prism static void OnImGuiRender(); private: static void FlushDrawList(); + static void AutoExposurePass(); static void GeometryPass(); static void CompositePass(); static void BloomBlurPass();