A game about forced loneliness, made by TACStudios
1// This is implementation of parallax occlusion mapping (POM)
2// This function require that the caller define a callback for the height sampling name ComputePerPixelHeightDisplacement
3// A PerPixelHeightDisplacementParam is used to provide all data necessary to calculate the heights to ComputePerPixelHeightDisplacement it doesn't need to be
4// visible by the POM algorithm.
5// This function is compatible with tiled uv.
6// it return the offset to apply to the UVSet provide in PerPixelHeightDisplacementParam
7// viewDirTS is view vector in texture space matching the UVSet
8// ref: https://www.gamedev.net/resources/_/technical/graphics-programming-and-theory/a-closer-look-at-parallax-occlusion-mapping-r3262
9
10#ifndef POM_USER_DATA_PARAMETERS
11 #define POM_USER_DATA_PARAMETERS
12#endif
13
14#ifndef POM_USER_DATA_ARGUMENTS
15 #define POM_USER_DATA_ARGUMENTS
16#endif
17
18real2
19#ifdef POM_NAME_ID
20MERGE_NAME(ParallaxOcclusionMapping,POM_NAME_ID)
21#else
22ParallaxOcclusionMapping
23#endif
24(real lod, real lodThreshold, int numSteps, real3 viewDirTS, PerPixelHeightDisplacementParam ppdParam, out real outHeight POM_USER_DATA_PARAMETERS)
25{
26 // Convention: 1.0 is top, 0.0 is bottom - POM is always inward, no extrusion
27 real stepSize = 1.0 / (real)numSteps;
28
29 // View vector is from the point to the camera, but we want to raymarch from camera to point, so reverse the sign
30 // The length of viewDirTS vector determines the furthest amount of displacement:
31 // real parallaxLimit = -length(viewDirTS.xy) / viewDirTS.z;
32 // real2 parallaxDir = normalize(Out.viewDirTS.xy);
33 // real2 parallaxMaxOffsetTS = parallaxDir * parallaxLimit;
34 // Above code simplify to
35 real2 parallaxMaxOffsetTS = (viewDirTS.xy / -viewDirTS.z);
36 real2 texOffsetPerStep = stepSize * parallaxMaxOffsetTS;
37
38 // Do a first step before the loop to init all value correctly
39 real2 texOffsetCurrent = real2(0.0, 0.0);
40 real prevHeight = ComputePerPixelHeightDisplacement(texOffsetCurrent, lod, ppdParam POM_USER_DATA_ARGUMENTS);
41 texOffsetCurrent += texOffsetPerStep;
42 real currHeight = ComputePerPixelHeightDisplacement(texOffsetCurrent, lod, ppdParam POM_USER_DATA_ARGUMENTS);
43 real rayHeight = 1.0 - stepSize; // Start at top less one sample
44
45 // Linear search
46 for (int stepIndex = 0; stepIndex < numSteps; ++stepIndex)
47 {
48 // Have we found a height below our ray height ? then we have an intersection
49 if (currHeight > rayHeight)
50 break; // end the loop
51
52 prevHeight = currHeight;
53 rayHeight -= stepSize;
54 texOffsetCurrent += texOffsetPerStep;
55
56 // Sample height map which in this case is stored in the alpha channel of the normal map:
57 currHeight = ComputePerPixelHeightDisplacement(texOffsetCurrent, lod, ppdParam POM_USER_DATA_ARGUMENTS);
58 }
59
60 // Found below and above points, now perform line interesection (ray) with piecewise linear heightfield approximation
61
62 // Refine the search with secant method
63#define POM_SECANT_METHOD 1
64#if POM_SECANT_METHOD
65
66 real pt0 = rayHeight + stepSize;
67 real pt1 = rayHeight;
68 real delta0 = pt0 - prevHeight;
69 real delta1 = pt1 - currHeight;
70
71 real delta;
72 real2 offset;
73
74 // Secant method to affine the search
75 // Ref: Faster Relief Mapping Using the Secant Method - Eric Risser
76 for (int i = 0; i < 3; ++i)
77 {
78 // intersectionHeight is the height [0..1] for the intersection between view ray and heightfield line
79 real intersectionHeight = (pt0 * delta1 - pt1 * delta0) / (delta1 - delta0);
80 // Retrieve offset require to find this intersectionHeight
81 offset = (1 - intersectionHeight) * texOffsetPerStep * numSteps;
82
83 currHeight = ComputePerPixelHeightDisplacement(offset, lod, ppdParam POM_USER_DATA_ARGUMENTS);
84
85 delta = intersectionHeight - currHeight;
86
87 if ((delta >= -0.01) && (delta <= 0.01))
88 break;
89
90 // intersectionHeight < currHeight => new lower bounds
91 if (delta < 0.0)
92 {
93 delta1 = delta;
94 pt1 = intersectionHeight;
95 }
96 else
97 {
98 delta0 = delta;
99 pt0 = intersectionHeight;
100 }
101 }
102
103#else // regular POM intersection
104
105 //real pt0 = rayHeight + stepSize;
106 //real pt1 = rayHeight;
107 //real delta0 = pt0 - prevHeight;
108 //real delta1 = pt1 - currHeight;
109 //real intersectionHeight = (pt0 * delta1 - pt1 * delta0) / (delta1 - delta0);
110 //real2 offset = (1 - intersectionHeight) * texOffsetPerStep * numSteps;
111
112 // A bit more optimize
113 real delta0 = currHeight - rayHeight;
114 real delta1 = (rayHeight + stepSize) - prevHeight;
115 real ratio = delta0 / (delta0 + delta1);
116 real2 offset = texOffsetCurrent - ratio * texOffsetPerStep;
117
118 currHeight = ComputePerPixelHeightDisplacement(offset, lod, ppdParam POM_USER_DATA_ARGUMENTS);
119
120#endif
121
122 outHeight = currHeight;
123
124 // Fade the effect with lod (allow to avoid pop when switching to a discrete LOD mesh)
125 offset *= (1.0 - saturate(lod - lodThreshold));
126
127 return offset;
128}