diff --git a/Engine b/Engine index a393409..5e5f135 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit a393409321b5138653632927c1656297f6899f07 +Subproject commit 5e5f135c66911433df155e4105eceadd0eae8658 diff --git a/Game/assets/shaders/gbuffer_voxel.frag b/Game/assets/shaders/gbuffer_voxel.frag new file mode 100644 index 0000000..6ef1d16 --- /dev/null +++ b/Game/assets/shaders/gbuffer_voxel.frag @@ -0,0 +1,103 @@ +#version 450 +#extension GL_GOOGLE_include_directive : require + +// GBuffer pass, voxel format: engine gbuffer.frag plus RT3 (voxel model) + +#include "sleak/frame.glsl" +#include "craft/shading_models.glsl" + +layout(location = 0) in vec3 fragWorldPos; +layout(location = 1) in vec3 fragWorldNorm; +layout(location = 2) in vec3 fragWorldTan; +layout(location = 3) in vec3 fragWorldBit; +layout(location = 4) in vec4 fragColor; +layout(location = 5) in vec2 fragUV; + +// -- Set 0: PBR material textures (bindings 0-5) -------------------- +layout(set = 0, binding = 0) uniform sampler2D texDiffuse; +layout(set = 0, binding = 1) uniform sampler2D texNormal; +layout(set = 0, binding = 2) uniform sampler2D texMetallic; +layout(set = 0, binding = 3) uniform sampler2D texRoughness; +layout(set = 0, binding = 4) uniform sampler2D texAO; +layout(set = 0, binding = 5) uniform sampler2D texEmissive; + +// -- Set 0: per-material scalar parameters (binding 6) -------------- +layout(set = 0, binding = 6) uniform MaterialParams { + vec4 albedoFactor; // xyz = base color tint, w = opacity + float metallicFactor; + float roughnessFactor; + float aoFactor; + float normalIntensity; + vec4 emissiveFactor; // xyz = emissive color tint, w = intensity + float tilingX; + float tilingY; + float offsetX; + float offsetY; + uint hasNormalMap; + uint hasMetallicMap; + uint hasRoughnessMap; + uint hasAOMap; + uint hasEmissiveMap; + float _pad0; + float _pad1; + float _pad2; +}; + +layout(location = 0) out vec4 outAlbedoAO; +layout(location = 1) out vec4 outNormalRough; +layout(location = 2) out vec4 outMetalEmit; +layout(location = 3) out vec4 outMotionModel; + +// Atlas texels below this alpha are cut out +const float CRAFT_ALPHA_CUTOFF = 0.5; +// Roughness floor that keeps the BRDF finite +const float CRAFT_MIN_ROUGHNESS = 0.04; + +void main() { + vec2 uv = fragUV * vec2(tilingX, tilingY) + vec2(offsetX, offsetY); + + // --- Base Colour / Albedo --- + vec4 albedoSample = texture(texDiffuse, uv) * albedoFactor; + if (albedoSample.a < CRAFT_ALPHA_CUTOFF) discard; + + // --- Normal (TBN basis) --- + vec3 N = normalize(fragWorldNorm); + if (hasNormalMap != 0u) { + vec3 tN = texture(texNormal, uv).xyz * 2.0 - 1.0; + tN.xy *= normalIntensity; + tN.z = sqrt(max(1.0 - dot(tN.xy, tN.xy), 0.0)); + vec3 T = normalize(fragWorldTan - dot(fragWorldTan, N) * N); + vec3 B = normalize(fragWorldBit - dot(fragWorldBit, N) * N - + dot(fragWorldBit, T) * T); + N = normalize(mat3(T, B, N) * tN); + } + + // --- Metallic --- + float metallic = hasMetallicMap != 0u + ? texture(texMetallic, uv).r * metallicFactor + : metallicFactor; + metallic = clamp(metallic, 0.0, 1.0); + + // --- Roughness --- + float roughness = hasRoughnessMap != 0u + ? texture(texRoughness, uv).r * roughnessFactor + : roughnessFactor; + roughness = clamp(roughness, CRAFT_MIN_ROUGHNESS, 1.0); + + // --- Ambient Occlusion (vertex AO in the red channel) --- + float ao = hasAOMap != 0u + ? texture(texAO, uv).r * aoFactor + : fragColor.r * aoFactor; + ao = clamp(ao, 0.0, 1.0); + + // --- Emissive (HDR in RT2.gba) --- + vec3 emissive = hasEmissiveMap != 0u + ? texture(texEmissive, uv).rgb * emissiveFactor.rgb * emissiveFactor.w + : emissiveFactor.rgb * emissiveFactor.w; + + outAlbedoAO = SleakFrameGuard(vec4(albedoSample.rgb, ao)); + outNormalRough = vec4(N * 0.5 + 0.5, roughness); + outMetalEmit = vec4(metallic, emissive); + outMotionModel = SleakEncodeMotionModel(vec2(0.0), 0.0, + CRAFT_SHADING_MODEL_VOXEL); +} diff --git a/Game/assets/shaders/gbuffer_voxel.frag.spv b/Game/assets/shaders/gbuffer_voxel.frag.spv new file mode 100644 index 0000000..e7c9917 Binary files /dev/null and b/Game/assets/shaders/gbuffer_voxel.frag.spv differ diff --git a/Game/assets/shaders/gbuffer_gl.frag b/Game/assets/shaders/gbuffer_voxel_gl.frag similarity index 71% rename from Game/assets/shaders/gbuffer_gl.frag rename to Game/assets/shaders/gbuffer_voxel_gl.frag index 1ab080a..ba7ed29 100644 --- a/Game/assets/shaders/gbuffer_gl.frag +++ b/Game/assets/shaders/gbuffer_voxel_gl.frag @@ -1,9 +1,7 @@ #version 450 core +#extension GL_GOOGLE_include_directive : require -// GBuffer geometry pass — outputs material properties to 3 render targets. -// RT0: AlbedoRGB + AO (RGBA8) -// RT1: WorldNormalXYZ + Rough (RGBA16F, normal packed to [0,1]) -// RT2: Metallic + EmissiveScale (RGBA8) +// GBuffer pass, OpenGL, voxel format: engine gbuffer_gl.frag plus RT3 in vec3 fragWorldPos; in vec3 fragWorldNorm; @@ -16,6 +14,7 @@ in vec4 fragShadowCoord; layout(location = 0) out vec4 outAlbedoAO; layout(location = 1) out vec4 outNormalRough; layout(location = 2) out vec4 outMetalEmit; +layout(location = 3) out vec4 outMotionModel; // Material textures (binding must match TEXTURE_SLOT defines) layout(binding = 0) uniform sampler2D diffuseTexture; @@ -52,11 +51,15 @@ layout(std140, binding = 1) uniform MaterialUBO { vec2 _pad1; }; +#include "sleak/frame.glsl" +#include "craft/shading_models.glsl" + void main() { vec2 uv = fragUV * matTiling + matOffset; // Albedo - vec4 albedo = (HasDiffuseMap != 0u) ? texture(diffuseTexture, uv) : matDiffuseColor; + vec4 albedo = (HasDiffuseMap != 0u) ? texture(diffuseTexture, uv) + : matDiffuseColor; if (albedo.a < matAlphaCutoff) discard; // Normal (world space) @@ -70,13 +73,19 @@ void main() { } // PBR scalars - float roughness = (HasRoughnessMap != 0u) ? texture(roughnessTexture, uv).r : matRoughness; - float metallic = (HasMetallicMap != 0u) ? texture(metallicTexture, uv).r : matMetallic; - float ao = (HasAOMap != 0u) ? texture(aoTexture, uv).r : (matAO * fragColor.r); - float emitScale = (HasEmissiveMap != 0u) ? length(texture(emissiveTexture, uv).rgb) : - length(matEmissiveColor) * matEmissiveIntensity; + float roughness = (HasRoughnessMap != 0u) ? texture(roughnessTexture, uv).r + : matRoughness; + float metallic = (HasMetallicMap != 0u) ? texture(metallicTexture, uv).r + : matMetallic; + float ao = (HasAOMap != 0u) ? texture(aoTexture, uv).r + : (matAO * fragColor.r); + float emitScale = (HasEmissiveMap != 0u) + ? length(texture(emissiveTexture, uv).rgb) + : length(matEmissiveColor) * matEmissiveIntensity; - outAlbedoAO = vec4(albedo.rgb, ao); + outAlbedoAO = SleakFrameGuard(vec4(albedo.rgb, ao)); outNormalRough = vec4(N * 0.5 + 0.5, roughness); // pack normal to [0,1] outMetalEmit = vec4(metallic, clamp(emitScale, 0.0, 1.0), 0.0, 1.0); + outMotionModel = SleakEncodeMotionModel(vec2(0.0), 0.0, + CRAFT_SHADING_MODEL_VOXEL); } diff --git a/Game/assets/shaders/gbuffer_gl.vert b/Game/assets/shaders/gbuffer_voxel_gl.vert similarity index 100% rename from Game/assets/shaders/gbuffer_gl.vert rename to Game/assets/shaders/gbuffer_voxel_gl.vert diff --git a/Game/assets/shaders/include/craft/shading_models.glsl b/Game/assets/shaders/include/craft/shading_models.glsl new file mode 100644 index 0000000..838c140 --- /dev/null +++ b/Game/assets/shaders/include/craft/shading_models.glsl @@ -0,0 +1,9 @@ +#ifndef CRAFT_SHADING_MODELS_GLSL +#define CRAFT_SHADING_MODELS_GLSL + +#include "sleak/gbuffer.glsl" + +// SleakCraft shading model ids in GBuffer RT3.w; 0 belongs to the engine +#define CRAFT_SHADING_MODEL_VOXEL 1u + +#endif diff --git a/Game/assets/shaders/lighting_pass_gl.frag b/Game/assets/shaders/lighting_pass_gl.frag deleted file mode 100644 index ebce548..0000000 --- a/Game/assets/shaders/lighting_pass_gl.frag +++ /dev/null @@ -1,310 +0,0 @@ -#version 450 core -#extension GL_GOOGLE_include_directive : require - -// Deferred lighting pass. -// Reads the 4 GBuffer textures (bound at units 8-11), -// reconstructs world position from depth via InvViewProj, -// and computes Cook-Torrance PBR lighting + PCF shadows. - -in vec2 fragUV; -out vec4 outColor; - -// GBuffer samplers (bound at texture units 8-12) -layout(binding = 8) uniform sampler2D gbAlbedoAO; -layout(binding = 9) uniform sampler2D gbNormalRough; -layout(binding = 10) uniform sampler2D gbMetalEmit; -layout(binding = 11) uniform sampler2D gbDepth; // world pos reconstructed from this - -// SSAO (bound at unit 13; blurred R8 AO — 1.0 means fully lit) -layout(binding = 13) uniform sampler2D gbSSAO; -uniform int uSSAOEnabled; - -// IBL (bound at units 14-16 by EnsureIBL/Bind) -layout(binding = 14) uniform samplerCube irradianceMap; -layout(binding = 15) uniform samplerCube prefilterMap; -layout(binding = 16) uniform sampler2D brdfLUT; - -// Shadow map (already bound at unit 3 by the shadow pass) -layout(binding = 3) uniform sampler2DShadow shadowMap; - -#include "sleak/frame.glsl" - -// ---- UBOs ---- -struct LightData { - vec3 Position; uint Type; - vec3 Direction; float Intensity; - vec3 Color; float Range; - float SpotInnerCos; float SpotOuterCos; - float AreaWidth; float AreaHeight; -}; - -layout(std140, binding = 2) uniform LightUBO { - vec3 CameraPos; - uint NumActiveLights; - vec3 AmbientColor; - float AmbientIntensity; - vec4 FogColor; // horizon - float FogStart; - float FogEnd; - float _lightPad0, _lightPad1; - LightData Lights[16]; - // Trailing block — appended after Lights[] so older shader copies that - // stop here still read a valid prefix. Keep field order in sync with - // LightCBData in ConstantBuffer.hpp. - vec4 FogColorZenith; // zenith - float HeightFogTop; - float HeightFogDensity; - float HeightFogFalloff; - float HeightFogEnabled; -}; - -layout(std140, binding = 5) uniform ShadowUBO { - mat4 ShadowLightVP; - float ShadowBias; - float ShadowStrength; - float ShadowTexelSize; - float ShadowLightSize; - uint PCSSEnabled; - uint ShadowMapEnabled; - float _shadowPad0, _shadowPad1; - mat4 NdcToShadow; // NDC -> shadow clip, CPU-composed (no shimmer) -}; - -layout(std140, binding = 6) uniform DeferredCB { - mat4 InvViewProj; - float ScreenWidth; - float ScreenHeight; - float NearPlane; - float FarPlane; -}; - -layout(std140, binding = 10) uniform IBLUBO { - uint IBLEnabled; - float IBLIntensity; - float MaxReflectionLOD; - uint _iblPad0; -}; - -// ---- Constants ---- -const float PI = 3.14159265359; - -// Poisson disk for PCF -const vec2 poissonDisk[16] = vec2[]( - vec2(-0.94201624, -0.39906216), vec2( 0.94558609, -0.76890725), - vec2(-0.09418410, -0.92938870), vec2( 0.34495938, 0.29387760), - vec2(-0.91588581, 0.45771432), vec2(-0.81544232, -0.87912464), - vec2(-0.38277543, 0.27676845), vec2( 0.97484398, 0.75648379), - vec2( 0.44323325, -0.97511554), vec2( 0.53742981, -0.47373420), - vec2(-0.26496911, -0.41893023), vec2( 0.79197514, 0.19090188), - vec2(-0.24188840, 0.99706507), vec2(-0.81409955, 0.91437590), - vec2( 0.19984126, 0.78641367), vec2( 0.14383161, -0.14100790) -); - -// ---- PBR helper functions ---- -float DistributionGGX(vec3 N, vec3 H, float roughness) { - float a = roughness * roughness; - float a2 = a * a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH * NdotH; - float denom = NdotH2 * (a2 - 1.0) + 1.0; - return a2 / max(PI * denom * denom, 1e-7); -} - -float GeometrySchlickGGX(float NdotV, float roughness) { - float r = roughness + 1.0; - float k = (r * r) / 8.0; - return NdotV / max(NdotV * (1.0 - k) + k, 1e-7); -} - -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); - return GeometrySchlickGGX(NdotV, roughness) * GeometrySchlickGGX(NdotL, roughness); -} - -vec3 FresnelSchlick(float cosTheta, vec3 F0) { - return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0); -} - -vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) { - float oneMinusRoughness = 1.0 - roughness; - return F0 + (max(vec3(oneMinusRoughness), F0) - F0) - * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} - -// ---- Shadow ---- -// World-space projection via the texel-snapped ShadowLightVP (the -// NdcToShadow composition shimmered under camera motion). -float CalcShadowPCF(vec3 worldPos, float NdotL) { - if (ShadowMapEnabled == 0u) return 1.0; - - vec4 sc = ShadowLightVP * vec4(worldPos, 1.0); - vec3 proj = sc.xyz / sc.w; - proj.xy = proj.xy * 0.5 + 0.5; - proj.z = SleakNdcToDepth(proj.z); - - if (proj.z > 1.0 || any(lessThan(proj.xy, vec2(0.0))) || any(greaterThan(proj.xy, vec2(1.0)))) - return 1.0; - - vec2 fade = smoothstep(vec2(0.0), vec2(0.05), proj.xy) - * smoothstep(vec2(0.0), vec2(0.05), 1.0 - proj.xy); - float edgeFade = fade.x * fade.y; - - float shadow = 0.0; - for (int i = 0; i < 16; ++i) { - shadow += texture(shadowMap, - vec3(proj.xy + poissonDisk[i] * ShadowTexelSize * 5.0, - proj.z - ShadowBias)); - } - shadow /= 16.0; - - // Fade shadow on surfaces facing away from light - float fadedShadow = shadow * smoothstep(0.0, 0.15, NdotL); - return mix(1.0, fadedShadow, ShadowStrength * edgeFade); -} - -// World position of a GBuffer texel from its depth -vec3 ReconstructWorldPos(vec2 uv, float depth) { - vec4 ndc = vec4(SleakUvToNdc(uv), SleakDepthToNdc(depth), 1.0); - vec4 world = InvViewProj * ndc; - return world.xyz / world.w; -} - -// ---- ACES tone mapping ---- -vec3 ACESFilm(vec3 x) { - return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0); -} - -// Sky-matched gradient fog + exponential height fog. View direction picks a -// blend between horizon and zenith colors so distant terrain dissolves into -// the same color the sky renders behind it. -vec3 ApplyFog(vec3 color, vec3 worldPos) { - if (FogEnd <= 0.0) return color; - vec3 toFrag = worldPos - CameraPos; - float dist = length(toFrag.xz); - float distFog = clamp((dist - FogStart) / max(FogEnd - FogStart, 1e-4), 0.0, 1.0); - float heightFog = 0.0; - if (HeightFogEnabled > 0.5) { - float h = max(HeightFogTop - worldPos.y, 0.0); - heightFog = clamp(HeightFogDensity * (1.0 - exp(-h * HeightFogFalloff)), - 0.0, 1.0); - } - float fogAmount = 1.0 - (1.0 - distFog) * (1.0 - heightFog); - vec3 viewDir = normalize(toFrag); - float t = smoothstep(0.0, 1.0, clamp(viewDir.y * 0.5 + 0.5, 0.0, 1.0)); - vec3 fogColor = mix(FogColor.rgb, FogColorZenith.rgb, t); - return mix(color, fogColor, fogAmount); -} - -void main() { - // ---- Sample GBuffer ---- - vec4 albedoAO = texture(gbAlbedoAO, fragUV); - vec4 normalRough = texture(gbNormalRough, fragUV); - vec4 metalEmit = texture(gbMetalEmit, fragUV); - float depth = texture(gbDepth, fragUV).r; - - // Sky pixel — skip lighting (also leaves depth=1.0 for skybox pass) - if (depth >= 1.0) discard; - - // Write GBuffer depth to the default FBO depth buffer so that the - // forward transparent pass (skybox, water) has correct depth for testing. - gl_FragDepth = depth; - - vec3 albedo = albedoAO.rgb; - float ao = albedoAO.a; - // Multiply per-vertex AO by screen-space AO. Remap SSAO result from - // [0,1] to [0.5,1] so it can at most halve ambient — prevents shadowed - // block sides from collapsing to pitch black when the shadow map has - // already zeroed out direct light. - if (uSSAOEnabled != 0) { - float ssao = texture(gbSSAO, fragUV).r; - ao *= 0.5 + 0.5 * ssao; - } - vec3 N = normalize(normalRough.rgb * 2.0 - 1.0); // unpack from [0,1] - float roughness = normalRough.a; - float metallic = metalEmit.r; - float emitScale = metalEmit.g; - - // World position reconstructed from depth + InvViewProj (no worldpos RT). - vec3 worldPos = ReconstructWorldPos(fragUV, depth); - vec4 ndcPos = vec4(fragUV * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); - - // ---- Lighting accumulation (matches forward shader style) ---- - vec3 totalDiffuse = vec3(0.0); - - for (uint i = 0u; i < NumActiveLights; ++i) { - LightData light = Lights[i]; - - vec3 L; - float attenuation = 1.0; - - if (light.Type == 0u) { - // Directional - L = normalize(-light.Direction); - } else if (light.Type == 1u) { - // Point - vec3 toLight = light.Position - worldPos; - float dist = length(toLight); - L = toLight / dist; - attenuation = 1.0 / (1.0 + dist * dist / max(light.Range * light.Range, 1e-4)); - } else { - continue; - } - - // Wrap diffuse (matches forward shader 0.85 wrap) - float rawNdotL = dot(N, L); - float NdotL = clamp(rawNdotL * 0.85 + 0.15, 0.0, 1.0); - - float shadow = 1.0; - if (light.Type == 0u) { - shadow = CalcShadowPCF(worldPos, max(rawNdotL, 0.0)); - // Fade shadow for back-facing surfaces (prevents cave light leak) - shadow *= smoothstep(0.0, 0.15, rawNdotL); - } - - totalDiffuse += light.Color * light.Intensity * attenuation * NdotL * shadow; - } - - // ---- Ambient + AO ---- - // Hemisphere ambient is the base. With IBL enabled we ADD reflected - // environmental light on top — additive form keeps the scene from going - // dim when the source skybox is LDR (low irradiance dynamic range). - vec3 V = normalize(CameraPos - worldPos); - vec3 F0 = mix(vec3(0.04), albedo, metallic); - vec3 skyAmbient = AmbientColor * AmbientIntensity; - vec3 groundAmbient = skyAmbient * vec3(0.55, 0.50, 0.45); - float hemisphere = N.y * 0.5 + 0.5; - vec3 hemAmbient = mix(groundAmbient, skyAmbient, hemisphere); - vec3 ambient = albedo * hemAmbient; - - if (IBLEnabled != 0u) { - float NdotV_ibl = max(dot(N, V), 0.0); - vec3 kS_ibl = FresnelSchlickRoughness(NdotV_ibl, F0, roughness); - vec3 kD_ibl = (1.0 - kS_ibl) * (1.0 - metallic); - vec3 irradiance = texture(irradianceMap, N).rgb; - vec3 diffuseIBL = irradiance * albedo; - vec3 R = reflect(-V, N); - vec3 prefilteredColor= textureLod(prefilterMap, R, - roughness * MaxReflectionLOD).rgb; - vec2 envBRDF = texture(brdfLUT, - vec2(NdotV_ibl, roughness)).rg; - vec3 specularIBL = prefilteredColor - * (kS_ibl * envBRDF.x + envBRDF.y); - ambient += (kD_ibl * diffuseIBL + specularIBL) * IBLIntensity; - } - ambient *= ao; - - // Diffuse term carries albedo here (totalDiffuse is light radiance only). - vec3 color = ambient + albedo * totalDiffuse; - - // ---- Emissive ---- - color += albedo * emitScale; - - // ---- Tone mapping ---- - color = ACESFilm(color); - - // ---- Fog ---- - color = ApplyFog(color, worldPos); - - outColor = SleakFrameGuard(vec4(color, 1.0)); -} diff --git a/Game/assets/shaders/lighting_pass_gl.vert b/Game/assets/shaders/lighting_pass_gl.vert deleted file mode 100644 index 9860cad..0000000 --- a/Game/assets/shaders/lighting_pass_gl.vert +++ /dev/null @@ -1,18 +0,0 @@ -#version 450 core - -// Fullscreen triangle — no vertex buffer required. -// Call glDrawArrays(GL_TRIANGLES, 0, 3) with an empty VAO. -out vec2 fragUV; - -void main() { - // Vertex positions for a triangle that covers the entire screen: - // 0: (-1,-1) 1: (3,-1) 2: (-1, 3) - vec2 positions[3] = vec2[3]( - vec2(-1.0, -1.0), - vec2( 3.0, -1.0), - vec2(-1.0, 3.0) - ); - vec2 pos = positions[gl_VertexID]; - gl_Position = vec4(pos, 0.0, 1.0); - fragUV = pos * 0.5 + 0.5; -} diff --git a/Game/assets/shaders/lighting_voxel.frag b/Game/assets/shaders/lighting_voxel.frag new file mode 100644 index 0000000..d2b2fc9 --- /dev/null +++ b/Game/assets/shaders/lighting_voxel.frag @@ -0,0 +1,145 @@ +#version 450 +#extension GL_GOOGLE_include_directive : require + +// SleakCraft deferred lighting, Vulkan: voxel pixels get CraftShadeVoxel +#include "sleak/lighting_default.glsl" +#include "craft/shading_models.glsl" + +// Wrap lighting: N.L * scale + offset +const float CRAFT_WRAP_SCALE = 0.85; +const float CRAFT_WRAP_OFFSET = 0.15; +// N.L below which back faces get no direct light +const float CRAFT_SHADOW_FACE_FADE = 0.15; +// Ground bounce relative to the sky ambient +const vec3 CRAFT_GROUND_TINT = vec3(0.55, 0.50, 0.45); +// SSAO can at most halve the ambient term +const float CRAFT_SSAO_FLOOR = 0.5; +// Upper HDR clamp that keeps NaNs out of the bloom chain +const float CRAFT_MAX_HDR = 1000.0; + +/// Voxel lighting of one non-sky pixel: wrap diffuse sun with PCF shadow, +/// hemisphere ambient, extra lights, emissive, fog. Linear HDR. +vec3 CraftShadeVoxel(ivec2 px, vec2 uv, float depth) { + vec3 worldPos = SleakReconstructWorldPos(uv, depth); + vec4 albedoAO = texelFetch(gAlbedoAO, px, 0); + vec4 normalRg = texelFetch(gNormalRough, px, 0); + vec4 metalEmit = texelFetch(gMetalEmit, px, 0); + + vec3 albedo = albedoAO.rgb; + float bakedAO = albedoAO.a; + float ssao = texture(gSSAO, uv).r; + float ao = bakedAO * ssao; + vec3 N = normalize(normalRg.rgb * 2.0 - 1.0); + float roughness = normalRg.a; + float metallic = metalEmit.r; + vec3 emissive = metalEmit.gba; + + vec3 V = normalize(uCameraPos.xyz - worldPos); + float NdotV = max(dot(N, V), 0.0); + + vec3 F0 = mix(SLEAK_DIELECTRIC_F0, albedo, metallic); + + vec3 Lo = vec3(0.0); + vec3 ambient; + if (iblEnabled != 0u) { + // Cook-Torrance sun + vec3 L = normalize(-uLightDir.xyz); + vec3 H = normalize(V + L); + float NdotL = max(dot(N, L), 0.0); + + if (NdotL > 0.0) { + float D = SleakDistributionGGX(N, H, roughness); + float G = SleakGeometrySmith(N, V, L, roughness); + vec3 F = SleakFresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 kS = F; + vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic); + + vec3 specular = (D * G * F) / + (4.0 * NdotV * NdotL + SLEAK_BRDF_EPSILON); + + float shadow = SleakCalcShadow(worldPos, N); + shadow *= smoothstep(0.0, CRAFT_SHADOW_FACE_FADE, dot(N, L)); + + vec3 lightColor = uLightColor.rgb * uLightColor.a; + Lo = (kD * albedo / SLEAK_PI + specular) * lightColor * NdotL * + shadow; + } + + // Extra directional lights, no shadows + for (uint li = 0u; li < uNumExtraLights; ++li) { + vec3 Lx = normalize(-uExtraDir[li].xyz); + vec3 Hx = normalize(V + Lx); + float NdotLx = max(dot(N, Lx), 0.0); + if (NdotLx > 0.0) { + float Dx = SleakDistributionGGX(N, Hx, roughness); + float Gx = SleakGeometrySmith(N, V, Lx, roughness); + vec3 Fx = SleakFresnelSchlick(max(dot(Hx, V), 0.0), F0); + vec3 kSx = Fx; + vec3 kDx = (vec3(1.0) - kSx) * (1.0 - metallic); + vec3 specx = (Dx * Gx * Fx) / + (4.0 * NdotV * NdotLx + SLEAK_BRDF_EPSILON); + vec3 lightColorx = uExtraColor[li].rgb * uExtraColor[li].a; + Lo += (kDx * albedo / SLEAK_PI + specx) * lightColorx * NdotLx; + } + } + + // Split-sum IBL + vec3 F_amb = SleakFresnelSchlickRoughness(NdotV, F0, roughness); + vec3 kS_amb = F_amb; + vec3 kD_amb = (1.0 - kS_amb) * (1.0 - metallic); + + vec3 irradiance = texture(iblIrradiance, N).rgb; + vec3 diffuse_ibl = kD_amb * irradiance * albedo; + + vec3 R = reflect(-V, N); + vec3 prefilteredColor = + textureLod(iblPrefilter, R, roughness * maxReflectionLOD).rgb; + vec2 brdf = texture(iblBrdfLUT, vec2(NdotV, roughness)).rg; + vec3 specular_ibl = prefilteredColor * (F_amb * brdf.x + brdf.y); + + ambient = (diffuse_ibl + specular_ibl) * ao * iblIntensity; + } else { + // Wrap diffuse sun and hemisphere ambient + vec3 skyAmbient = uAmbient.rgb * uAmbient.a; + vec3 groundAmbient = skyAmbient * CRAFT_GROUND_TINT; + float hemisphere = N.y * 0.5 + 0.5; + vec3 Ldir = normalize(-uLightDir.xyz); + float rawNdL = dot(N, Ldir); + float wrapNdL = + clamp(rawNdL * CRAFT_WRAP_SCALE + CRAFT_WRAP_OFFSET, 0.0, 1.0); + float shad = 0.0; + if (rawNdL > -CRAFT_SHADOW_FACE_FADE) + shad = SleakCalcShadow(worldPos, N) * + smoothstep(-CRAFT_SHADOW_FACE_FADE, 0.0, rawNdL); + vec3 direct = uLightColor.rgb * uLightColor.a * wrapNdL * shad; + for (uint li = 0u; li < uNumExtraLights; ++li) { + vec3 Lx = normalize(-uExtraDir[li].xyz); + float NdLx = max(dot(N, Lx), 0.0); + direct += uExtraColor[li].rgb * uExtraColor[li].a * NdLx; + } + float aoGL = bakedAO * (CRAFT_SSAO_FLOOR + CRAFT_SSAO_FLOOR * ssao); + ambient = albedo * mix(groundAmbient, skyAmbient, hemisphere) * aoGL; + Lo = albedo * direct; + } + + vec3 color = ambient + Lo + emissive; + + // Fog in linear HDR; the composite pass tonemaps + color = SleakApplyFog(color, worldPos); + return clamp(color, vec3(0.0), vec3(CRAFT_MAX_HDR)); +} + +void main() { + ivec2 px = ivec2(gl_FragCoord.xy); + float depth = texelFetch(gDepth, px, 0).r; + if (depth >= 1.0) { outColor = vec4(0.0, 0.0, 0.0, 1.0); return; } + + vec3 color; + if (SleakShadingModel(SleakReadMotionModel(px)) == + CRAFT_SHADING_MODEL_VOXEL) + color = CraftShadeVoxel(px, fragUV, depth); + else + color = SleakShadeDefault(px, fragUV, depth); + outColor = SleakFrameGuard(vec4(color, 1.0)); +} diff --git a/Game/assets/shaders/lighting_voxel.frag.spv b/Game/assets/shaders/lighting_voxel.frag.spv new file mode 100644 index 0000000..256088f Binary files /dev/null and b/Game/assets/shaders/lighting_voxel.frag.spv differ diff --git a/Game/assets/shaders/lighting_voxel_gl.frag b/Game/assets/shaders/lighting_voxel_gl.frag new file mode 100644 index 0000000..8002a5d --- /dev/null +++ b/Game/assets/shaders/lighting_voxel_gl.frag @@ -0,0 +1,123 @@ +#version 450 core +#extension GL_GOOGLE_include_directive : require + +// SleakCraft deferred lighting, OpenGL: voxel pixels get CraftShadeVoxel +#include "sleak/lighting_default.glsl" +#include "craft/shading_models.glsl" + +// Wrap lighting: N.L * scale + offset +const float CRAFT_WRAP_SCALE = 0.85; +const float CRAFT_WRAP_OFFSET = 0.15; +// N.L below which back faces get no direct light +const float CRAFT_SHADOW_FACE_FADE = 0.15; +// Ground bounce relative to the sky ambient +const vec3 CRAFT_GROUND_TINT = vec3(0.55, 0.50, 0.45); +// SSAO can at most halve the ambient term +const float CRAFT_SSAO_FLOOR = 0.5; +// Keeps the light range away from a zero divisor +const float CRAFT_RANGE_EPSILON = 1e-4; +// Light types (LightType in Light.hpp) +const uint CRAFT_LIGHT_DIRECTIONAL = 0u; +const uint CRAFT_LIGHT_POINT = 1u; + +/// Voxel lighting of one non-sky pixel: every light in LightUBO (PCF for +/// directional), hemisphere ambient plus IBL, emissive, ACES, then fog. +vec3 CraftShadeVoxel(ivec2 px, vec2 uv, float depth) { + vec4 albedoAO = texture(gAlbedoAO, uv); + vec4 normalRough = texture(gNormalRough, uv); + vec4 metalEmit = texture(gMetalEmit, uv); + + vec3 albedo = albedoAO.rgb; + float ao = albedoAO.a; + if (SleakSSAOEnabled()) { + float ssao = texture(gSSAO, uv).r; + ao *= CRAFT_SSAO_FLOOR + CRAFT_SSAO_FLOOR * ssao; + } + vec3 N = normalize(normalRough.rgb * 2.0 - 1.0); + float roughness = normalRough.a; + float metallic = metalEmit.r; + float emitScale = metalEmit.g; + + vec3 worldPos = SleakReconstructWorldPos(uv, depth); + + vec3 totalDiffuse = vec3(0.0); + for (uint i = 0u; i < NumActiveLights; ++i) { + SleakLightData light = Lights[i]; + + vec3 L; + float attenuation = 1.0; + + if (light.Type == CRAFT_LIGHT_DIRECTIONAL) { + L = normalize(-light.Direction); + } else if (light.Type == CRAFT_LIGHT_POINT) { + vec3 toLight = light.Position - worldPos; + float dist = length(toLight); + L = toLight / dist; + attenuation = 1.0 / (1.0 + dist * dist / + max(light.Range * light.Range, + CRAFT_RANGE_EPSILON)); + } else { + continue; + } + + float rawNdotL = dot(N, L); + float NdotL = clamp(rawNdotL * CRAFT_WRAP_SCALE + CRAFT_WRAP_OFFSET, + 0.0, 1.0); + + float shadow = 1.0; + if (light.Type == CRAFT_LIGHT_DIRECTIONAL) { + shadow = SleakCalcShadowPCF(worldPos, max(rawNdotL, 0.0)); + shadow *= smoothstep(0.0, CRAFT_SHADOW_FACE_FADE, rawNdotL); + } + + totalDiffuse += + light.Color * light.Intensity * attenuation * NdotL * shadow; + } + + // Hemisphere ambient, plus reflected environment light with IBL + vec3 V = normalize(CameraPos - worldPos); + vec3 F0 = mix(SLEAK_DIELECTRIC_F0, albedo, metallic); + vec3 skyAmbient = AmbientColor * AmbientIntensity; + vec3 groundAmbient = skyAmbient * CRAFT_GROUND_TINT; + float hemisphere = N.y * 0.5 + 0.5; + vec3 hemAmbient = mix(groundAmbient, skyAmbient, hemisphere); + vec3 ambient = albedo * hemAmbient; + + if (iblEnabled != 0u) { + float NdotV_ibl = max(dot(N, V), 0.0); + vec3 kS_ibl = + SleakFresnelSchlickRoughness(NdotV_ibl, F0, roughness); + vec3 kD_ibl = (1.0 - kS_ibl) * (1.0 - metallic); + vec3 irradiance = texture(iblIrradiance, N).rgb; + vec3 diffuseIBL = irradiance * albedo; + vec3 R = reflect(-V, N); + vec3 prefilteredColor= textureLod(iblPrefilter, R, + roughness * maxReflectionLOD).rgb; + vec2 envBRDF = texture(iblBrdfLUT, + vec2(NdotV_ibl, roughness)).rg; + vec3 specularIBL = prefilteredColor + * (kS_ibl * envBRDF.x + envBRDF.y); + ambient += (kD_ibl * diffuseIBL + specularIBL) * iblIntensity; + } + ambient *= ao; + + vec3 color = ambient + albedo * totalDiffuse; + color += albedo * emitScale; + color = SleakACESFilm(color); + return SleakApplyFog(color, worldPos); +} + +void main() { + float depth = texture(gDepth, fragUV).r; + if (depth >= 1.0) discard; + gl_FragDepth = depth; + + ivec2 px = ivec2(gl_FragCoord.xy); + vec3 color; + if (SleakShadingModel(SleakReadMotionModel(px)) == + CRAFT_SHADING_MODEL_VOXEL) + color = CraftShadeVoxel(px, fragUV, depth); + else + color = SleakShadeDefault(px, fragUV, depth); + outColor = SleakFrameGuard(vec4(color, 1.0)); +} diff --git a/Game/assets/shaders/shadow_depth_voxel_gl.vert b/Game/assets/shaders/shadow_depth_voxel_gl.vert new file mode 100644 index 0000000..6701573 --- /dev/null +++ b/Game/assets/shaders/shadow_depth_voxel_gl.vert @@ -0,0 +1,21 @@ +#version 450 core +#extension GL_GOOGLE_include_directive : require + +// Shadow depth pass, OpenGL, voxel format: position only + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec4 inColor; +layout(location = 3) in vec2 inUV; + +// LightVP * World in WVP during the shadow pass +layout(std140, binding = 0) uniform TransformUBO { + mat4 WVP; + mat4 World; +}; + +#include "sleak/frame.glsl" + +void main() { + gl_Position = SleakFrameGuardClip(WVP * vec4(inPosition, 1.0)); +} diff --git a/Game/include/World/VoxelVertex.hpp b/Game/include/World/VoxelVertex.hpp index afe423b..b97f61c 100644 --- a/Game/include/World/VoxelVertex.hpp +++ b/Game/include/World/VoxelVertex.hpp @@ -104,6 +104,8 @@ inline Sleak::VertexFormatHandle GetVoxelVertexFormat() { desc.gbufferShaderStem = "gbuffer_voxel"; desc.transparentShaderStem = "water_shader"; desc.transparentReadsScene = true; + // Writes RT3 with CRAFT_SHADING_MODEL_VOXEL + desc.gbufferFragmentStem = "gbuffer_voxel"; return Sleak::VertexFormatRegistry::Register(desc); }(); return handle; diff --git a/Game/src/Game.cpp b/Game/src/Game.cpp index bf699b0..be89209 100644 --- a/Game/src/Game.cpp +++ b/Game/src/Game.cpp @@ -8,6 +8,13 @@ #include "World/VoxelVertex.hpp" #include +namespace { + +/// Deferred lighting stem that shades the voxel GBuffer stem's output. +constexpr const char* VOXEL_LIGHTING_STEM = "lighting_voxel"; + +} // namespace + Game::Game() { bIsGameRunning = true; } @@ -19,6 +26,7 @@ Game::~Game() { bool Game::Initialize() { // Register the voxel vertex layout before any chunk mesh is created GetVoxelVertexFormat(); + Sleak::Application::GetInstance()->SetLightingShader(VOXEL_LIGHTING_STEM); Sleak::Debug::ShaderHotReload::AddSourceDirectory(SLEAKCRAFT_SHADER_DIR); m_menuScene = new MainMenuScene("MainMenuScene");