2026-03-26 17:47:08 +01:00
|
|
|
// BVLE Voxels - Toping Pixel Shader (Phase 4.2)
|
2026-03-29 19:46:25 +02:00
|
|
|
// Vegetation: vertical gradient + up-biased lighting for stable grass look.
|
|
|
|
|
// Stone: classic Lambert + hemisphere ambient.
|
2026-03-26 17:47:08 +01:00
|
|
|
|
|
|
|
|
#include "voxelCommon.hlsli"
|
|
|
|
|
|
|
|
|
|
Texture2DArray<float4> materialTextures : register(t1);
|
|
|
|
|
SamplerState texSampler : register(s0);
|
|
|
|
|
|
|
|
|
|
struct PSInput {
|
|
|
|
|
float4 position : SV_POSITION;
|
|
|
|
|
float3 worldPos : WORLDPOS;
|
|
|
|
|
float3 normal : NORMAL;
|
2026-03-29 19:46:25 +02:00
|
|
|
float localHeight : LOCALHEIGHT;
|
2026-03-26 17:47:08 +01:00
|
|
|
nointerpolation uint materialID : MATERIALID;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-28 14:48:11 +01:00
|
|
|
struct PSOutput {
|
|
|
|
|
float4 color : SV_TARGET0;
|
|
|
|
|
float4 normal : SV_TARGET1;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-26 17:47:08 +01:00
|
|
|
[RootSignature(VOXEL_ROOTSIG)]
|
2026-03-28 14:48:11 +01:00
|
|
|
PSOutput main(PSInput input) {
|
|
|
|
|
PSOutput output;
|
2026-03-26 17:47:08 +01:00
|
|
|
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);
|
2026-03-26 18:48:35 +01:00
|
|
|
|
|
|
|
|
// ── Material-dependent lighting ─────────────────────────────
|
|
|
|
|
float3 lit;
|
2026-03-29 15:00:12 +02:00
|
|
|
float hemiLerp = N.y * 0.5 + 0.5;
|
|
|
|
|
float3 V = normalize(cameraPosition.xyz - input.worldPos);
|
2026-03-29 19:46:25 +02:00
|
|
|
|
2026-03-26 18:48:35 +01:00
|
|
|
if (input.materialID == 3u) {
|
2026-03-29 15:00:12 +02:00
|
|
|
// Stone: classic Lambert + hemisphere ambient
|
2026-03-29 19:46:25 +02:00
|
|
|
float NdotL = max(dot(N, L), 0.0);
|
2026-03-29 15:00:12 +02:00
|
|
|
float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, hemiLerp);
|
2026-03-26 18:48:35 +01:00
|
|
|
lit = texColor * (sunColor.rgb * NdotL + ambient);
|
|
|
|
|
} else {
|
2026-03-29 19:46:25 +02:00
|
|
|
// ── 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);
|
2026-03-26 18:48:35 +01:00
|
|
|
|
2026-03-29 19:46:25 +02:00
|
|
|
// Soft wrap lighting (half-Lambert)
|
|
|
|
|
float halfLambert = NdotL * 0.5 + 0.5;
|
|
|
|
|
float wrap = halfLambert * halfLambert;
|
2026-03-26 20:00:33 +01:00
|
|
|
|
2026-03-29 19:46:25 +02:00
|
|
|
// Translucency: light passing through thin blades from behind
|
2026-03-26 19:07:04 +01:00
|
|
|
float backLight = saturate(dot(V, L));
|
2026-03-29 19:46:25 +02:00
|
|
|
float transAmount = (1.0 - saturate(NdotL)) * 0.5;
|
2026-03-26 18:48:35 +01:00
|
|
|
float translucency = backLight * transAmount;
|
|
|
|
|
|
2026-03-29 19:46:25 +02:00
|
|
|
// Hemisphere ambient, boosted for vegetation inter-reflection
|
|
|
|
|
float3 ambient = lerp(groundAmbient.rgb, skyAmbient.rgb, lightN.y * 0.5 + 0.5) * 1.3;
|
2026-03-26 18:48:35 +01:00
|
|
|
|
2026-03-29 19:46:25 +02:00
|
|
|
// Combine
|
|
|
|
|
float3 diffuse = sunColor.rgb * (wrap * 0.85 + translucency * 0.35);
|
|
|
|
|
lit = grassColor * (diffuse + ambient);
|
2026-03-26 18:48:35 +01:00
|
|
|
}
|
2026-03-26 17:47:08 +01:00
|
|
|
|
2026-03-29 19:46:25 +02:00
|
|
|
// ── Rim light (reduced on vegetation) ──
|
2026-03-29 15:00:12 +02:00
|
|
|
float NdotV = saturate(dot(N, V));
|
2026-03-29 19:46:25 +02:00
|
|
|
float rimScale = (input.materialID == 3u) ? 1.0 : 0.2;
|
2026-03-29 15:00:12 +02:00
|
|
|
float rim = pow(1.0 - NdotV, rimParams.x) * rimParams.y * rimScale;
|
|
|
|
|
lit += rimColor.rgb * rim;
|
|
|
|
|
|
2026-03-28 14:48:11 +01:00
|
|
|
output.color = float4(lit, 1.0);
|
|
|
|
|
output.normal = float4(N, 0.0);
|
|
|
|
|
return output;
|
2026-03-26 17:47:08 +01:00
|
|
|
}
|