bvle-voxels/shaders/voxelTopingPS.hlsl

95 lines
3.7 KiB
HLSL
Raw Permalink Normal View History

// 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<float4> 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;
2026-03-26 20:00:33 +01:00
// 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;
}