Skip to content

Commit 468d641

Browse files
committed
add parallax support
1 parent 485d56b commit 468d641

30 files changed

+777
-50
lines changed

CodeWalker.Shaders/BasicPS.hlsl

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,43 @@
33

44
float4 main(VS_OUTPUT input) : SV_TARGET
55
{
6+
// Calculate parallax offset if height mapping is enabled
7+
float2 parallaxTexOffset = float2(0, 0);
8+
float parallaxSelfShadow = 1.0;
9+
if (EnableHeightMap && RenderMode == 0)
10+
{
11+
float3 viewDir = -normalize(input.CamRelPos); // Negate to get direction FROM surface TO camera
12+
float3 norm0 = normalize(input.Normal);
13+
float3 tang0 = normalize(input.Tangent.xyz);
14+
float3 bitang0 = normalize(input.Bitangent.xyz);
15+
parallaxTexOffset = ParallaxOffset(
16+
Heightmap, TextureSS, input.Texcoord0,
17+
viewDir, norm0, tang0, bitang0,
18+
heightScale, heightBias);
19+
20+
// Parallax self-shadow, transform light dir to tangent space and trace
21+
float3 tanLightDir;
22+
tanLightDir.x = dot(tang0, GlobalLights.LightDir.xyz);
23+
tanLightDir.y = dot(bitang0, GlobalLights.LightDir.xyz);
24+
tanLightDir.z = dot(norm0, GlobalLights.LightDir.xyz);
25+
float shadowAmount = TraceSelfShadow(Heightmap, TextureSS,
26+
input.Texcoord0 + parallaxTexOffset,
27+
tanLightDir, 1.0, heightScale);
28+
parallaxSelfShadow = 1.0 - shadowAmount * PARALLAX_SELF_SHADOW_AMOUNT;
29+
}
30+
31+
// Apply parallax offset to base texture coordinates
32+
float2 texc0 = input.Texcoord0 + parallaxTexOffset;
33+
634
float4 c = float4(0.5, 0.5, 0.5, 1);
735
if (RenderMode == 0) c = float4(1, 1, 1, 1);
836
if (EnableTexture > 0)
937
{
10-
float2 texc = input.Texcoord0;
38+
float2 texc = texc0;
1139
if (RenderMode >= 5)
1240
{
13-
if (RenderSamplerCoord == 2) texc = input.Texcoord1;
14-
else if (RenderSamplerCoord == 3) texc = input.Texcoord2;
41+
if (RenderSamplerCoord == 2) texc = input.Texcoord1 + parallaxTexOffset;
42+
else if (RenderSamplerCoord == 3) texc = input.Texcoord2 + parallaxTexOffset;
1543
}
1644

1745
c = Colourmap.Sample(TextureSS, texc);
@@ -80,8 +108,8 @@ float4 main(VS_OUTPUT input) : SV_TARGET
80108
if (RenderMode == 0)
81109
{
82110

83-
float4 nv = Bumpmap.Sample(TextureSS, input.Texcoord0); //sample r1.xyzw, v2.xyxx, t3.xyzw, s3 (BumpSampler)
84-
float4 sv = Specmap.Sample(TextureSS, input.Texcoord0); //sample r2.xyzw, v2.xyxx, t4.xyzw, s4 (SpecSampler)
111+
float4 nv = Bumpmap.Sample(TextureSS, texc0); //sample r1.xyzw, v2.xyxx, t3.xyzw, s3 (BumpSampler)
112+
float4 sv = Specmap.Sample(TextureSS, texc0); //sample r2.xyzw, v2.xyxx, t4.xyzw, s4 (SpecSampler)
85113

86114

87115
float2 nmv = nv.xy;
@@ -92,7 +120,7 @@ float4 main(VS_OUTPUT input) : SV_TARGET
92120
if (EnableDetailMap)
93121
{
94122
//detail normalmapp
95-
r0.xy = input.Texcoord0 * detailSettings.zw; //mul r0.xy, v2.xyxx, detailSettings.zwzz
123+
r0.xy = texc0 * detailSettings.zw; //mul r0.xy, v2.xyxx, detailSettings.zwzz
96124
r0.zw = r0.xy * 3.17; //mul r0.zw, r0.xxxy, l(0.000000, 0.000000, 3.170000, 3.170000)
97125
r0.xy = Detailmap.Sample(TextureSS, r0.xy).xy - 0.5; //sample r1.xyzw, r0.xyxx, t2.xyzw, s2 (DetailSampler) //mad r0.xy, r1.xyxx, l(2.000000, 2.000000, 0.000000, 0.000000), l(-1.000000, -1.000000, 0.000000, 0.000000)
98126
r0.zw = Detailmap.Sample(TextureSS, r0.zw).xy - 0.5; //sample r1.xyzw, r0.zwzz, t2.xyzw, s2 (DetailSampler) //mad r0.zw, r1.xxxy, l(0.000000, 0.000000, 2.000000, 2.000000), l(0.000000, 0.000000, -1.000000, -1.000000) //r0.zw = r0.zw*0.5; //mul r0.zw, r0.zzzw, l(0.000000, 0.000000, 0.500000, 0.500000)
@@ -161,7 +189,7 @@ float4 main(VS_OUTPUT input) : SV_TARGET
161189

162190
float4 fc = c;
163191

164-
c.rgb = FullLighting(c.rgb, spec, norm, input.Colour0, GlobalLights, EnableShadows, input.Shadows.x, input.LightShadow);
192+
c.rgb = FullLighting(c.rgb, spec, norm, input.Colour0, GlobalLights, EnableShadows, input.Shadows.x, input.LightShadow, parallaxSelfShadow);
165193

166194

167195
if (IsEmissive==1)

CodeWalker.Shaders/BasicPS.hlsli

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Texture2D<float4> Specmap : register(t3);
66
Texture2D<float4> Detailmap : register(t4);
77
Texture2D<float4> Colourmap2 : register(t5);
88
Texture2D<float4> TintPalette : register(t6);
9+
Texture2D<float4> Heightmap : register(t7);
910
SamplerState TextureSS : register(s0);
1011

1112

@@ -39,6 +40,10 @@ cbuffer PSGeomVars : register(b2)
3940
float wetnessMultiplier;
4041
uint SpecOnly;
4142
float4 TextureAlphaMask;
43+
uint EnableHeightMap;
44+
float heightScale;
45+
float heightBias;
46+
float Pad0;
4247
}
4348

4449

CodeWalker.Shaders/BasicPS_Deferred.hlsl

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,32 @@
33

44
PS_OUTPUT main(VS_OUTPUT input)
55
{
6+
// Calculate parallax offset if height mapping is enabled
7+
float2 parallaxTexOffset = float2(0, 0);
8+
if (EnableHeightMap && RenderMode == 0)
9+
{
10+
float3 viewDir = -normalize(input.CamRelPos); // Negate to get direction FROM surface TO camera
11+
parallaxTexOffset = ParallaxOffset(
12+
Heightmap, TextureSS, input.Texcoord0,
13+
viewDir, normalize(input.Normal),
14+
normalize(input.Tangent.xyz), normalize(input.Bitangent.xyz),
15+
heightScale, heightBias);
16+
}
17+
18+
// Apply parallax offset to base texture coordinates
19+
float2 texc0 = input.Texcoord0 + parallaxTexOffset;
20+
621
float4 c = float4(0.5, 0.5, 0.5, 1);
722
if (RenderMode == 0) c = float4(1, 1, 1, 1);
823
if (EnableTexture > 0)
924
{
10-
float2 texc = input.Texcoord0;
25+
float2 texc = texc0;
1126
if (RenderMode >= 5)
1227
{
1328
if (RenderSamplerCoord == 2)
14-
texc = input.Texcoord1;
29+
texc = input.Texcoord1 + parallaxTexOffset;
1530
else if (RenderSamplerCoord == 3)
16-
texc = input.Texcoord2;
31+
texc = input.Texcoord2 + parallaxTexOffset;
1732
}
1833

1934
c = Colourmap.Sample(TextureSS, texc);
@@ -85,8 +100,8 @@ PS_OUTPUT main(VS_OUTPUT input)
85100
if (RenderMode == 0)
86101
{
87102

88-
float4 nv = Bumpmap.Sample(TextureSS, input.Texcoord0);
89-
float4 sv = Specmap.Sample(TextureSS, input.Texcoord0);
103+
float4 nv = Bumpmap.Sample(TextureSS, texc0);
104+
float4 sv = Specmap.Sample(TextureSS, texc0);
90105

91106

92107
float2 nmv = nv.xy;
@@ -97,7 +112,7 @@ PS_OUTPUT main(VS_OUTPUT input)
97112
if (EnableDetailMap)
98113
{
99114
//detail normalmapp
100-
r0.xy = input.Texcoord0 * detailSettings.zw;
115+
r0.xy = texc0 * detailSettings.zw;
101116
r0.zw = r0.xy * 3.17;
102117
r0.xy = Detailmap.Sample(TextureSS, r0.xy).xy - 0.5;
103118
r0.zw = Detailmap.Sample(TextureSS, r0.zw).xy - 0.5;

CodeWalker.Shaders/Common.hlsli

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,163 @@ float3 NormalMap(float2 nmv, float bumpinezz, float3 norm, float3 tang, float3 b
119119
}
120120

121121

122+
// POM constants
123+
#define POM_MIN_STEPS 3
124+
#define POM_MAX_STEPS 16
125+
#define POM_VDOTN_BLEND_FACTOR 0.25f
126+
#define POM_HEIGHT_SCALE 0.1f // Global parallax strength multiplier (1.0 = full, 0.5 = half)
127+
128+
// Distance-based POM fade constants (reduces noise at steep angles/distance)
129+
#define POM_DISTANCE_START 5.0f // Distance where fade begins
130+
#define POM_DISTANCE_END 50.0f // Distance where POM is fully disabled
131+
132+
// Binary search refinement for more precise intersection (reduces ring artifacts at close range)
133+
#define POM_BINARY_SEARCH_STEPS 5 // Number of binary search iterations after linear search
134+
#define POM_CLOSE_DISTANCE 2.0f // Distance threshold for close-range step boost
135+
#define POM_CLOSE_STEP_MULTIPLIER 2.0f // Step multiplier when very close to surface
136+
137+
// Distance fade lookup table (5 control points for smooth non-linear falloff)
138+
// Based on GTA V pomWeights table
139+
#define NUM_POM_CTRL_POINTS 5
140+
static const float pomWeights[NUM_POM_CTRL_POINTS] = {
141+
1.0f, // Full quality at close range
142+
0.9f,
143+
0.5f, // 50% at mid distance
144+
0.1f,
145+
0.0f // Disabled at far distance
146+
};
147+
148+
// Compute smooth distance-based fade for POM steps
149+
float ComputePOMDistanceFade(float distanceBlend)
150+
{
151+
if (distanceBlend >= 1.0f)
152+
return 0.0f;
153+
154+
// Find the nearest control points and interpolate
155+
int startPoint = clamp(int(distanceBlend * (NUM_POM_CTRL_POINTS - 1)), 0, NUM_POM_CTRL_POINTS - 2);
156+
int endPoint = startPoint + 1;
157+
158+
float t = distanceBlend * (NUM_POM_CTRL_POINTS - 1) - float(startPoint);
159+
return lerp(pomWeights[startPoint], pomWeights[endPoint], t);
160+
}
161+
162+
// Performs relief mapping by tracing through the height field
163+
float TraceHeight(Texture2D<float4> heightMapSampler, SamplerState samplerState, float2 texCoords, float2 direction, float2 bias, int maxNumberOfSteps)
164+
{
165+
if (maxNumberOfSteps == 0)
166+
{
167+
return 0.0f;
168+
}
169+
170+
float heightStep = 1.0f / float(maxNumberOfSteps);
171+
float2 offsetPerStep = direction * heightStep;
172+
173+
float currentBound = 1.0f;
174+
float previousBound = currentBound;
175+
176+
float2 texCoordOffset = bias;
177+
178+
// Use derivatives for proper mip selection to avoid aliasing
179+
float2 ddx0 = ddx(texCoords.xy);
180+
float2 ddy0 = ddy(texCoords.xy);
181+
182+
float currentHeight = heightMapSampler.SampleGrad(samplerState, texCoords.xy, ddx0, ddy0).r + 1e-6f;
183+
float previousHeight = currentHeight;
184+
185+
[unroll(POM_MAX_STEPS)]
186+
for (int s = 0; s < maxNumberOfSteps; ++s)
187+
{
188+
if (currentHeight < currentBound)
189+
{
190+
previousBound = currentBound;
191+
previousHeight = currentHeight;
192+
193+
currentBound -= heightStep;
194+
texCoordOffset += offsetPerStep;
195+
currentHeight = heightMapSampler.SampleGrad(samplerState, texCoords + texCoordOffset, ddx0, ddy0).r;
196+
}
197+
else
198+
{
199+
break;
200+
}
201+
}
202+
203+
// Interpolate between the two points to find a more precise height
204+
float currentDelta = currentBound - currentHeight;
205+
float previousDelta = previousBound - previousHeight;
206+
float denominator = previousDelta - currentDelta;
207+
208+
float finalHeight = currentHeight;
209+
210+
if (denominator > 0)
211+
{
212+
finalHeight = ((currentBound * previousDelta) - (previousBound * currentDelta)) / denominator;
213+
}
214+
215+
return clamp(finalHeight, 0.0, 1.0f);
216+
}
217+
218+
// Parallax self-shadow: traces through heightmap in light direction to find occlusion
219+
// Returns shadow factor (0 = fully lit, 1 = fully in shadow)
220+
float TraceSelfShadow(Texture2D<float4> heightMapSampler, SamplerState samplerState, float2 texCoords, float3 tanLightDir, float edgeWeight, float hScale)
221+
{
222+
float2 inXY = (tanLightDir.xy * hScale * edgeWeight) / max(tanLightDir.z, 0.01f);
223+
224+
// Sample base height at current (displaced) position
225+
float sh0 = heightMapSampler.SampleLevel(samplerState, texCoords, 0).r;
226+
227+
// Trace 7 samples along light direction with increasing weight for closer occlusion
228+
float shA = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.88, 0).r - sh0 - 0.88) * 1;
229+
float sh9 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.77, 0).r - sh0 - 0.77) * 2;
230+
float sh8 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.66, 0).r - sh0 - 0.66) * 4;
231+
float sh7 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.55, 0).r - sh0 - 0.55) * 6;
232+
float sh6 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.44, 0).r - sh0 - 0.44) * 8;
233+
float sh5 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.33, 0).r - sh0 - 0.33) * 10;
234+
float sh4 = (heightMapSampler.SampleLevel(samplerState, texCoords + inXY * 0.22, 0).r - sh0 - 0.22) * 12;
235+
236+
float finalHeight = max(max(max(max(max(max(shA, sh9), sh8), sh7), sh6), sh5), sh4);
237+
return saturate(finalHeight);
238+
}
239+
240+
#define PARALLAX_SELF_SHADOW_AMOUNT 0.95f
241+
242+
// Calculate parallax texture coordinate offset
243+
float2 ParallaxOffset(Texture2D<float4> heightMapSampler, SamplerState samplerState, float2 texCoords,
244+
float3 viewDir, float3 normal, float3 tangent, float3 bitangent,
245+
float inHeightScale, float inHeightBias)
246+
{
247+
// Transform view direction to tangent space
248+
float3 tanEyePos;
249+
tanEyePos.x = dot(tangent.xyz, viewDir.xyz);
250+
tanEyePos.y = dot(bitangent.xyz, viewDir.xyz);
251+
tanEyePos.z = dot(normal.xyz, viewDir.xyz);
252+
tanEyePos = normalize(tanEyePos);
253+
254+
// Clamp Z to avoid division issues at grazing angles
255+
float zLimit = 0.1f;
256+
float clampedZ = max(zLimit, tanEyePos.z);
257+
258+
// Calculate view-dependent step count for quality/performance balance
259+
float VdotN = abs(dot(normalize(viewDir.xyz), normalize(normal.xyz)));
260+
float numberOfSteps = lerp(POM_MAX_STEPS, POM_MIN_STEPS, VdotN);
261+
262+
// Apply global scale based on view angle for smooth falloff
263+
float globalScale = saturate(numberOfSteps - 1.0f) * saturate(VdotN / POM_VDOTN_BLEND_FACTOR);
264+
265+
// Calculate max parallax offset and bias offset
266+
float2 maxParallaxOffset = (-tanEyePos.xy / clampedZ) * inHeightScale * globalScale;
267+
float2 heightBiasOffset = (tanEyePos.xy / clampedZ) * inHeightBias * globalScale;
268+
269+
// Trace through height field
270+
float height = TraceHeight(heightMapSampler, samplerState, texCoords, maxParallaxOffset, heightBiasOffset, (int)numberOfSteps);
271+
272+
// Calculate final texture coordinate offset
273+
float2 texCoordOffset = heightBiasOffset + (maxParallaxOffset * (1.0f - height));
274+
275+
return texCoordOffset;
276+
}
277+
278+
122279

123280

124281
float3 BasicLighting(float4 lightcolour, float4 ambcolour, float pclit)

CodeWalker.Shaders/Shadowmap.hlsli

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ float ShadowAmount(float4 shadowcoord, float shadowdepth)//, inout float4 colour
225225

226226

227227

228-
float3 FullLighting(float3 diff, float3 spec, float3 norm, float4 vc0, uniform ShaderGlobalLightParams globalLights, uint enableShadows, float shadowdepth, float4 shadowcoord)
228+
float3 FullLighting(float3 diff, float3 spec, float3 norm, float4 vc0, uniform ShaderGlobalLightParams globalLights, uint enableShadows, float shadowdepth, float4 shadowcoord, float selfShadow = 1.0)
229229
{
230230
float lf = saturate(dot(norm, globalLights.LightDir.xyz));
231231

@@ -241,6 +241,9 @@ float3 FullLighting(float3 diff, float3 spec, float3 norm, float4 vc0, uniform S
241241
}
242242
}
243243

244+
// Apply parallax self-shadow to cascade shadow
245+
shadowlit *= selfShadow;
246+
244247
lf *= shadowlit;
245248

246249
float3 speclit = spec*shadowlit;

0 commit comments

Comments
 (0)