bvle-voxels/shaders/voxelTopingPS.hlsl

85 lines
3.4 KiB
HLSL
Raw Normal View History

// 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;
};
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);
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
2026-03-26 20:00:33 +01:00
// Diffuse floor: even fully back-facing blades get some sun contribution
// Simulates light scattering through dense grass (cheap GI approximation)
wrap = max(wrap, 0.35);
// 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));
2026-03-26 20:00:33 +01:00
float transAmount = (1.0 - saturate(rawNdotL)) * 0.6;
float translucency = backLight * transAmount;
2026-03-26 20:00:33 +01:00
// Higher ambient for vegetation: grass blades bounce light between each other
// (simplified inter-reflection / GI), brighter than stone ambient
float3 ambient = float3(0.30, 0.33, 0.35);
2026-03-26 20:00:33 +01:00
// Wrap + translucency for soft, low-contrast vegetation shading
float3 diffuse = sunColor.rgb * (wrap * 0.88 + translucency * 0.55);
lit = texColor * (diffuse + ambient);
}
output.color = float4(lit, 1.0);
output.normal = float4(N, 0.0);
return output;
}