UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
bend_sss_cpu.h
Go to the documentation of this file.
1#pragma once
2
3// Copyright 2023 Sony Interactive Entertainment.
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17// If you have feedback, or found this code useful, we'd love to hear from you.
18// https://www.bendstudio.com
19// https://www.twitter.com/bendstudio
20//
21// We are *always* looking for talented graphics and technical programmers!
22// https://www.bendstudio.com/careers
23
24// Common screen space shadow projection code (CPU):
25//--------------------------------------------------------------
26
27namespace Bend
28{
29 // Generating a screen-space-shadow requires a number of Compute Shader dispatches
30 // The compute shader reads from a depth buffer, and writes a single-channel texture of the same dimensions
31 // Each dispatch is of the same compute shader, (see bend_sss_gpu.h).
32 // The number of dispatches required varies based on the on-screen location of the light.
33 // Typically there will be just one or two dispatches when the light is off-screen, and 4 to 6 when the light is on-screen.
34 // Syncing the GPU between individual dispatches is not required
35
36 // These structures and function are used to generate the number of dispatches, the wave count of each dispatch (X/Y/Z) and shader parameters for each dispatch
37
39 {
40 int WaveCount[3]; // Compute Shader Dispatch(X,Y,Z) wave counts X/Y/Z
41 int WaveOffset_Shader[2]; // This value is passed in to shader. It will be different for each dispatch
42 };
43
45 {
46 float LightCoordinate_Shader[4]; // This value is passed in to shader, this will be the same value for all dispatches for this light
47
48 DispatchData Dispatch[8]; // List of dispatches (max count is 8)
49 int DispatchCount; // Number of compute dispatches written to the list
50 };
51
52
53 // Helper functions
54 inline int bend_min(const int a, const int b) { return a > b ? b : a; }
55 inline int bend_max(const int a, const int b) { return a > b ? a : b; }
56
57
58 // Call this function on the CPU to get a list of Compute Shader dispatches required to generate a screen-space shadow for a given light
59 // Syncing the GPU between individual dispatches is not required
60 //
61 // inLightProjection: Homogeneous coordinate of the light, result of {light} * {ViewProjectionMatrix}, (without W divide)
62 // For infinite directional lights, use {light} = float4(normalized light direction, 0) and for point/spot lights use {light} = float4(light world position, 1)
63 //
64 // inViewportSize: width/height of the render target
65 //
66 // inRenderBounds: 2D Screen Bounds of the light within the viewport, inclusive. [0,0], [width,height] for full-screen.
67 // Note; the shader will still read/write outside of these bounds (by a maximum of 2 * WAVE_SIZE pixels), due to how the wavefront projection works.
68 //
69 // inExpandedDepthRange: Set to true if the rendering API expects z/w coordinate output from a vertex shader to be a [-1,+1] expanded range, and becomes [0,1] range in the depth buffer. Typically this is false.
70 //
71 // inWaveSize: Wavefront size of the compiled compute shader (currently only tested with 64)
72 //
74 {
75 DispatchList result = {};
76
77 // Floating point division in the shader has a practical limit for precision when the light is *very* far off screen (~1m pixels+)
78 // So when computing the light XY coordinate, use an adjusted w value to handle these extreme values
80 float FP_limit = 0.000002f * (float)inWaveSize;
81
84
85 // Need precise XY pixel coordinates of the light
86 result.LightCoordinate_Shader[0] = ((inLightProjection[0] / xy_light_w) * +0.5f + 0.5f) * (float)inViewportSize[0];
87 result.LightCoordinate_Shader[1] = ((inLightProjection[1] / xy_light_w) * -0.5f + 0.5f) * (float)inViewportSize[1];
89 result.LightCoordinate_Shader[3] = inLightProjection[3] > 0 ? 1 : -1;
90
92 {
93 result.LightCoordinate_Shader[2] = result.LightCoordinate_Shader[2] * 0.5f + 0.5f;
94 }
95
96 int light_xy[2] = { (int)(result.LightCoordinate_Shader[0] + 0.5f), (int)(result.LightCoordinate_Shader[1] + 0.5f) };
97
98 // Make the bounds inclusive, relative to the light
99 const int biased_bounds[4] =
100 {
102 -(inMaxRenderBounds[1] - light_xy[1]),
104 -(inMinRenderBounds[1] - light_xy[1]),
105 };
106
107 // Process 4 quadrants around the light center,
108 // They each form a rectangle with one corner on the light XY coordinate
109 // If the rectangle isn't square, it will need breaking in two on the larger axis
110 // 0 = bottom left, 1 = bottom right, 2 = top left, 2 = top right
111 for (int q = 0; q < 4; q++)
112 {
113 // Quads 0 and 3 needs to be +1 vertically, 1 and 2 need to be +1 horizontally
114 bool vertical = q == 0 || q == 3;
115
116 // Bounds relative to the quadrant
117 const int bounds[4] =
118 {
119 bend_max(0, ((q & 1) ? biased_bounds[0] : -biased_bounds[2])) / inWaveSize,
120 bend_max(0, ((q & 2) ? biased_bounds[1] : -biased_bounds[3])) / inWaveSize,
121 bend_max(0, (((q & 1) ? biased_bounds[2] : -biased_bounds[0]) + inWaveSize * (vertical ? 1 : 2) - 1)) / inWaveSize,
122 bend_max(0, (((q & 2) ? biased_bounds[3] : -biased_bounds[1]) + inWaveSize * (vertical ? 2 : 1) - 1)) / inWaveSize,
123 };
124
125 if ((bounds[2] - bounds[0]) > 0 && (bounds[3] - bounds[1]) > 0)
126 {
127 int bias_x = (q == 2 || q == 3) ? 1 : 0;
128 int bias_y = (q == 1 || q == 3) ? 1 : 0;
129
130 DispatchData& disp = result.Dispatch[result.DispatchCount++];
131
132 disp.WaveCount[0] = inWaveSize;
133 disp.WaveCount[1] = bounds[2] - bounds[0];
134 disp.WaveCount[2] = bounds[3] - bounds[1];
135 disp.WaveOffset_Shader[0] = ((q & 1) ? bounds[0] : -bounds[2]) + bias_x;
136 disp.WaveOffset_Shader[1] = ((q & 2) ? -bounds[3] : bounds[1]) + bias_y;
137
138 // We want the far corner of this quadrant relative to the light,
139 // as we need to know where the diagonal light ray intersects with the edge of the bounds
141 if (q == 1) axis_delta = +biased_bounds[2] + biased_bounds[1];
142 if (q == 2) axis_delta = -biased_bounds[0] - biased_bounds[3];
143 if (q == 3) axis_delta = -biased_bounds[2] + biased_bounds[3];
144
146
147 if (axis_delta > 0)
148 {
149 DispatchData& disp2 = result.Dispatch[result.DispatchCount++];
150
151 // Take copy of current volume
152 disp2 = disp;
153
154 if (q == 0)
155 {
156 // Split on Y, split becomes -1 larger on x
158 disp.WaveCount[2] -= disp2.WaveCount[2];
159 disp2.WaveOffset_Shader[1] = disp.WaveOffset_Shader[1] + disp.WaveCount[2];
160 disp2.WaveOffset_Shader[0]--;
161 disp2.WaveCount[1] ++;
162 }
163 if (q == 1)
164 {
165 // Split on X, split becomes +1 larger on y
166 disp2.WaveCount[1] = bend_min(disp.WaveCount[1], axis_delta);
167 disp.WaveCount[1] -= disp2.WaveCount[1];
168 disp2.WaveOffset_Shader[0] = disp.WaveOffset_Shader[0] + disp.WaveCount[1];
169 disp2.WaveCount[2] ++;
170 }
171 if (q == 2)
172 {
173 // Split on X, split becomes -1 larger on y
174 disp2.WaveCount[1] = bend_min(disp.WaveCount[1], axis_delta);
175 disp.WaveCount[1] -= disp2.WaveCount[1];
176 disp.WaveOffset_Shader[0] += disp2.WaveCount[1];
177 disp2.WaveCount[2] ++;
178 disp2.WaveOffset_Shader[1]--;
179 }
180 if (q == 3)
181 {
182 // Split on Y, split becomes +1 larger on x
183 disp2.WaveCount[2] = bend_min(disp.WaveCount[2], axis_delta);
184 disp.WaveCount[2] -= disp2.WaveCount[2];
185 disp.WaveOffset_Shader[1] += disp2.WaveCount[2];
186 disp2.WaveCount[1] ++;
187 }
188
189 // Remove if too small
190 if (disp2.WaveCount[1] <= 0 || disp2.WaveCount[2] <= 0)
191 {
192 disp2 = result.Dispatch[--result.DispatchCount];
193 }
194 if (disp.WaveCount[1] <= 0 || disp.WaveCount[2] <= 0)
195 {
196 disp = result.Dispatch[--result.DispatchCount];
197 }
198 }
199 }
200 }
201
202 // Scale the shader values by the wave count, the shader expects this
203 for (int i = 0; i < result.DispatchCount; i++)
204 {
205 result.Dispatch[i].WaveOffset_Shader[0] *= inWaveSize;
206 result.Dispatch[i].WaveOffset_Shader[1] *= inWaveSize;
207 }
208
209 return result;
210 }
211}
212
213
214/*
215* Common Problems, and tips to solve them:
216*
217* The shader doesn't compile?
218* - The shader is only tested with HLSL (DXC compiler) and PS5 using a HLSL->PS5 warpper
219* It should be possible to compile for DX11 with FXC, with features removed (early-out's use of wave intrinsics is not supported in DX11)
220* Other shader languages (e.g., glsl) are unsupported and will require manual conversion
221*
222* I have it compiled and running, but I'm seeing complete nonsense?
223* - Start by enabling 'DebugOutputWaveIndex' to visualize the wavefront layout.
224* You should see wavefronts aligned and projected towards the light position / direction. If not, then 'inLightProjection' is probably wrong.
225*
226* Struggling to get 'inLightProjection' right?
227* - Think of this as similar to what a vertex-shader would output for position export; that is, a 4-component transformed position (e.g., SV_POSITION output from the VS)
228* This usually means:
229* inLightProjection = float4(position,1) * ViewProjectionMatrix - positional light
230* or:
231* inLightProjection = float4(direction,0) * ViewProjectionMatrix - directional light
232*
233* Almost everything is in shadow?
234* Is the background around an object casting a shadow on to it?
235* Does everything look like a weird paper cut-out?
236* There is a big shadow halo around the light source?
237* - FarDepthValue / NearDepthValue may not be set correctly
238* These are the values you'd see in the depth buffer for objects at the near and far clip plane (typically far = 0, near = 1)
239* In very rare cases, the vertex shader may be expected to output a [-1,+1] z-range, which becomes [0,1] in the depth buffer. If this is the case, enable 'inExpandedZRange'.
240*
241* There are small glitchy lines all over the output?
242* - Try with/without 'USE_HALF_PIXEL_OFFSET' defined in the shader. This is enabled by default for HLSL.
243*
244* Invalid shadows occasionally appear from offscreen / at the edge of the screen?
245* - The 'PointBorderSampler' may not be setup correctly. The shader will intentionally read from offscreen, so the sampler must return an invalid value (e.g., FarDepthValue) in these cases by using clamp-to-border.
246*
247* Light is always coming from the same direction no matter how I rotate the camera?
248* - You may be using just the ProjectionMatrix, not the ViewProjectionMatrix when computing 'inLightProjection'.
249*
250* Shadow is extremely thick, or very faded?
251* - Try scaling 'SurfaceThickness' up and down, start with 0.005, and scale up/down in multiples of 2. Make sure to scale BilinearThreshold in a similar way.
252*
253* I see lots of striated patterns on flat surfaces?
254* - 'BilinearThreshold' may not be set to an ideal value (enable 'DebugOutputEdgeMask' to debug it), or try enabling 'IgnoreEdgePixels'
255* - These issues can be more common in otherwise occluded areas when BilinearSamplingOffsetMode is false, and may be prevalent if visualizing the shadow - but not noticeable in a lit scene.
256*/
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
USkinnedMeshComponent float
Definition SkinnedMeshComponent.h:60
Definition bend_sss_cpu.h:28
DispatchList BuildDispatchList(float inLightProjection[4], int inViewportSize[2], int inMinRenderBounds[2], int inMaxRenderBounds[2], bool inExpandedZRange=false, int inWaveSize=64)
Definition bend_sss_cpu.h:73
int bend_max(const int a, const int b)
Definition bend_sss_cpu.h:55
int bend_min(const int a, const int b)
Definition bend_sss_cpu.h:54
Definition bend_sss_cpu.h:39
int WaveOffset_Shader[2]
Definition bend_sss_cpu.h:41
int WaveCount[3]
Definition bend_sss_cpu.h:40
Definition bend_sss_cpu.h:45
float LightCoordinate_Shader[4]
Definition bend_sss_cpu.h:46
int DispatchCount
Definition bend_sss_cpu.h:49
DispatchData Dispatch[8]
Definition bend_sss_cpu.h:48