Skip to content
62 changes: 62 additions & 0 deletions package/Shaders/Common/Triplanar.hlsli
Original file line number Diff line number Diff line change
@@ -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<float4> 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<float4> 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<float4> 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
19 changes: 12 additions & 7 deletions package/Shaders/Lighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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));
Comment thread
Dlizzio marked this conversation as resolved.
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);
Expand Down
Loading