1 #ifndef UNITY_POSTFX_AMBIENT_OCCLUSION
2 #define UNITY_POSTFX_AMBIENT_OCCLUSION
4 #include "../StdLib.hlsl"
5 #include "../Colors.hlsl"
9 // Options for further customization
12 // By default, a 5-tap Gaussian with the linear sampling technique is used
13 // in the bilateral noise filter. It can be replaced with a 7-tap Gaussian
14 // with adaptive sampling by enabling the macro below. Although the
15 // differences are not noticeable in most cases, it may provide preferable
16 // results with some special usage (e.g. NPR without textureing).
17 // #define BLUR_HIGH_QUALITY
19 // By default, a fixed sampling pattern is used in the AO estimator. Although
20 // this gives preferable results in most cases, a completely random sampling
21 // pattern could give aesthetically better results. Disable the macro below
22 // to use such a random pattern instead of the fixed one.
23 #define FIX_SAMPLING_PATTERN
25 // The SampleNormal function normalizes samples from G-buffer because
26 // they're possibly unnormalized. We can eliminate this if it can be said
27 // that there is no wrong shader that outputs unnormalized normals.
28 // #define VALIDATE_NORMALS
30 // The constant below determines the contrast of occlusion. This allows
31 // users to control over/under occlusion. At the moment, this is not exposed
32 // to the editor because it's rarely useful.
33 static const float kContrast = 0.6;
35 // The constant below controls the geometry-awareness of the bilateral
36 // filter. The higher value, the more sensitive it is.
37 static const float kGeometryCoeff = 0.8;
39 // The constants below are used in the AO estimator. Beta is mainly used
40 // for suppressing self-shadowing noise, and Epsilon is used to prevent
41 // calculation underflow. See the paper (Morgan 2011 http://goo.gl/2iz3P)
42 // for further details of these constants.
43 static const float kBeta = 0.002;
47 // System built-in variables
48 TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
49 TEXTURE2D_SAMPLER2D(_CameraGBufferTexture2, sampler_CameraGBufferTexture2);
50 TEXTURE2D_SAMPLER2D(_CameraDepthTexture, sampler_CameraDepthTexture);
51 TEXTURE2D_SAMPLER2D(_CameraDepthNormalsTexture, sampler_CameraDepthNormalsTexture);
53 float4 _MainTex_TexelSize;
59 #if !defined(SHADER_API_GLES)
60 #define SAMPLE_COUNT _AOParams.w
62 // GLES2: In many cases, dynamic looping is not supported.
63 #define SAMPLE_COUNT 3
66 // Source texture properties
67 TEXTURE2D_SAMPLER2D(_SAOcclusionTexture, sampler_SAOcclusionTexture);
68 float4 _SAOcclusionTexture_TexelSize;
71 #define INTENSITY _AOParams.x
72 #define RADIUS _AOParams.y
73 #define DOWNSAMPLE _AOParams.z
75 // Accessors for packed AO/normal buffer
76 half4 PackAONormal(half ao, half3 n)
78 return half4(ao, n * 0.5 + 0.5);
81 half GetPackedAO(half4 p)
86 half3 GetPackedNormal(half4 p)
88 return p.gba * 2.0 - 1.0;
91 // Boundary check for depth sampler
92 // (returns a very large value if it lies out of bounds)
93 float CheckBounds(float2 uv, float d)
95 float ob = any(uv < 0) + any(uv > 1);
96 #if defined(UNITY_REVERSED_Z)
104 // Depth/normal sampling functions
105 float SampleDepth(float2 uv)
107 float d = Linear01Depth(SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, sampler_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(uv), 0));
108 return d * _ProjectionParams.z + CheckBounds(uv, d);
111 float3 SampleNormal(float2 uv)
113 #if defined(SOURCE_GBUFFER)
114 float3 norm = SAMPLE_TEXTURE2D(_CameraGBufferTexture2, sampler_CameraGBufferTexture2, uv).xyz;
115 norm = norm * 2 - any(norm); // gets (0,0,0) when norm == 0
116 norm = mul((float3x3)unity_WorldToCamera, norm);
117 #if defined(VALIDATE_NORMALS)
118 norm = normalize(norm);
122 float4 cdn = SAMPLE_TEXTURE2D(_CameraDepthNormalsTexture, sampler_CameraDepthNormalsTexture, uv);
123 return DecodeViewNormalStereo(cdn) * float3(1.0, 1.0, -1.0);
127 float SampleDepthNormal(float2 uv, out float3 normal)
129 normal = SampleNormal(UnityStereoTransformScreenSpaceTex(uv));
130 return SampleDepth(uv);
133 // Normal vector comparer (for geometry-aware weighting)
134 half CompareNormal(half3 d1, half3 d2)
136 return smoothstep(kGeometryCoeff, 1.0, dot(d1, d2));
139 // Trigonometric function utility
140 float2 CosSin(float theta)
143 sincos(theta, sn, cs);
144 return float2(cs, sn);
147 // Pseudo random number generator with 2D coordinates
148 float UVRandom(float u, float v)
150 float f = dot(float2(12.9898, 78.233), float2(u, v));
151 return frac(43758.5453 * sin(f));
154 // Check if the camera is perspective.
155 // (returns 1.0 when orthographic)
156 float CheckPerspective(float x)
158 return lerp(x, 1.0, unity_OrthoParams.w);
161 // Reconstruct view-space position from UV and depth.
162 // p11_22 = (unity_CameraProjection._11, unity_CameraProjection._22)
163 // p13_31 = (unity_CameraProjection._13, unity_CameraProjection._23)
164 float3 ReconstructViewPos(float2 uv, float depth, float2 p11_22, float2 p13_31)
166 return float3((uv * 2.0 - 1.0 - p13_31) / p11_22 * CheckPerspective(depth), depth);
169 // Sample point picker
170 float3 PickSamplePoint(float2 uv, float index)
172 // Uniformaly distributed points on a unit sphere
173 // http://mathworld.wolfram.com/SpherePointPicking.html
174 #if defined(FIX_SAMPLING_PATTERN)
175 float gn = GradientNoise(uv * DOWNSAMPLE);
176 // FIXEME: This was added to avoid a NVIDIA driver issue.
178 float u = frac(UVRandom(0.0, index + uv.x * 1e-10) + gn) * 2.0 - 1.0;
179 float theta = (UVRandom(1.0, index + uv.x * 1e-10) + gn) * TWO_PI;
181 float u = UVRandom(uv.x + _Time.x, uv.y + index) * 2.0 - 1.0;
182 float theta = UVRandom(-uv.x - _Time.x, uv.y + index) * TWO_PI;
184 float3 v = float3(CosSin(theta) * sqrt(1.0 - u * u), u);
185 // Make them distributed between [0, _Radius]
186 float l = sqrt((index + 1.0) / SAMPLE_COUNT) * RADIUS;
191 // Distance-based AO estimator based on Morgan 2011
192 // "Alchemy screen-space ambient obscurance algorithm"
193 // http://graphics.cs.williams.edu/papers/AlchemyHPG11/
195 float4 FragAO(VaryingsDefault i) : SV_Target
197 float2 uv = i.texcoord;
199 // Parameters used in coordinate conversion
200 float3x3 proj = (float3x3)unity_CameraProjection;
201 float2 p11_22 = float2(unity_CameraProjection._11, unity_CameraProjection._22);
202 float2 p13_31 = float2(unity_CameraProjection._13, unity_CameraProjection._23);
204 // View space normal and depth
206 float depth_o = SampleDepthNormal(uv, norm_o);
208 // Reconstruct the view-space position.
209 float3 vpos_o = ReconstructViewPos(uv, depth_o, p11_22, p13_31);
213 for (int s = 0; s < int(SAMPLE_COUNT); s++)
216 #if defined(SHADER_API_D3D11)
217 // This 'floor(1.0001 * s)' operation is needed to avoid a NVidia shader issue. This issue
218 // is only observed on DX11.
219 float3 v_s1 = PickSamplePoint(uv, floor(1.0001 * s));
221 float3 v_s1 = PickSamplePoint(uv, s);
224 v_s1 = faceforward(v_s1, -norm_o, v_s1);
225 float3 vpos_s1 = vpos_o + v_s1;
227 // Reproject the sample point
228 float3 spos_s1 = mul(proj, vpos_s1);
229 float2 uv_s1_01 = (spos_s1.xy / CheckPerspective(vpos_s1.z) + 1.0) * 0.5;
231 // Depth at the sample point
232 float depth_s1 = SampleDepth(uv_s1_01);
234 // Relative position of the sample point
235 float3 vpos_s2 = ReconstructViewPos(uv_s1_01, depth_s1, p11_22, p13_31);
236 float3 v_s2 = vpos_s2 - vpos_o;
238 // Estimate the obscurance value
239 float a1 = max(dot(v_s2, norm_o) - kBeta * depth_o, 0.0);
240 float a2 = dot(v_s2, v_s2) + EPSILON;
244 ao *= RADIUS; // Intensity normalization
246 // Apply other parameters.
247 ao = PositivePow(ao * INTENSITY / SAMPLE_COUNT, kContrast);
249 // Apply fog when enabled (forward-only)
250 #if (APPLY_FORWARD_FOG)
251 float d = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.texcoordStereo));
252 d = ComputeFogDistance(d);
256 return PackAONormal(ao, norm_o);
259 // Geometry-aware separable bilateral filter
260 float4 FragBlur(VaryingsDefault i) : SV_Target
262 #if defined(BLUR_HORIZONTAL)
263 // Horizontal pass: Always use 2 texels interval to match to
264 // the dither pattern.
265 float2 delta = float2(_MainTex_TexelSize.x * 2.0, 0.0);
267 // Vertical pass: Apply _Downsample to match to the dither
268 // pattern in the original occlusion buffer.
269 float2 delta = float2(0.0, _MainTex_TexelSize.y / DOWNSAMPLE * 2.0);
272 #if defined(BLUR_HIGH_QUALITY)
274 // High quality 7-tap Gaussian with adaptive sampling
276 half4 p0 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo);
277 half4 p1a = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - delta));
278 half4 p1b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + delta));
279 half4 p2a = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - delta * 2.0));
280 half4 p2b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + delta * 2.0));
281 half4 p3a = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - delta * 3.2307692308));
282 half4 p3b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + delta * 3.2307692308));
284 #if defined(BLUR_SAMPLE_CENTER_NORMAL)
285 half3 n0 = SampleNormal(i.texcoordStereo);
287 half3 n0 = GetPackedNormal(p0);
290 half w0 = 0.37004405286;
291 half w1a = CompareNormal(n0, GetPackedNormal(p1a)) * 0.31718061674;
292 half w1b = CompareNormal(n0, GetPackedNormal(p1b)) * 0.31718061674;
293 half w2a = CompareNormal(n0, GetPackedNormal(p2a)) * 0.19823788546;
294 half w2b = CompareNormal(n0, GetPackedNormal(p2b)) * 0.19823788546;
295 half w3a = CompareNormal(n0, GetPackedNormal(p3a)) * 0.11453744493;
296 half w3b = CompareNormal(n0, GetPackedNormal(p3b)) * 0.11453744493;
299 s = GetPackedAO(p0) * w0;
300 s += GetPackedAO(p1a) * w1a;
301 s += GetPackedAO(p1b) * w1b;
302 s += GetPackedAO(p2a) * w2a;
303 s += GetPackedAO(p2b) * w2b;
304 s += GetPackedAO(p3a) * w3a;
305 s += GetPackedAO(p3b) * w3b;
307 s /= w0 + w1a + w1b + w2a + w2b + w3a + w3b;
311 // Fater 5-tap Gaussian with linear sampling
312 half4 p0 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo);
313 half4 p1a = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - delta * 1.3846153846));
314 half4 p1b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + delta * 1.3846153846));
315 half4 p2a = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord - delta * 3.2307692308));
316 half4 p2b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, UnityStereoTransformScreenSpaceTex(i.texcoord + delta * 3.2307692308));
318 #if defined(BLUR_SAMPLE_CENTER_NORMAL)
319 half3 n0 = SampleNormal(i.texcoordStereo);
321 half3 n0 = GetPackedNormal(p0);
324 half w0 = 0.2270270270;
325 half w1a = CompareNormal(n0, GetPackedNormal(p1a)) * 0.3162162162;
326 half w1b = CompareNormal(n0, GetPackedNormal(p1b)) * 0.3162162162;
327 half w2a = CompareNormal(n0, GetPackedNormal(p2a)) * 0.0702702703;
328 half w2b = CompareNormal(n0, GetPackedNormal(p2b)) * 0.0702702703;
331 s = GetPackedAO(p0) * w0;
332 s += GetPackedAO(p1a) * w1a;
333 s += GetPackedAO(p1b) * w1b;
334 s += GetPackedAO(p2a) * w2a;
335 s += GetPackedAO(p2b) * w2b;
337 s /= w0 + w1a + w1b + w2a + w2b;
341 return PackAONormal(s, n0);
344 // Gamma encoding (only needed in gamma lighting mode)
345 half EncodeAO(half x)
347 #if UNITY_COLORSPACE_GAMMA
348 return 1.0 - max(LinearToSRGB(1.0 - saturate(x)), 0.0);
354 // Geometry-aware bilateral filter (single pass/small kernel)
355 half BlurSmall(TEXTURE2D_ARGS(tex, samp), float2 uv, float2 delta)
357 half4 p0 = SAMPLE_TEXTURE2D(tex, samp, UnityStereoTransformScreenSpaceTex(uv));
358 half4 p1 = SAMPLE_TEXTURE2D(tex, samp, UnityStereoTransformScreenSpaceTex(uv + float2(-delta.x, -delta.y)));
359 half4 p2 = SAMPLE_TEXTURE2D(tex, samp, UnityStereoTransformScreenSpaceTex(uv + float2( delta.x, -delta.y)));
360 half4 p3 = SAMPLE_TEXTURE2D(tex, samp, UnityStereoTransformScreenSpaceTex(uv + float2(-delta.x, delta.y)));
361 half4 p4 = SAMPLE_TEXTURE2D(tex, samp, UnityStereoTransformScreenSpaceTex(uv + float2( delta.x, delta.y)));
363 half3 n0 = GetPackedNormal(p0);
366 half w1 = CompareNormal(n0, GetPackedNormal(p1));
367 half w2 = CompareNormal(n0, GetPackedNormal(p2));
368 half w3 = CompareNormal(n0, GetPackedNormal(p3));
369 half w4 = CompareNormal(n0, GetPackedNormal(p4));
372 s = GetPackedAO(p0) * w0;
373 s += GetPackedAO(p1) * w1;
374 s += GetPackedAO(p2) * w2;
375 s += GetPackedAO(p3) * w3;
376 s += GetPackedAO(p4) * w4;
378 return s / (w0 + w1 + w2 + w3 + w4);
381 // Final composition shader
382 float4 FragComposition(VaryingsDefault i) : SV_Target
384 float2 delta = _SAOcclusionTexture_TexelSize.xy / DOWNSAMPLE;
385 half ao = BlurSmall(TEXTURE2D_PARAM(_SAOcclusionTexture, sampler_SAOcclusionTexture), i.texcoord, delta);
387 return float4(ao * _AOColor, ao);
390 #if !SHADER_API_GLES // Excluding the MRT pass under GLES2
392 struct CompositionOutput
394 half4 gbuffer0 : SV_Target0;
395 half4 gbuffer3 : SV_Target1;
398 CompositionOutput FragCompositionGBuffer(VaryingsDefault i)
400 // Workaround: _SAOcclusionTexture_Texelsize hasn't been set properly
401 // for some reasons. Use _ScreenParams instead.
402 float2 delta = (_ScreenParams.zw - 1.0) / DOWNSAMPLE;
403 half ao = BlurSmall(TEXTURE2D_PARAM(_SAOcclusionTexture, sampler_SAOcclusionTexture), i.texcoord, delta);
406 o.gbuffer0 = half4(0.0, 0.0, 0.0, ao);
407 o.gbuffer3 = half4((half3)EncodeAO(ao) * _AOColor, 0.0);
413 float4 FragCompositionGBuffer(VaryingsDefault i) : SV_Target
420 float4 FragDebugOverlay(VaryingsDefault i) : SV_Target
422 float2 delta = _SAOcclusionTexture_TexelSize.xy / DOWNSAMPLE;
423 half ao = BlurSmall(TEXTURE2D_PARAM(_SAOcclusionTexture, sampler_SAOcclusionTexture), i.texcoord, delta);
425 return float4(1.0 - ao.xxx, 1.0);
428 #endif // UNITY_POSTFX_AMBIENT_OCCLUSION