Skip to content

Commit 2fd3b39

Browse files
committed
Add GEVulkanHiZDepth for screen space reflection
1 parent a4ca4c7 commit 2fd3b39

17 files changed

+891
-50
lines changed

data/shaders/ge_shaders/displace_mask.frag

Lines changed: 205 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,179 @@ layout(push_constant) uniform Constants
1919
#include "../utils/screen_space_reflection.frag"
2020

2121
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
24195

25196
void main()
26197
{
@@ -62,13 +233,42 @@ void main()
62233

63234
// fallback to skybox
64235
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+
65244
vec4 result;
66245
vec2 viewport_scale = u_camera.m_viewport.zw / u_camera.m_screensize;
67246
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+
}
70270
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. ||
72272
viewport_coords.y < 0. || viewport_coords.y > 1.)
73273
{
74274
result = fallback;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
2+
3+
layout(binding = 0) uniform sampler2D u_depth;
4+
layout(binding = 1, r32f) uniform writeonly image2D u_hiz_depth;
5+
6+
layout(push_constant) uniform PushConstants
7+
{
8+
ivec3 u_offset_miplevel;
9+
} pc;
10+
11+
void main()
12+
{
13+
ivec2 dst = ivec2(gl_GlobalInvocationID.xy);
14+
ivec2 current_size = imageSize(u_hiz_depth);
15+
16+
if (dst.x >= current_size.x || dst.y >= current_size.y)
17+
return;
18+
19+
if (pc.u_offset_miplevel.z == 0)
20+
{
21+
// at level 0, do a 1:1 copy
22+
float d = texelFetch(u_depth, dst + pc.u_offset_miplevel.xy, 0).r;
23+
imageStore(u_hiz_depth, dst, vec4(d));
24+
}
25+
else
26+
{
27+
// at higher mip levels, read a 2×2 block from previous level
28+
ivec2 src = dst * 2;
29+
int prev_level = pc.u_offset_miplevel.z - 1;
30+
ivec2 prev_size = textureSize(u_depth, prev_level);
31+
float d0 = texelFetch(u_depth, src + ivec2(0, 0), prev_level).r;
32+
float d1 = texelFetch(u_depth, src + ivec2(1, 0), prev_level).r;
33+
float d2 = texelFetch(u_depth, src + ivec2(0, 1), prev_level).r;
34+
float d3 = texelFetch(u_depth, src + ivec2(1, 1), prev_level).r;
35+
float min_depth = min(min(d0, d1), min(d2, d3));
36+
//float max_depth = max(max(d0, d1), max(d2, d3));
37+
bool extra_sample_x = (current_size.x * 2) < prev_size.x;
38+
bool extra_sample_y = (current_size.y * 2) < prev_size.y;
39+
if (extra_sample_x)
40+
{
41+
float d4 = texelFetch(u_depth, src + ivec2(2, 0), prev_level).r;
42+
float d5 = texelFetch(u_depth, src + ivec2(2, 1), prev_level).r;
43+
min_depth = min(min_depth, min(d4, d5));
44+
//max_depth = max(max_depth, max(d4, d5));
45+
}
46+
if (extra_sample_y)
47+
{
48+
float d6 = texelFetch(u_depth, src + ivec2(0, 2), prev_level).r;
49+
float d7 = texelFetch(u_depth, src + ivec2(1, 2), prev_level).r;
50+
min_depth = min(min_depth, min(d6, d7));
51+
//max_depth = max(max_depth, max(d6, d7));
52+
}
53+
if (extra_sample_x && extra_sample_y)
54+
{
55+
float d8 = texelFetch(u_depth, src + ivec2(2, 2), prev_level).r;
56+
min_depth = min(min_depth, d8);
57+
//max_depth = max(max_depth, d8);
58+
}
59+
imageStore(u_hiz_depth, dst, vec4(min_depth));
60+
//imageStore(u_hiz_depth, dst, vec4(min_depth, max_depth, 0.0, 0.0));
61+
}
62+
}

data/shaders/ge_shaders/utils/constants_utils.glsl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ layout (constant_id = 1) const float u_specular_levels_minus_one = 0.0;
33
layout (constant_id = 2) const bool u_deferred = false;
44
layout (constant_id = 3) const bool u_has_skybox = true;
55
layout (constant_id = 4) const bool u_ssr = false;
6+
layout (constant_id = 5) const uint u_hiz_iterations = 0;
67

78
vec3 convertColor(vec3 input_color)
89
{

data/shaders/sp_displace_ssr.frag

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ void main()
4949

5050
// fallback to skybox
5151
vec4 fallback = texture(u_skybox_texture, world_reflection);
52+
53+
// early exit if normal is facing camera too directly (no meaningful reflection)
54+
if (normal.z < -0.75)
55+
{
56+
o_displace_ssr = fallback;
57+
return;
58+
}
59+
5260
vec4 result;
5361
vec2 viewport_scale = vec2(1.0);
5462
vec2 viewport_offset = vec2(0.0);

lib/graphics_engine/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ set(GE_SOURCES
8888
src/ge_vulkan_environment_map.cpp
8989
src/ge_vulkan_fbo_texture.cpp
9090
src/ge_vulkan_features.cpp
91+
src/ge_vulkan_hiz_depth.cpp
9192
src/ge_vulkan_light_handler.cpp
9293
src/ge_vulkan_mesh_cache.cpp
9394
src/ge_vulkan_mesh_scene_node.cpp

lib/graphics_engine/include/ge_main.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ enum GEScreenSpaceReflectionType : unsigned
3333
{
3434
GSSRT_DISABLED = 0,
3535
GSSRT_FAST,
36+
GSSRT_HIZ,
37+
GSSRT_HIZ100 = GSSRT_HIZ,
38+
GSSRT_HIZ200,
39+
GSSRT_HIZ400,
40+
GSSRT_COUNT,
3641
};
3742

3843
struct GEConfig

0 commit comments

Comments
 (0)