@@ -19,8 +19,179 @@ layout(push_constant) uniform Constants
19
19
#include "../utils/ screen_space_reflection.frag"
20
20
21
21
layout (set = 2 , binding = 2 ) uniform samplerCube u_skybox_texture;
22
- layout (set = 3 , binding = 2 ) uniform sampler2D u_displace_color;
23
- layout (set = 3 , binding = 3 ) uniform sampler2DShadow u_depth;
22
+ layout (set = 3 , binding = 0 ) uniform sampler2D u_displace_color;
23
+ layout (set = 3 , binding = 1 ) uniform sampler2DShadow u_depth;
24
+ layout (set = 3 , binding = 2 ) uniform sampler2D u_hiz_depth;
25
+
26
+ #ifdef PBR_ENABLED
27
+
28
+ // Start tracing in this level.
29
+ #define HIZ_START_LEVEL 0
30
+ // Stop tracing if current level is higher than this. (higher level means lower value)
31
+ #define HIZ_STOP_LEVEL 0
32
+ #define HIZ_MAX_LEVEL 6
33
+
34
+ // Set to 1 to disable HiZ and perform naive linear search.
35
+ #define DEBUG_LINEAR_SEARCH 0
36
+ #define MAX_THICKNESS 0.001
37
+
38
+ vec3 intersectDepthPlane(vec3 o, vec3 d, float z)
39
+ {
40
+ return o + d * z;
41
+ }
42
+
43
+ // Index of the cell that contains the given 2D position.
44
+ ivec2 getCell(vec2 screenUV, ivec2 cellCount)
45
+ {
46
+ return ivec2 (screenUV * cellCount);
47
+ }
48
+
49
+ // The number of cells in the quad tree at the given level.
50
+ ivec2 getCellCount(int level)
51
+ {
52
+ return textureSize(u_hiz_depth, level);
53
+ }
54
+
55
+ // Returns screen space position of the intersection
56
+ // between o + d*t and the closest cell boundary at current HiZ level.
57
+ vec3 intersectCellBoundary(
58
+ vec3 pos, vec3 dir,
59
+ ivec2 cell, ivec2 cellCount,
60
+ vec2 crossStep, vec2 crossOffset)
61
+ {
62
+ vec3 intersection = vec3 (0.0 );
63
+
64
+ vec2 index = cell + crossStep;
65
+ vec2 boundary = index / vec2 (cellCount); // Screen space position of the boundary
66
+ boundary += crossOffset;
67
+
68
+ vec2 delta = boundary - pos.xy;
69
+ delta /= dir.xy;
70
+ float t = min (delta.x, delta.y);
71
+
72
+ intersection = intersectDepthPlane(pos, dir, t);
73
+ return intersection;
74
+ }
75
+
76
+ bool crossedCellBoundary(ivec2 oldCellIx, ivec2 newCellIx)
77
+ {
78
+ return any (notEqual (oldCellIx, newCellIx));
79
+ }
80
+
81
+ // Minimum depth of the current cell in the current HiZ level.
82
+ float getMinDepthPlane(ivec2 cellIx, int level)
83
+ {
84
+ return texelFetch(u_hiz_depth, cellIx, level).x;
85
+ }
86
+
87
+ float getMaxTraceDistance(vec3 p, vec3 v)
88
+ {
89
+ vec3 traceDistances;
90
+ if (v.x < 0.0 )
91
+ traceDistances.x = p.x / (- v.x);
92
+ else
93
+ traceDistances.x = (1.0 - p.x) / v.x;
94
+
95
+ if (v.y < 0.0 )
96
+ traceDistances.y = p.y / (- v.y);
97
+ else
98
+ traceDistances.y = (1.0 - p.y) / v.y;
99
+
100
+ if (v.z < 0.0 )
101
+ traceDistances.z = p.z / (- v.z);
102
+ else
103
+ traceDistances.z = (1.0 - p.z) / v.z;
104
+
105
+ return min (traceDistances.x, min (traceDistances.y, traceDistances.z));
106
+ }
107
+
108
+ // p : Screen space position
109
+ // v : Screen space reflection direction
110
+ // hitPointSS : Returns screen space hit point
111
+ // Return value : Whether RT actually hit a surface
112
+ bool traceHiZ(vec3 p, vec3 v, out vec2 hitPointSS)
113
+ {
114
+ const int maxLevel = min (HIZ_MAX_LEVEL, textureQueryLevels(u_hiz_depth) - 1 ); // Last mip level
115
+ float maxTraceDistance = getMaxTraceDistance(p, v);
116
+
117
+ // Get the cell cross direction and a small offset to enter
118
+ // the next cell when doing cell crossing.
119
+ vec2 crossStep = vec2 (v.x >= 0 ? 1 : - 1 , v.y >= 0 ? 1 : - 1 );
120
+ vec2 crossOffset = crossStep / u_camera.m_viewport.zw / 128 .;
121
+ crossStep = clamp (crossStep, 0.0 , 1.0 );
122
+
123
+ // Set current ray to the original screen coordinate and depth.
124
+ vec3 ray = p;
125
+ float minZ = ray.z;
126
+ float maxZ = ray.z + v.z * maxTraceDistance;
127
+ float deltaZ = maxZ - minZ;
128
+
129
+ vec3 o = ray;
130
+ vec3 d = v * maxTraceDistance;
131
+
132
+ int level = HIZ_START_LEVEL;
133
+ int deepestLevel = level;
134
+ #if DEBUG_LINEAR_SEARCH
135
+ level = 0 ;
136
+ #endif
137
+ uint iterations = 0 ;
138
+ bool isBackwardRay = v.z < 0 ;
139
+ float rayDir = isBackwardRay ? - 1.0 : 1.0 ;
140
+
141
+ // Cross to next cell s.t. we don't get a self-intersection immediately.
142
+ ivec2 startCellCount = getCellCount(level);
143
+ ivec2 rayCell = getCell(ray.xy, startCellCount);
144
+ ray = intersectCellBoundary(o, d, rayCell, startCellCount, crossStep, crossOffset * 64 .);
145
+
146
+ while (level >= HIZ_STOP_LEVEL && ray.z * rayDir <= maxZ * rayDir &&
147
+ iterations < u_hiz_iterations)
148
+ {
149
+ // Get the cell number of our current ray.
150
+ ivec2 cellCount = getCellCount(level);
151
+ ivec2 oldCellIx = getCell(ray.xy, cellCount);
152
+
153
+ // Get the minimum depth plane in which the current ray resides.
154
+ float cellMinZ = getMinDepthPlane(oldCellIx, level);
155
+
156
+ // Intersect only if ray depth is below the minimum depth plane.
157
+ vec3 tempRay;
158
+ if (cellMinZ > ray.z && ! isBackwardRay)
159
+ tempRay = intersectDepthPlane(o, d, (cellMinZ - minZ) / deltaZ);
160
+ else
161
+ tempRay = ray;
162
+
163
+ ivec2 newCellIx = getCell(tempRay.xy, cellCount);
164
+ float thickness = level == 0 ? (ray.z - cellMinZ) : 0 ;
165
+
166
+ bool crossed = (isBackwardRay && (cellMinZ > ray.z))
167
+ || (thickness > MAX_THICKNESS) || crossedCellBoundary(oldCellIx, newCellIx);
168
+
169
+ if (crossed)
170
+ {
171
+ ray = intersectCellBoundary(o, d, oldCellIx, cellCount, crossStep, crossOffset);
172
+ level = min (maxLevel, level + 1 );
173
+ deepestLevel = max (deepestLevel, level);
174
+ #if DEBUG_LINEAR_SEARCH
175
+ level = 0 ;
176
+ #endif
177
+ }
178
+ else
179
+ {
180
+ ray = tempRay;
181
+ level = level - 1 ;
182
+ }
183
+
184
+ iterations += 1 ;
185
+ }
186
+
187
+ // Results
188
+ // debugDeepestLevel = deepestLevel;
189
+ // debugIterations = iterations;
190
+ hitPointSS = ray.xy;
191
+ return level < HIZ_STOP_LEVEL && iterations < u_hiz_iterations;
192
+ }
193
+
194
+ #endif
24
195
25
196
void main()
26
197
{
@@ -62,13 +233,42 @@ void main()
62
233
63
234
// fallback to skybox
64
235
vec4 fallback = texture(u_skybox_texture, world_reflection);
236
+
237
+ // early exit if normal is facing camera too directly (no meaningful reflection)
238
+ if (normal.z < - 0.75 )
239
+ {
240
+ o_displace_ssr = fallback;
241
+ return ;
242
+ }
243
+
65
244
vec4 result;
66
245
vec2 viewport_scale = u_camera.m_viewport.zw / u_camera.m_screensize;
67
246
vec2 viewport_offset = u_camera.m_viewport.xy / u_camera.m_screensize;
68
- vec2 coords = RayCast(reflected, xpos, u_camera.m_projection_matrix,
69
- viewport_scale, viewport_offset, u_depth);
247
+ bool hit = true;
248
+ vec2 coords;
249
+ if (u_hiz_iterations == 0 )
250
+ {
251
+ coords = RayCast(reflected, xpos, u_camera.m_projection_matrix,
252
+ viewport_scale, viewport_offset, u_depth);
253
+ }
254
+ else
255
+ {
256
+ vec3 positionSS = CalcCoordFromPosition(xpos,
257
+ u_camera.m_projection_matrix, vec2 (1.0 ), vec2 (0.0 ));
258
+ vec3 positionCS = positionSS;
259
+ positionCS.xy = 2.0 * positionCS.xy - 1.0 ;
260
+ vec3 position2VS = xpos + 1000.0 * reflected;
261
+ vec4 position2CS = u_camera.m_projection_matrix * vec4 (position2VS, 1.0 );
262
+ position2CS /= position2CS.w;
263
+ vec3 position2SS = position2CS.xyz;
264
+ position2SS.xy = vec2 (0.5 ) + 0.5 * position2SS.xy;
265
+ vec3 reflectionDirSS = normalize (position2SS - positionSS);
266
+ // Trace HiZ to find the hit point.
267
+ hit = traceHiZ(positionSS, reflectionDirSS, coords);
268
+ coords = coords * viewport_scale + viewport_offset;
269
+ }
70
270
vec2 viewport_coords = (coords - viewport_offset) / viewport_scale;
71
- if (viewport_coords.x < 0 . || viewport_coords.x > 1 . ||
271
+ if (! hit || viewport_coords.x < 0 . || viewport_coords.x > 1 . ||
72
272
viewport_coords.y < 0 . || viewport_coords.y > 1 .)
73
273
{
74
274
result = fallback;
0 commit comments