From a96e9a82978a5fd9ad55c1f1436051a1516c3943 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 16:13:16 +0300 Subject: [PATCH 1/5] build: pin Engine to feat/format-stems --- Engine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine b/Engine index a393409..16459d9 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit a393409321b5138653632927c1656297f6899f07 +Subproject commit 16459d9e9518bb977a5fa26ea36379ae2a917d6b From a46c1b3492d6b76c6de3ef38550eab70132c282b Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 13:31:50 +0300 Subject: [PATCH 2/5] shaders: voxel GL programs per pass --- Game/assets/shaders/gbuffer_gl.frag | 82 ----- ...{gbuffer_gl.vert => gbuffer_voxel_gl.vert} | 0 Game/assets/shaders/lighting_pass_gl.frag | 310 ------------------ Game/assets/shaders/lighting_pass_gl.vert | 18 - .../assets/shaders/shadow_depth_voxel_gl.vert | 21 ++ 5 files changed, 21 insertions(+), 410 deletions(-) delete mode 100644 Game/assets/shaders/gbuffer_gl.frag rename Game/assets/shaders/{gbuffer_gl.vert => gbuffer_voxel_gl.vert} (100%) delete mode 100644 Game/assets/shaders/lighting_pass_gl.frag delete mode 100644 Game/assets/shaders/lighting_pass_gl.vert create mode 100644 Game/assets/shaders/shadow_depth_voxel_gl.vert diff --git a/Game/assets/shaders/gbuffer_gl.frag b/Game/assets/shaders/gbuffer_gl.frag deleted file mode 100644 index 1ab080a..0000000 --- a/Game/assets/shaders/gbuffer_gl.frag +++ /dev/null @@ -1,82 +0,0 @@ -#version 450 core - -// 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) - -in vec3 fragWorldPos; -in vec3 fragWorldNorm; -in vec3 fragWorldTan; -in vec3 fragWorldBit; -in vec4 fragColor; -in vec2 fragUV; -in vec4 fragShadowCoord; - -layout(location = 0) out vec4 outAlbedoAO; -layout(location = 1) out vec4 outNormalRough; -layout(location = 2) out vec4 outMetalEmit; - -// Material textures (binding must match TEXTURE_SLOT defines) -layout(binding = 0) uniform sampler2D diffuseTexture; -layout(binding = 1) uniform sampler2D normalTexture; -layout(binding = 2) uniform sampler2D specularTexture; -layout(binding = 3) uniform sampler2D roughnessTexture; -layout(binding = 4) uniform sampler2D metallicTexture; -layout(binding = 5) uniform sampler2D aoTexture; -layout(binding = 6) uniform sampler2D emissiveTexture; - -// Material UBO (binding 1) - matches MaterialGPUData layout -layout(std140, binding = 1) uniform MaterialUBO { - uint HasDiffuseMap; - uint HasNormalMap; - uint HasSpecularMap; - uint HasRoughnessMap; - uint HasMetallicMap; - uint HasAOMap; - uint HasEmissiveMap; - uint _pad0; - vec4 matDiffuseColor; - vec3 matSpecularColor; - float matShininess; - vec3 matEmissiveColor; - float matEmissiveIntensity; - float matMetallic; - float matRoughness; - float matAO; - float matNormalIntensity; - vec2 matTiling; - vec2 matOffset; - float matOpacity; - float matAlphaCutoff; - vec2 _pad1; -}; - -void main() { - vec2 uv = fragUV * matTiling + matOffset; - - // Albedo - vec4 albedo = (HasDiffuseMap != 0u) ? texture(diffuseTexture, uv) : matDiffuseColor; - if (albedo.a < matAlphaCutoff) discard; - - // Normal (world space) - vec3 N = normalize(fragWorldNorm); - if (HasNormalMap != 0u) { - vec3 tn = texture(normalTexture, uv).rgb * 2.0 - 1.0; - tn.xy *= matNormalIntensity; - vec3 T = normalize(fragWorldTan); - vec3 B = normalize(fragWorldBit); - N = normalize(T * tn.x + B * tn.y + N * tn.z); - } - - // 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; - - outAlbedoAO = 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); -} 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/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/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)); +} From f99577c174888f42cde8c1499b8fc592c6a67871 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 13:38:37 +0300 Subject: [PATCH 3/5] shaders: voxel GBuffer stem with the voxel shading model --- Game/assets/shaders/gbuffer_voxel.frag | 103 ++++++++++++++++++ Game/assets/shaders/gbuffer_voxel.frag.spv | Bin 0 -> 10060 bytes Game/assets/shaders/gbuffer_voxel_gl.frag | 91 ++++++++++++++++ .../shaders/include/craft/shading_models.glsl | 9 ++ Game/include/World/VoxelVertex.hpp | 2 + 5 files changed, 205 insertions(+) create mode 100644 Game/assets/shaders/gbuffer_voxel.frag create mode 100644 Game/assets/shaders/gbuffer_voxel.frag.spv create mode 100644 Game/assets/shaders/gbuffer_voxel_gl.frag create mode 100644 Game/assets/shaders/include/craft/shading_models.glsl 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 0000000000000000000000000000000000000000..e7c99170316be01ab254ef3ed923ec639dc5dc76 GIT binary patch literal 10060 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 O_0{J5=5ej!uJ!*sv$!Du literal 0 HcmV?d00001 diff --git a/Game/assets/shaders/gbuffer_voxel_gl.frag b/Game/assets/shaders/gbuffer_voxel_gl.frag new file mode 100644 index 0000000..ba7ed29 --- /dev/null +++ b/Game/assets/shaders/gbuffer_voxel_gl.frag @@ -0,0 +1,91 @@ +#version 450 core +#extension GL_GOOGLE_include_directive : require + +// GBuffer pass, OpenGL, voxel format: engine gbuffer_gl.frag plus RT3 + +in vec3 fragWorldPos; +in vec3 fragWorldNorm; +in vec3 fragWorldTan; +in vec3 fragWorldBit; +in vec4 fragColor; +in vec2 fragUV; +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; +layout(binding = 1) uniform sampler2D normalTexture; +layout(binding = 2) uniform sampler2D specularTexture; +layout(binding = 3) uniform sampler2D roughnessTexture; +layout(binding = 4) uniform sampler2D metallicTexture; +layout(binding = 5) uniform sampler2D aoTexture; +layout(binding = 6) uniform sampler2D emissiveTexture; + +// Material UBO (binding 1) - matches MaterialGPUData layout +layout(std140, binding = 1) uniform MaterialUBO { + uint HasDiffuseMap; + uint HasNormalMap; + uint HasSpecularMap; + uint HasRoughnessMap; + uint HasMetallicMap; + uint HasAOMap; + uint HasEmissiveMap; + uint _pad0; + vec4 matDiffuseColor; + vec3 matSpecularColor; + float matShininess; + vec3 matEmissiveColor; + float matEmissiveIntensity; + float matMetallic; + float matRoughness; + float matAO; + float matNormalIntensity; + vec2 matTiling; + vec2 matOffset; + float matOpacity; + float matAlphaCutoff; + 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; + if (albedo.a < matAlphaCutoff) discard; + + // Normal (world space) + vec3 N = normalize(fragWorldNorm); + if (HasNormalMap != 0u) { + vec3 tn = texture(normalTexture, uv).rgb * 2.0 - 1.0; + tn.xy *= matNormalIntensity; + vec3 T = normalize(fragWorldTan); + vec3 B = normalize(fragWorldBit); + N = normalize(T * tn.x + B * tn.y + N * tn.z); + } + + // 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; + + 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/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/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; From 6427c924fac5dd293490cc3508b8b16220bb1c26 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 14:00:30 +0300 Subject: [PATCH 4/5] shaders: voxel lighting stem --- Game/assets/shaders/lighting_voxel.frag | 145 ++++++++++++++++++++ Game/assets/shaders/lighting_voxel.frag.spv | Bin 0 -> 42384 bytes Game/assets/shaders/lighting_voxel_gl.frag | 123 +++++++++++++++++ Game/src/Game.cpp | 8 ++ 4 files changed, 276 insertions(+) create mode 100644 Game/assets/shaders/lighting_voxel.frag create mode 100644 Game/assets/shaders/lighting_voxel.frag.spv create mode 100644 Game/assets/shaders/lighting_voxel_gl.frag 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 0000000000000000000000000000000000000000..256088fa6c3ed4f844990a42ca2210ed061697f4 GIT binary patch literal 42384 zcmZvl2b^40`GyB}H&A8XW>N2#F=tLKxaOrNs-qQS21cinAA9agQi)&2BYt+ryVo!qpbv!_qP zLBu(ZG?%nGzS^h+)JEi5?b{S=udQ60JY~k@sZ%FUnK)zC!i6&y4W825J)^g$uX{#U z&p`LA!JZ}E`mI>__4Li^UEEbVbl_3e-GTp#)G@ucyL0}efzAcp6FLVwrw?}ac5j7T z)4tje`{c!)16^A!nZ4`Y4SPHNtJP-p_x2A|{%gm7`rOX0p1wH;_jh&oRwX)!xEA&W z{ewOIeRT`UO0^ABW?FY=*TKt-TP^9?*@|rUyT}*zoZQ`8=fi8GQs#iZ!R`S%u%x?d z@&HwK_YEG>-?ONjIxTFSBk^4{YoNQk@6i55_3|s%wods&$4{En(@Ttcf!RCl-S~%@ zl5Mrk$rmqav|$VIsnfb=H*CXdTLNa+%TA->#fGkzaqt$-?E$Y;a}m_XcYJ5>EXSn( zBr4ivZ&Sm+qqaTZr2c{4u153fF?K*Zgw(;x+MT=}V?xiO!GWHciygknlaFo&qRF__ z%k4tmDA!io6}*34@2KrYKG45-&fLE4MHvGN)#Fa??qAS7IB?4JS#x`PX3aNrM!~V) z6Td^c`Uj7$V(s<+Vhyj2`+u<(^bF2zw$Sd@Tj`kYYrJ}_w%UH=M^<oMXYqRhVOD{b^inUy1Hp=eBCDx?^(bOsMhdx*PYPa zJJ?wn;_Lo5y}lPLE8l^3V9y|Xz_Rkg(8jYP4sa2ckD(4@WL*_=FA}p}pq%SzI4%*Tk^e#c($BWg9o# zxxS8k7KL2bw}2Pbx44GwHh53vcQ<)g9e31TQFjVEXZAX2#(9;zfA;KpoE4bpIgPy% z-cfruysv*?L1*u{p3Zt*!)lwLHC*aFudg1*L)~L>F(A=3HT1@SM0fox&VYcUyM`7Q z6CK?()QEGWr*hNZb+owAsCq-4JKN)0s`TlD1KoXdI6H@NZPn&_s%(dMb23fuIkmZt zwY#Pobu}z$gX?NBTT3;y%{I8U)VYce41%3=D)wQJG zvE6;_-x;Td&Goc+|87?r9*6fYgr^^_sb#ha-F=IC22UyMuCHbGNu9mD&i1nMYYeeZ z>~m?jUeez+wNE^GaGqI+s5@MZq)Oz9FU30oC ziAe3WL4BI{5N)*&!W;Jr?X?e!JGl?5mmgL;2mb%>FWPGtpf75kXYI8Mhm>otUEIPi zZQ+*<;cc~R;hj~#ZxHWnzuRj!fxYhUU-j>Hcvp8{f1`bEwFlsCIF8r1!1F7=r@+hl z(O!GHg+JHApKswSazYOFv-!+vv}1L67q>oyueWRMin9pe9ktQ$xmlJ~d$v)lGBTq4yE(wKLGlxj1VG@2GtQ-q|~|yQ_a}{rYR1rD$`;E)&(xLu>58)0h>zYM1DH@G{YHyd14jcVkYjgwO1p-`z!1v|kNh zw5YSc5x2c|jWMQAuQ${=yUy}h#W#W*_mmyA+rdNc$vSFxpw-*N4Q|?b7h3m%o<)o5 zPfv|F_nPqW<5PK zdz+h?d#t|BKe|sY*(NEmV%>(BXTy`(A$onR>+hQ~b%Jr6f6KZiW>DBY+kQW40S@sK z;3*Y;2AuQuIk5eR?M1jj9pjh4<@$J~h5vB~Z>zlqpInvw^AKBm?e!M^Mhky)2yd&s z4WCrSdS{5Oz4o6Wysfqz9iKnF(a$z99dKQ)1YWeTd)DIK&icKNeOgbg*dN!2mwkNK z5Z+PS4BqQitvcf!%RTE_qrM5?Db>C46!89a++I5jTp!oUb}W2CWjhXB)_HsjpD~2D z)n>vcS7m1nv9;H_Tlkz7-ZO-^)q3HRs#qruv9;F*hVZu9V)*>&C)d}{Nn$$S7@h`p zTnSSj=kwsp#;DC%d)Oc_(~U^Lm#;Kntkt#-e-KE|wTaQ60x!R6lmIDA4i7f*o8v466KKRtxE)qW1o z-u~6Hjn%I%?%U2EWN!P(n)24`=7FSy*>$H23wwFBc`JCiFYg5}_vJD0vfgn+cw6lNdHo(@ zSp74KIlVJ{Aj{)!{oI?1&$9P9J_alkNl-IS9g+{aL+Kz>`LZywVYwh*6TA$?;ORirT zU(GswuH2ti+ehCgmF48E<N1T77I7;FNRh z%5w77a%%Pd)yp;J&G%aMwilZ3v($Y5k?~rs((WiU$4R0JGaoXCNC(oq3f;D*Q}*$3%{(B z8wxFJ;HE;$8n~m-vIf3cXjuaf6`JE{n;$MT-&v|XT4-xl+D{72ca-|MU@}%~SK5ws ztugMtmn_F!-a77T#u`I8*TyLFQE-oN*OGJX{^k27vFA_TNu`ePvW{r=_SVZSduLUz zx6q8MHlfhkDsBHlYp=8^XLF?S>arj!) zVm+fMZF%w9{1=zH=DHHEi`DaXBxyZjTEE<}8_9ni8-3MB!97pK>g&Kg&qk6ww0n-J z#l9|hBxyO4@z*0)vpvRNpWOKBmfwKf`0Dy^L~eWHZ~ydH*MAdo?drxGO|D&S6lk*w zYuA=!>}@NX<73(F$StcriuLs#a^t9*MloVLw`j)GcaKU>eCyt)#n%||p8&QVd14>X zqQ!S=r7vp-pK(-L`eoaWsWitf`ug;%3+|XYr}n#>+&PkO%(p&fS6t2hn)EM|FP7ahupa`=H3;bTWQaq8MInGRZKIW+(u`huwN0Qt2 z57n(_?iYSqojcAe)cbUL<)f~z$#FT8+;hTlN$jQIwu&43ToT8A8*o>4AwbQ#%OiY4 zN?wFPKl}Fa%1OGN8LJZ zC3g)w&gsKfz>fF3vEM^(Ikm*R7yL&1Zu=(YeQ-6$Cq7>VKe6MS2g1Jw-|7d`n`OQZ z*GD}*-vHls!79zR-w*G+`jH1x&jWCM)Klg`@X>c&vTyh|;YY3fK(n52!Szv(&$q$z z@920i{5$Xyx4-}X)bkKrANBZr7yOm|-)YwKJ@|}`Ki6#A_u=}er_96P`}?}F z)jqM&eyQgNaDCKM=7->;FZo=v%%kuF&)@LDlz9xUk9x}d2z=j!9h&R+$MD~`o!J}% z?_cy$PnpNTbB^4-IR;O_yU)G6S>~s3ebiItN$@6*eyKSIPr+||YVT&-o`&nAo-#iJ zzxnc6%`(rxw^?^;a}0hC*GD~No&^tlZ+x?Dzksj$%5R%v{Y$t$>M8Ra_<~!yn`7`i z{OfNW&@A&SxIXGB^K0<*tw%KL`3?Mw$4_py?YD4!)KlgK@RlE4t~o!ygV%m@N^=Z; z57kF~JN8P?pBG7!NYlUtYZ-Pes_`$xi;2V0NrmyfD6?VDEE80pUzXzf+`Ei1e6 zwqxhsmV6@GY2-=n>yyxip|ur$2cvxmo|N`1i79-XpJe2#*vnW7yBPAtpW z&zWfUFL7qWAA9uC`hMu#oCNQv?DEs$u7~(9g+II6BV)qPgV$=;*17(lg4?gy?}7hf zwF8>|->v)C#%kB!#yD5uE5jWV$9g!}wwCsF5i<7Xo50t*_whYaelxiJ4Br;MYQcAg zuU7Cw;VTvVaQG?}H-0yqW6Z{kg3mhtYpvPVh1hh}$8{t3IYlkkRi8^d!hH@Yxz8OX z_c_DEzSx%Uz*qj+Xy&}Jt{#G`C2frEvxfcGKgss}5S@Q@yY!C&Ing4nH66v#k5bSHQN1Y{=1A4mG~h+g7^=qkh(R zZ{?_Fd*wdcc!c{bCZplk3Bk~xqtr?K700yT#p*#`7gLW-cM;u zxm7v2ue$P>X1TTNe1qoxya3L(bS5NDA6zZT{v60jVP9R#womS}m0H^Cvz3Sb$AW1> z(_ikhm0Io@-w)58V(z(a**WOO-H2ptxzAy0Y2z92E8$m>t|0BsS?}1@yFzX~Z=<*u z7-M~M-|@Nz>lxbJ4ejqkhJaQ%HZ8?L|a zX2bRO-E6q^`)*dg5AAi_r@$Tasi=<4!C*C$hvUpKmsPZ!+#>giy8W>Amy9u8L1&oTB{;V?$l^Wg|I?H<V57o7&?K8l(T|ehfz9)6r?i0WplB;WXKGn3> zuO(p5fAw3c_RRv%BrQR=ecfOW+o!FIw2)*w#A(MoaN5xWmTPmKdcm$=+u$0JXMFm= zj*m9ii+oSUeOt=)gJ+T(&vM%Ja~*2?2W_x@w#7E3Ed$`R#Wg2STL!^ti|bFGwk!ds zEv`j*+Hw;3c-mq-%V|$rw3ThKO~$kjCxdNo*2^hiHIHbgR+{TK+6ODm^>%2r4nKru zdHuD!hHQi9iMGu9>0qys>e^jDYObF>NbVKT`g*!c4^T~Tjb4fj5APS=OFP|jyw|W@tCR0ZdyV%&(uyS8 zWVtg)`Z+IWlAlG=r%X3fQs1*H`|0GCvmI(_hhyqF=&>d#?eMy5JJun0eB2*DN>caU z+P1jAoI_GS3m^H%D$Q%)0(^{rUZq>NICa}T_Xm#+NY?E>{VtMqznk28+%qpBU0iYZ z%u7l7WzYNsSfA{fmx0yva}6&dS-*Pfx4olC9-EM?-|NCilJ##&ZvE~dpCXMUT~_t; z>Pq`G`3T0^Sl5u2BZ;*yt$aTNzJjDZzMlohSG#RfE8Dg;++#CR+O~OxosTUFyk&v6 z0^9y=$i3Fur|U?bXSw#?Qt(^Bw-sFfy9@p@_?3cdf3@OS2iL=`H~qT-Z2w%R+T!uj`tTx#?YR!H-YWvMU~IZVExqHXY8Yz*#4bXVf*I%d%oUI?%0_B zow4~6`5hz=?O!I>Zf?CkW86tH#+Rvg82Me5)`P$F+9t>FMUv-} zWB3P>nm+p5?>~~%^>+^BIsg6yR+~r4n7js7kI$dMjzj757r1)Xhbv-*x04dn{f5S{yW%m>K~?`E0e!PvQBM@^)|S*FaJPOkIz5Bt@Zs2O+EGf z8*DlClzRu<+L!;JsmJHP;MQ?+A*v_#a&XJ3pH=l`d9ZbA%UW#%+edBAo9Bj_8EzTta19KH>!Y5z7y;e_TdvQm!1eQpwrZs*AIeJImV7mczS@it z-_^m|mdEE`fj^(wfmFKDFRO(+3oAsYt&BX?AHQ)cZf6B}Kb4PqV-a~T#+>U(v0`CB} zjXRNNU-bX|S)t-1$Tu&z<##Q(=kVbLcTetY;r>4viKl(Ah5P?w#BMzQpNw$*&uQWQ zzZkLWe__G(zqp0_|6;^n`(+jPK5J9(XwoRsB5X@Z8SBj|oActB$US#_KeZLfGQPLc zzl(BPSGwGFw+-B}4&N5;cqjgLaO1j{>;E3GKI*P7we7*i(Ux_)16bW7nxAQ_DM#C> z(p;S?}cW3?Y6}=ri3 z=2*Mu?1!eWw)9~P*m~w+&$y0->+50L^ifNhabU|V!JabX;g;cFy^KC;>CXhPW$vRb zzSo>gQkzJ+it+cFyMLwMRPh7gYH!hogUCI+7ONjfQuEvvr=BTb>-kz$&mmy7gGs4p zDp=h;C^5`)Z+|G*dfhYhHNI__M($x7^f`>AW*fxrPp&t)dfId}*fwP@j{&QhJj_P0 z=A5HrA#&?J0&o;b|HOJfxQul?T+QTR<~7+^AAlT3(kJm}fcZBhzJ6X0Epr0cHt6Tv zA5N~8xt|5L{+T4(liFSQ`cux@{>uUNGFj}&ncCjaXJ-lId$t9Bv;eleda@8>&eBUE9YgC=i~r}rYT-9lJmtRt z*GJv*wogre&zqaU_BZ?%xP8vMfm`AFsE?qhuJhZ#{Hs6vYIpB5wtdx>wtNw+7XGD* zr)_t@^-+({m%+9%=lz{vHRmzq?t)uZ+ga7QcQ;sFKilFst0nGNz{U-~7pzZW-Uq&i zq#mEIf=i#T!Sz$mef!tJ#@6Qgl9%giZ`$Lr2g&v2I@+_qdx2e3?<3EedVm<^n)+77 z)2;{MmP_Bi30F(sEw2{;Z-dpszfgm__!0O*6Ii9vbEit|iwjbdSx9~^c zmdQN+0BrlzQ|}MKmeZCvkAf|izC8xkPd#n>5!m{*Ip*?m%*RoW$3CQ-)B6^9zXFe` zu=6sO~b!iZsE_h@aHRTe>}e)hg)~}6L9O#`Snw{KI%EYo&@u+etv1s z`DI(QWvqS%9*dvzW4q*ga);?T`V82$XBlm&|L5ScJ^VJ&uDs?fEs>_E<(+*1&JTwpF`xDbHLwev?Ta6G%CKCRW&I*hwVk zY=83f@pr^f5C46|^Q`hB+_jkG8nv#M(6#BGKD`XKzO3U{;FeR*`1}FPzxw!ScYI>| zV`Y06nttbz@|^f8*gExbz1xOAfo;Dw$3tF@$HDk`96-u=99Ut;S@c{ zVEdA``~z-V)H9y{1oN+6hxT%;|5e!>YyHZx{x{q@^~qSj19m*LIo9%WtfvvjV=5{8 z$e{&34D9$$CwISa{Qdofa=%!;h5P#prG0b@-=>B8`wQ_m{=|a2XZhO;rG09}$FiO_ zr9aETN0HOa<~T18R{sV*K99D6)f~&WICne19zOqRYbUAMS8?Wa7})V}?8Msji+v@q zV`N)~gGZ2Ti{ER}XE?T1Dn0KaR)wpXJgTNQ*Uf6^#?{Y##_?85ooj&A@*J=xSk2^N zmiPK=L5x3+q@VGeFLmR&kFEn&%XqE_whw;yCO+$f)w6Cl09#Hy<=zEuorew4)Z_E+ z%BPII5t@2pZw$7add`WFVEeHZNxR6w#Z0v2x6Z<`2b$wjx z+k;(C+Aaga|2+Yhg~+AK4XTs`aH0I*uQ4i1E?r!5D8)$(4@b9o9}T`l8wFg)X?J>wSp zA(h?tRIypccI{8D-FCUZ9Y%8Q4<)yM!znYZ(zCx!hpU-9tXr5EM?j2mIC)|m306;x zqrhq=kHpAa9gS|=a{WFAtd?Zo9P4AzEbF^%+htsR?b}px?U|?J!0z?o?}zJ?I*$iC zx2f|3V6~*wIRh+cBinZ(HT zP~KPZteshKW6UF^?OkZpdr zw4oPlfAW5R0o*$E)uzAmV*j++hAkP$0q}_=Kl3n_pI?TC3$^v4So>K_}X0q@^TFvU$2pM zN$wh0pZwSgyB?0KuBqnN zHPyIY6SO7n1>o~5ZrlsOW!#J4`Xuhh!D{;PuNl{Ci?+nQ6nsg=jr$338TT@{K8gED zu$q4SYsU2&rOmir|E~bshg{D;1y(b8(r{U`Qd%csV51#>F zQ*r0_vtZ}0U4EJ%x7b z8cu#M$!Cz2$rJCsN>9A6!qrS3iK)Ni@-_7O`B;t9*Ws2?&p3SpoN>}_KfIq&Pn-w9 zj$QbJV14qw`kP>V)bqUmEwFKYAEMpoHe-8lq%E<&3wFKBU1#3|JC2$2?}OEn@(lAZ zn(=*ZdI)SBeXYa$E$!k*$S30HxX#D-G5DjT+^esF_9M7@zLWi9u$uGi`;*7P9=<=( z_7l>dnKWT9_xSaQ2!Ht`F|20@2_00Qkz{Yjn zwP)VFht-yO{w-K7@2p+`tLg8U{~qk&m}~nTNzE}Ar;RUzZKLgfiCiArAHeoW+biVq z)c-2j^IO{=$>sjP5w5{Mfp;f2p5?Ub_aeEr#QzI8b^jSGkL|Bu=frZalgnfK8`yEw z_6E5;?fN@7?Rpa|&*%DYfydD<>oTTg^)b$C%U;nG3)&gxjgp-NcGQ)ETc`Ix5?F0etFNG%I33xJhpbQTK4u1@WC`n zT_5LX82Ap#=UILQxYq>rTt8L>TUMKG@EJobF;)gUpW(ydj$y86BjEa|r){f%ZFlNe z6|Sal;;at7zlgI2-2abJJw9uKUHhqPEx3N_p0oCGZLs<#_+>9#2dRgL z>hqIY+PwkzbmD09*-0MThTyDWpQGgYyzt#%zw>GtZI<_0Og-f{23tOS6L`5tjD+i> zp7(j9z^=2r&)W#DpE2zFreN#I_>2atSuSl*%f7q0HUh3gGKTx^7HI0(ceezqnLMnY zW)q|Fz16MI6QjHz*cx41=6oBlvD9tLX5?z=|F&S)TKIP0%c;n|xWB&#u8(@=b$jqV zG$<+ax&ylY+7oX_u=AU}eJ8Mf>hakbY`aqDE@1uC^>K`M1-k~*uifDKsmEt`u=5a~ z_k#6Px9?tC)qJO&z1duw&)oBNq6Am=N{w1mQ(k-A>W&1ebz5d{7K+OeEz>C+B_L<{E6h+^m85U57w4- zbO2b*-T}^`s6x$5Lh3t-`X6T3FPY5;hH!Y?A{!H2wb1c@l>!r>WO{adQng9ITIet^r4YjqCMWd#(Y8fwdXi^>ZXxE!Xd(z-qaE9|QJq z9<&`zQga@}Y2*9B*?*1$%VYZh*gn~}OW-3NEkTAB21TR?lAZA+Tk&S) znsFvrAN92DEU@iP9cP2p^i7-(gUjppN8qcG)Z?=h?AlLV9|h~D?m258&jG8K*Y9)T z>Pgw>KZa&owA9?o+PhT?o}6p-K)+g*QTF))kR=!*{ePdRx^3HHYnbV zdog+=?h5tqDO>_iTy5ob@KU(;jNK=|##gt^=aH+~7WcHvz|L9tC&A@)@N&35>bVYH z0nRnhJmd2zbp5p_-j!g-DSPBqVExqN^J%c{%60H+uzu?LI8WDroy+v=GhqGH6X&yF zuXU;GTDY3N_FbEr*Ro^C-Q&#V`&8WAc+ThNzQ8S5v&+D#tiSU-%W z-S+t2_$ly{6}Np)gI)7!@6X`2H~+7|GjM&>Gv7Z48%JBdi}Wm5-Q-~#9Iv0kwHe>> z`6bvkg+B*&-8(*`u|E&C%sg<~^eeb}*6XjqmQzo;-+(PweqQ}sxO&c;7r<&s*>`@2 zW=!o_bH4}MR&Dm<7vySb_e)^cSoq6eeG>B(us-VP#~;AgF}CT)AL06`+Yi^rt6y{^3u_WF}G=X&}pntImI8(=k)N7hiD z}K(+uy;)Qnzh?Ay>;dy$x2&IR6{0_AiqDZ;`9n_R+NCKVZ+xa$f(7 zrk?YKrBze&yDyHvW40Wcx|(s{!LF7uX#?Az@||KkntH~h1FUB9$e3gg8HTP;o*h;I z>*F(tHtVf_#syCQR|4Cg@Ri~E8u$sSJl)kSHR^Ny| z=iR^>aNFiNsNH*5uLIWM=WN==e#WLP^{oZBzC4et16K35k$j)H9=V6lBihy_sksKl z>E{OE=DQNGJhlzNj*0dA8J@iPt^{0vS3<5W?b--zyVAdn!S-(_l5MjO`ed9o0l%B1 zU;3%$Ig|F9XMKzUyFPLrdd_UBM#}ma4OTOGSU=q$M&sSbX6T7go->=HYs)#a1=v{X z>ED)M=OsQ{f%Q|5&(>h)D?ZzR^;5TxBgxgoe)j2}YJax_J3r>WH`tzh2a<>O9V@%J zb$Ktf6WADz&wTjKVEZi;!5(03nS(vSY9^1&LC(>=(DlhVx;I!K&rxlTjh_RnTZiYzKH%~k-50J;&e8q! zC8_5baSYhFKHqE4Goqg8G~zb39mE`Z)otX7Wfs6L%tdBW~lF zV-h@ZwUz5{GF*Gs&HiBHs~gY#^8m1IkI#W%{nYhwoDKqyB)RX%r-0S;&H7VwO}W0D zbMv(MV6bh@{rVwbwcM|3Q!Cf3YbR|y6ntg1m)hpT;8(+Q&QF88|Ep(joL*_#;&(XM zSlVp&RC2ZS^9Zo_n&C&ny-tN61^1ekb{!4(dXxK(W8i8@`K;|&H0ROJ+Kg!&eQlTH zc^uff^-JvcgKb~t<#@1u>bBi`0kyR41K_glGvLly>No+ckGkc3=2COab1z|@cr&qC zNBAsw`3%ta{Jpaa3){pU)Z{h7N+`n-Z|2^8c9u(a2 z{*9~H<^G+kaO?ALU6p)H!S(m=UBxaRUvSI&_pV}>`}eNG_4jXHmE6C1RdWC4Rk-E- zn^)nM_itW>YxnP7g=_ckUX|RxdsTA(_EpJewQ&FbRcZHcUxizrfBP!j`uy8h;oAM% zSK-?I+gBy`Z(o(%zkOA5|L#@EZ)oBE-K*H$PyAa~;nwHhx+?iYE!@9xRoeagR^hha zziSn4JpZOuxb_!XxPQ~CwEH)$!Y%LLv?{rO)2ihDO{;L%Ydar4g%UhEAJfA9+g8T++OQG%Am`f&zUNhBF-zUJ<6Mh+3pWI7+60DE9<-NwL>2Lq906W&{|EIueCXe(#_jgyK>(fm? zazA+${C~aw{WQA%+Vi|~HQ1OFN!o3b*Ln4f>u12WsT|kO!qxS^hFl*1&w-uSdBn^( zTnAUz|5|c+`g{Xe|MdCuU^P?v+eR$QUXP~Tx_o!_1+dRC<@fb&LQ_w?o55-(kHoZo z@9S{`{QO`Kt2F^HXw=Le!s3*=B!H!+{m%#eux$q9KKI(Zc{4&_MJ{M}w zbD{S}+7jz7uYIwe*RnN`ec5-4c5nXtj#{%Pp+OZdKP8)v;wvD#`337RCPl4@|wkOHussCqS&u?u{lgsmY|1)4e?>C<1wCndcxwgdr z1vqs-3zoN8Z6J}{l5i|qg~czOv~zHoS&0x zPaVGl+xPI_gKbad?nSUZ>Y2Njz?nPknY)+4+7jyzV9zn@eT7_}dxAfLC(}mDXw&Bf za`lw|6Ie~#tK{<7{tQ;jp7j^7pZBZl;~w!k_}a=x{#UTq1@&A%-T+%xoAta#u9g^Y zf}PLszk?maT+iMD>!Y5wy$!bAspB7DHGLE3pWxd`iSsY;YHafA@%cB{wI82%!1}3s z&f2H{fYr;-`~M49Ps-=n6sg%3?e_I=X( z@2(1V-!0!+uZE_ceRp-Rn#sfZ-FNfOdJXi%Sc;yutck8IbG{bXSn9ULXHB*Ae{Ha9 zEqopLIdJ>pUc4?`AN5@0)&u{q?Md-omD^~rU1C$K&fN!lEnZOPTG!!@xB*u6P? zSGYcz#*@oa|0Hm(lfG|} z=lVSv?DgAtmeX#GG3443|3Gl+J^(C_Z3@^qvD`smd2EM(9Y<{klgrbtL&0g+RIohP z@58`ezpcxdmet2N`;%)=9n-<~J^XO6?aAC70oO-8b9W>-bEiFXcNAD#VjTm{_4{bB zJlF4I!Ct>Dqs_KWBUexP_k-269Y-#Y?E_%7T)$_4y?(3fvD5cq#x2Tw!SUwh)64tAWfN1g%JPdz?of^Ao>gJ*&DQ`g6S zoeg#_)2|PM^;1urkAS__rLLuLHGS>7HZ`wh`;oiHnaek?xViD1&yRu~_k8|$4p=Rp z|7lYzubrF0)8_NQu2Jj$7+9XRUI5OyeLh&8auuR_@>N!WQ0jrxla*i1PN;K_>_gS!YhF=TTC+q8TV13l% za~-%`_t(SqQ@4)KkgLW2^I-S++)vyHSBw1%VEg2r?|F6;+%~&s>#NUj}P8 zd6Z*)Gn#hW<9p+~z~%S#?uNVO)84PZZEwD>cMn`2_00FZVB=`Z_x0`rtD8J*gX484 zT$}M7*RO$XQ~1}xu6xI4H1=+p5iee3e`+?fx#Bqz1OBvhr;}N)i>h@z( z2JHvn*6-{65KTSh9tB%g-L^eMuI9e>4dS~`J_a|Y?>v7D_WDzPU+*Vq>RCgNgVjtP zSwne_djdT%mSRtzeu}OwZF>@IEOp!VBXYHj)6-zJjPuXHYWcq2Q(!gQ?t1t+*z>ZS z*JshxbH4lntmbz!9DmQdU&7VZY{xTTwT#L0VEa?PQ~VX0ddB3}U^SCR#w2^lZ_xG0 zv%_z}`b;Egv)<>()zkmqf$dND@8SC7{_{n!KI)c#fm|)){W4fBeSZb4wkk2x_dmeZ z%kS&`5pLT&2eo?->vh07UV>{EzgpQ+-=E;tm*K1ltHX`& zdjoCy4X2JZz}hkgYl78G9+`ujqidn-lXG-!us#z>+8i4{2UfQZ&yRJ%x1=C&okl%VB`9HuRYI*ex|G~{d^a=?B|AX`{^@>w)FGeU~TE=Mqo9QNBWt#8>2Vk zHl8^)fhVrErIgXvy>TR5d)Cb;u<_O7vnklN$7eKHKXrW^r_I2wG4~z$=5RHAv;Ndv zQ?4)P+&pdG0&JUezrH0{E%)o%)XFvM+DRL?2A{{d9=;9yLU_*kZQ<_!>e(B&t2Ax# zdk@%H+HAL<`>Um&+k?H=4Br9nbt-&ExYx9_V<))Ro7{Ko3|C9aXKlNnIgftUW=!Mg zYr7oJUBT9^Ut;eDwtbnG-NE{)+xCa3S1oOOFSu;`9&qO@b?gb(N8R#1bE!GzxtB0c zyuGnmNBH~T= 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/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"); From 546643cadd39d5e795caf5c0da14bac000182543 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 19:05:54 +0300 Subject: [PATCH 5/5] build: pin Engine to the format-stems merge --- Engine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine b/Engine index 16459d9..5e5f135 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit 16459d9e9518bb977a5fa26ea36379ae2a917d6b +Subproject commit 5e5f135c66911433df155e4105eceadd0eae8658