diff --git a/package/Shaders/Common/Triplanar.hlsli b/package/Shaders/Common/Triplanar.hlsli new file mode 100644 index 0000000000..1b920827ac --- /dev/null +++ b/package/Shaders/Common/Triplanar.hlsli @@ -0,0 +1,62 @@ +#ifndef TRIPLANAR_HLSLI +#define TRIPLANAR_HLSLI + +namespace Triplanar +{ + static const float BLEND_SHARPNESS = 6.0; // Power for weight computation; higher = sharper axis transitions + static const float STRETCH_CUTOFF = 0.4; // ~cos(66°) — per-axis alignment below this produces visible stretching + + /// Compute triplanar blend weights using face normal mask and smooth vertex normal blend. + float3 GetWeights(float3 vertexNormal, float3 faceNormal, float sharpness = BLEND_SHARPNESS) + { + float3 mask = step(STRETCH_CUTOFF, abs(faceNormal)); + float3 w = pow(abs(vertexNormal), sharpness) * mask; + return w / (dot(w, 1.0) + EPSILON_DIVISION); + } + + /// Weighted triplanar sample blending all 3 planes — stable for alpha/fade values. + float4 Sample(Texture2D tex, SamplerState samp, float3 worldPos, float3 weights, float scale) + { + return tex.Sample(samp, worldPos.yz * scale) * weights.x + + tex.Sample(samp, worldPos.xz * scale) * weights.y + + tex.Sample(samp, worldPos.xy * scale) * weights.z; + } + + /// Compute gradients for stochastic triplanar sampling, pre-computed before branching. + void ComputeGradients(float3 worldPos, float scale, out float3 dPdx, out float3 dPdy) + { + dPdx = ddx(worldPos * scale); + dPdy = ddy(worldPos * scale); + } + + /// Stochastic triplanar: select one projection plane via noise, reducing 3 texture reads to 1. + float4 SampleStochastic(Texture2D tex, SamplerState samp, float3 worldPos, float3 weights, float scale, float noise) + { + float3 dPdx, dPdy; + ComputeGradients(worldPos, scale, dPdx, dPdy); + + if (noise < weights.x) + return tex.SampleGrad(samp, worldPos.yz * scale, dPdx.yz, dPdy.yz); + if (noise < weights.x + weights.y) + return tex.SampleGrad(samp, worldPos.xz * scale, dPdx.xz, dPdy.xz); + return tex.SampleGrad(samp, worldPos.xy * scale, dPdx.xy, dPdy.xy); + } + + /// Stochastic triplanar with mip bias via gradient scaling. + float4 SampleStochasticBias(Texture2D tex, SamplerState samp, float3 worldPos, float3 weights, float scale, float bias, float noise) + { + float3 dPdx, dPdy; + ComputeGradients(worldPos, scale, dPdx, dPdy); + float biasScale = exp2(bias); + dPdx *= biasScale; + dPdy *= biasScale; + + if (noise < weights.x) + return tex.SampleGrad(samp, worldPos.yz * scale, dPdx.yz, dPdy.yz); + if (noise < weights.x + weights.y) + return tex.SampleGrad(samp, worldPos.xz * scale, dPdx.xz, dPdy.xz); + return tex.SampleGrad(samp, worldPos.xy * scale, dPdx.xy, dPdy.xy); + } +} + +#endif // TRIPLANAR_HLSLI diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index 0e2587f52b..18ae8c89a8 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -10,6 +10,7 @@ #include "Common/Random.hlsli" #include "Common/SharedData.hlsli" #include "Common/Skinned.hlsli" +#include "Common/Triplanar.hlsli" #if defined(FACEGEN) || defined(FACEGEN_RGB_TINT) # define SKIN @@ -1977,8 +1978,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float projWeight = 0; # if defined(PROJECTED_UV) - float2 projNoiseUv = ProjectedUVParams.zz * input.TexCoord0.zw; - float projNoise = TexCharacterLightProjNoiseSampler.Sample(SampCharacterLightProjNoiseSampler, projNoiseUv).x; + float3 projWorldPos = input.WorldPosition.xyz + FrameBuffer::CameraPosAdjust[eyeIndex].xyz; + float3 triFaceNormal = normalize(-cross(ddx(input.WorldPosition.xyz), ddy(input.WorldPosition.xyz))); + float3 triWeights = Triplanar::GetWeights(tbnTr[2], triFaceNormal); + float projNoise = Triplanar::Sample(TexCharacterLightProjNoiseSampler, SampCharacterLightProjNoiseSampler, projWorldPos, triWeights, ProjectedUVParams.z).x; float3 texProj = normalize(input.TexProj); # if defined(TREE_ANIM) || defined(LODOBJECTSHD) float vertexAlpha = 1; @@ -1993,18 +1996,20 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) if (projWeight < 0) discard; + rawBaseColor = Triplanar::SampleStochasticBias(TexColorSampler, SampColorSampler, projWorldPos, triWeights, ProjectedUVParams2.y, SharedData::MipBias, screenNoise); + baseColor = float4(Color::Diffuse(rawBaseColor.rgb), rawBaseColor.a); worldNormal.xyz = projectedNormal; # if defined(SNOW) psout.Parameters.y = 1; # endif // SNOW # elif !defined(FACEGEN) && !defined(MULTI_LAYER_PARALLAX) && !defined(PARALLAX) && !defined(SPARKLE) if (ProjectedUVParams3.w > 0.5) { - float2 projNormalDiffuseUv = ProjectedUVParams3.x * projNoiseUv; - float3 projNormal = TransformNormal(TexProjNormalSampler.Sample(SampProjNormalSampler, projNormalDiffuseUv).xyz); - float2 projDetailNormalUv = ProjectedUVParams3.y * projNoiseUv; - float3 projDetailNormal = TexProjDetail.Sample(SampProjDetailSampler, projDetailNormalUv).xyz; + float diffuseNormalScale = ProjectedUVParams3.x * ProjectedUVParams.z; + float3 projNormal = TransformNormal(Triplanar::SampleStochastic(TexProjNormalSampler, SampProjNormalSampler, projWorldPos, triWeights, diffuseNormalScale, screenNoise).xyz); + float detailNormalScale = ProjectedUVParams3.y * ProjectedUVParams.z; + float3 projDetailNormal = Triplanar::SampleStochastic(TexProjDetail, SampProjDetailSampler, projWorldPos, triWeights, detailNormalScale, screenNoise).xyz; float3 finalProjNormal = normalize(TransformNormal(projDetailNormal) * float3(1, 1, projNormal.z) + float3(projNormal.xy, 0)); - float3 projBaseColor = Color::ColorToLinear(TexProjDiffuseSampler.Sample(SampProjDiffuseSampler, projNormalDiffuseUv).xyz) * Color::ColorToLinear(ProjectedUVParams2.xyz); + float3 projBaseColor = Color::ColorToLinear(Triplanar::SampleStochastic(TexProjDiffuseSampler, SampProjDiffuseSampler, projWorldPos, triWeights, diffuseNormalScale, screenNoise).xyz) * Color::ColorToLinear(ProjectedUVParams2.xyz); projectedMaterialWeight = smoothstep(0, 1, 5 * (0.1 + projWeight)); # if defined(TRUE_PBR) projBaseColor = saturate(EnvmapData.xyz * projBaseColor);