@@ -119,6 +119,139 @@ 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+ // Calculate parallax texture coordinate offset
219+ float2 ParallaxOffset (Texture2D <float4 > heightMapSampler, SamplerState samplerState, float2 texCoords,
220+ float3 viewDir, float3 normal, float3 tangent, float3 bitangent,
221+ float inHeightScale, float inHeightBias)
222+ {
223+ // Transform view direction to tangent space
224+ float3 tanEyePos;
225+ tanEyePos.x = dot (tangent.xyz, viewDir.xyz);
226+ tanEyePos.y = dot (bitangent.xyz, viewDir.xyz);
227+ tanEyePos.z = dot (normal.xyz, viewDir.xyz);
228+ tanEyePos = normalize (tanEyePos);
229+
230+ // Clamp Z to avoid division issues at grazing angles
231+ float zLimit = 0.1f ;
232+ float clampedZ = max (zLimit, tanEyePos.z);
233+
234+ // Calculate view-dependent step count for quality/performance balance
235+ float VdotN = abs (dot (normalize (viewDir.xyz), normalize (normal.xyz)));
236+ float numberOfSteps = lerp (POM_MAX_STEPS, POM_MIN_STEPS, VdotN);
237+
238+ // Apply global scale based on view angle for smooth falloff
239+ float globalScale = saturate (numberOfSteps - 1.0f ) * saturate (VdotN / POM_VDOTN_BLEND_FACTOR);
240+
241+ // Calculate max parallax offset and bias offset
242+ float2 maxParallaxOffset = (-tanEyePos.xy / clampedZ) * inHeightScale * globalScale;
243+ float2 heightBiasOffset = (tanEyePos.xy / clampedZ) * inHeightBias * globalScale;
244+
245+ // Trace through height field
246+ float height = TraceHeight (heightMapSampler, samplerState, texCoords, maxParallaxOffset, heightBiasOffset, (int )numberOfSteps);
247+
248+ // Calculate final texture coordinate offset
249+ float2 texCoordOffset = heightBiasOffset + (maxParallaxOffset * (1.0f - height));
250+
251+ return texCoordOffset;
252+ }
253+
254+
122255
123256
124257float3 BasicLighting (float4 lightcolour, float4 ambcolour, float pclit)
0 commit comments