From b7da2408c804e744646dbb42f958f2e6aaf8c4ed Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 23 Feb 2026 06:31:54 -0800 Subject: [PATCH] Add derivative-based normal mapping to terrain for per-pixel detail --- assets/shaders/terrain.frag.glsl | 14 ++++++++++++++ assets/shaders/terrain.frag.spv | Bin 9288 -> 11752 bytes 2 files changed, 14 insertions(+) diff --git a/assets/shaders/terrain.frag.glsl b/assets/shaders/terrain.frag.glsl index 96aa2c57..36203be2 100644 --- a/assets/shaders/terrain.frag.glsl +++ b/assets/shaders/terrain.frag.glsl @@ -85,6 +85,20 @@ void main() { } vec3 norm = normalize(Normal); + + // Derivative-based normal mapping: perturb vertex normal using texture detail + { + float lum = dot(finalColor.rgb, vec3(0.299, 0.587, 0.114)); + float dLdx = dFdx(lum); + float dLdy = dFdy(lum); + vec3 dpdx = dFdx(FragPos); + vec3 dpdy = dFdy(FragPos); + // Bump strength controls how much texture detail affects lighting + const float bumpStrength = 9.0; + vec3 perturbation = (dLdx * cross(norm, dpdy) + dLdy * cross(dpdx, norm)) * bumpStrength; + norm = normalize(norm - perturbation); + } + vec3 lightDir2 = normalize(-lightDir.xyz); vec3 ambient = ambientColor.rgb * finalColor.rgb; float diff = max(abs(dot(norm, lightDir2)), 0.2); diff --git a/assets/shaders/terrain.frag.spv b/assets/shaders/terrain.frag.spv index 2dfc80071ac97b0289004d3ba62939819756af6b..5318d52de017eb865f15ccb648ecd4b5b9b69519 100644 GIT binary patch literal 11752 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 Ax&QzG literal 9288 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