bvle-voxels/shaders/voxelTopingPS.hlsl
Samuel Bouchet 36b8de9285 Phase 4.2: match grass blade colors to voxel faces + stronger translucency
Use same ambient (0.15, 0.18, 0.25) as voxel PS instead of greener
tint. Increase translucency (0.6) to reduce contrast when orbiting
around grass. Wrap at 0.85 for balanced lit-side brightness.
2026-03-26 19:07:04 +01:00

71 lines
3 KiB
HLSL

// BVLE Voxels - Toping Pixel Shader (Phase 4.2)
// Simplified version of voxelPS: triplanar texture sampling + basic lighting.
// No height-based blending (topings are small decorative meshes).
#include "voxelCommon.hlsli"
Texture2DArray<float4> materialTextures : register(t1);
SamplerState texSampler : register(s0);
struct PSInput {
float4 position : SV_POSITION;
float3 worldPos : WORLDPOS;
float3 normal : NORMAL;
nointerpolation uint materialID : MATERIALID;
};
[RootSignature(VOXEL_ROOTSIG)]
float4 main(PSInput input) : SV_TARGET0 {
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);
float rawNdotL = dot(N, L);
// ── Material-dependent lighting ─────────────────────────────
// Stone (materialID=3): standard diffuse like voxel faces
// Vegetation (grass etc.): stylized wrap lighting + translucency
// inspired by Airborn Trees (simonschreibt.de/gat/airborn-trees/)
float3 lit;
if (input.materialID == 3u) {
// Stone: classic Lambert + cool ambient (matches voxel PS)
float NdotL = max(rawNdotL, 0.0);
float3 ambient = float3(0.15, 0.18, 0.25);
lit = texColor * (sunColor.rgb * NdotL + ambient);
} else {
// ── Vegetation: soft wrap lighting ──────────────────────
// Half-Lambert: wraps light around the surface, no hard terminator
float halfLambert = rawNdotL * 0.5 + 0.5;
float wrap = halfLambert * halfLambert; // squared for falloff shape
// Translucency: thin blades let light through from behind
// Stronger effect to reduce contrast when orbiting around grass
float3 V = normalize(cameraPosition.xyz - input.worldPos);
float backLight = saturate(dot(V, L));
float transAmount = (1.0 - saturate(rawNdotL)) * 0.8;
float translucency = backLight * transAmount;
// Same ambient as voxel faces for color consistency
float3 ambient = float3(0.15, 0.18, 0.25);
// Wrap + strong translucency to minimize light/dark contrast
float3 diffuse = sunColor.rgb * (wrap * 0.85 + translucency * 0.6);
lit = texColor * (diffuse + ambient);
}
return float4(lit, 1.0);
}