// BVLE Voxels - Toping Pixel Shader (Phase 4.2) // Vegetation: vertical gradient + up-biased lighting for stable grass look. // Stone: classic Lambert + hemisphere ambient. #include "voxelCommon.hlsli" Texture2DArray materialTextures : register(t1); SamplerState texSampler : register(s0); struct PSInput { float4 position : SV_POSITION; float3 worldPos : WORLDPOS; float3 normal : NORMAL; float localHeight : LOCALHEIGHT; nointerpolation uint materialID : MATERIALID; }; struct PSOutput { float4 color : SV_TARGET0; float4 normal : SV_TARGET1; }; [RootSignature(VOXEL_ROOTSIG)] PSOutput main(PSInput input) { PSOutput output; float3 N = normalize(input.normal); float tiling = textureTiling; // Material texture index (materialID 1-5 → array layer 0-4) uint texIdx = clamp(input.materialID - 1u, 0u, 4u); // Triplanar sampling (same as voxel PS) float3 blend = abs(N); blend = blend / (blend.x + blend.y + blend.z + 0.001); float4 xSample = materialTextures.Sample(texSampler, float3(input.worldPos.yz * tiling, (float)texIdx)); float4 ySample = materialTextures.Sample(texSampler, float3(input.worldPos.xz * tiling, (float)texIdx)); float4 zSample = materialTextures.Sample(texSampler, float3(input.worldPos.xy * tiling, (float)texIdx)); float3 texColor = (xSample.rgb * blend.x + ySample.rgb * blend.y + zSample.rgb * blend.z); float3 L = normalize(-sunDirection.xyz); // ── Material-dependent lighting ───────────────────────────── float3 lit; float hemiLerp = N.y * 0.5 + 0.5; float3 V = normalize(cameraPosition.xyz - input.worldPos); if (input.materialID == 3u) { // Stone: classic Lambert + hemisphere ambient float NdotL = max(dot(N, L), 0.0); float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, hemiLerp); lit = texColor * (sunColor.rgb * NdotL + ambient); } else { // ── Vegetation shading ────────────────────────────────── // Vertical gradient: dark green at base → lighter at tip float h = input.localHeight; // 0=base, ~1=tip float3 baseColor = texColor * 0.55; // darker at roots float3 tipColor = texColor * 1.15; // brighter at tips float3 grassColor = lerp(baseColor, tipColor, h); // Up-biased normal for stable lighting on thin geometry // Blend between actual normal and UP vector based on how "grassy" (non-stone) float3 lightN = normalize(lerp(N, float3(0, 1, 0), 0.65)); float NdotL = dot(lightN, L); // Soft wrap lighting (half-Lambert) float halfLambert = NdotL * 0.5 + 0.5; float wrap = halfLambert * halfLambert; // Translucency: light passing through thin blades from behind float backLight = saturate(dot(V, L)); float transAmount = (1.0 - saturate(NdotL)) * 0.5; float translucency = backLight * transAmount; // Hemisphere ambient, boosted for vegetation inter-reflection float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, lightN.y * 0.5 + 0.5) * 1.3; // Combine float3 diffuse = sunColor.rgb * (wrap * 0.85 + translucency * 0.35); lit = grassColor * (diffuse + ambient); } // ── Rim light (reduced on vegetation) ── float NdotV = saturate(dot(N, V)); float rimScale = (input.materialID == 3u) ? 1.0 : 0.2; float rim = pow(1.0 - NdotV, rimParams.x) * rimParams.y * rimScale; lit += rimColor.rgb * rim; output.color = float4(lit, 1.0); output.normal = float4(N, 0.0); return output; }