1
2 // This Ambient Occlusion image effect is based on "Scalable Ambient Obscurance":
3
4 /**
5
6 \author Morgan McGuire and Michael Mara, NVIDIA and Williams College, http://research.nvidia.com, http://graphics.cs.williams.edu
7
8 Open Source under the "BSD" license: http://www.opensource.org/licenses/bsd-license.php
9
10 Copyright (c) 2011-2012, NVIDIA
11 All rights reserved.
12
13 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
14
15 Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
16 Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18
19 */
20
21 Shader "Hidden/ScreenSpaceAmbientObscurance"
22 {
23 Properties {
24 _MainTex ("Base (RGB)", 2D) = "white" {}
25 }
26
27 CGINCLUDE
28
29 #include "UnityCG.cginc"
30
31 #ifdef SHADER_API_D3D11
32 #define NUM_SAMPLES (15)
33 #else
34 #define NUM_SAMPLES (11)
35 #endif
36
37 #define FAR_PLANE_Z (300.0)
38 #define NUM_SPIRAL_TURNS (7)
39 #define bias (0.01)
40
41 float _Radius;
42 float _Radius2; // _Radius * _Radius;
43 float _Intensity;
44 float4 _ProjInfo;
45 float4x4 _ProjectionInv; // ref only
46
47 sampler2D_float _CameraDepthTexture;
48 sampler2D _Rand;
49 sampler2D _AOTex;
50 sampler2D _MainTex;
51
52 float4 _MainTex_TexelSize;
53
54 static const float gaussian[5] = { 0.153170, 0.144893, 0.122649, 0.092902, 0.062970 }; // stddev = 2.0
55
56 float2 _Axis;
57
58 /** Increase to make edges crisper. Decrease to reduce temporal flicker. */
59 #define EDGE_SHARPNESS (1.0)
60
61 float _BlurFilterDistance;
62 #define SCALE _BlurFilterDistance
63
64 /** Filter _Radius in pixels. This will be multiplied by SCALE. */
65 #define R (4)
66
67 struct v2f
68 {
69 float4 pos : SV_POSITION;
70 float2 uv : TEXCOORD0;
71 float2 uv2 : TEXCOORD1;
72 };
73
74 v2f vert( appdata_img v )
75 {
76 v2f o;
77 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
78 o.uv = v.texcoord.xy;
79 o.uv2 = v.texcoord.xy;
80 #if UNITY_UV_STARTS_AT_TOP
81 if (_MainTex_TexelSize.y < 0)
82 o.uv2.y = 1-o.uv2.y;
83 #endif
84 return o;
85 }
86
87 float3 ReconstructCSPosition(float2 S, float z)
88 {
89 float linEyeZ = LinearEyeDepth(z);
90 return float3(( ( S.xy * _MainTex_TexelSize.zw) * _ProjInfo.xy + _ProjInfo.zw) * linEyeZ, linEyeZ);
91
92 /*
93 // for reference
94 float4 clipPos = float4(S*2.0-1.0, (z*2-1), 1);
95 float4 viewPos;
96 viewPos.x = dot((float4)_ProjectionInv[0], clipPos);
97 viewPos.y = dot((float4)_ProjectionInv[1], clipPos);
98 viewPos.w = dot((float4)_ProjectionInv[3], clipPos);
99 viewPos.z = z;
100 viewPos = viewPos/viewPos.w;
101 return viewPos.xyz;
102 */
103 }
104
105 float3 ReconstructCSFaceNormal(float3 C) {
106 return normalize(cross(ddy(C), ddx(C)));
107 }
108
109
110 /** Returns a unit vector and a screen-space _Radius for the tap on a unit disk (the caller should scale by the actual disk _Radius) */
111
112 float2 TapLocation(int sampleNumber, float spinAngle, out float ssR){
113 // Radius relative to ssR
114 float alpha = float(sampleNumber + 0.5) * (1.0 / NUM_SAMPLES);
115 float angle = alpha * (NUM_SPIRAL_TURNS * 6.28) + spinAngle;
116
117 ssR = alpha;
118 return float2(cos(angle), sin(angle));
119 }
120
121 /** Used for packing Z into the GB channels */
122 float CSZToKey(float z) {
123 return saturate(z * (1.0 / FAR_PLANE_Z));
124 }
125
126 /** Used for packing Z into the GB channels */
127 void packKey(float key, out float2 p) {
128 // Round to the nearest 1/256.0
129 float temp = floor(key * 256.0);
130
131 // Integer part
132 p.x = temp * (1.0 / 256.0);
133
134 // Fractional part
135 p.y = key * 256.0 - temp;
136 }
137
138 /** Returns a number on (0, 1) */
139 float UnpackKey(float2 p)
140 {
141 return p.x * (256.0 / 257.0) + p.y * (1.0 / 257.0);
142 }
143
144
145 /** Read the camera-space position of the point at screen-space pixel ssP */
146 float3 GetPosition(float2 ssP) {
147 float3 P;
148
149 P.z = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, ssP.xy);
150
151 // Offset to pixel center
152 P = ReconstructCSPosition(float2(ssP) /*+ float2(0.5, 0.5)*/, P.z);
153 return P;
154 }
155
156 /** Read the camera-space position of the point at screen-space pixel ssP + unitOffset * ssR. Assumes length(unitOffset) == 1 */
157 float3 GetOffsetPosition(float2 ssC, float2 unitOffset, float ssR)
158 {
159 float2 ssP = saturate(float2(ssR*unitOffset) + ssC);
160
161 float3 P;
162 P.z = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, ssP.xy);
163
164 // Offset to pixel center
165 P = ReconstructCSPosition(float2(ssP)/* + float2(0.5, 0.5)*/, P.z);
166
167 return P;
168 }
169
170 /** Compute the occlusion due to sample with index \a i about the pixel at \a ssC that corresponds
171 to camera-space point \a C with unit normal \a n_C, using maximum screen-space sampling _Radius \a ssDiskRadius */
172
173 float SampleAO(in float2 ssC, in float3 C, in float3 n_C, in float ssDiskRadius, in int tapIndex, in float randomPatternRotationAngle)
174 {
175 // Offset on the unit disk, spun for this pixel
176 float ssR;
177 float2 unitOffset = TapLocation(tapIndex, randomPatternRotationAngle, ssR);
178 ssR *= ssDiskRadius;
179
180 // The occluding point in camera space
181 float3 Q = GetOffsetPosition(ssC, unitOffset, ssR);
182
183 float3 v = Q - C;
184
185 float vv = dot(v, v);
186 float vn = dot(v, n_C);
187
188 const float epsilon = 0.01;
189 float f = max(_Radius2 - vv, 0.0);
190 return f * f * f * max((vn - bias) / (epsilon + vv), 0.0);
191 }
192
193 float4 fragAO(v2f i) : SV_Target
194 {
195 float4 fragment = fixed4(1,1,1,1);
196
197 // Pixel being shaded
198 float2 ssC = i.uv2.xy;// * _MainTex_TexelSize.zw;
199
200 // View space point being shaded
201 float3 C = GetPosition(ssC);
202
203 //return abs(float4(C.xyz,0));
204 //if(abs(C.z)<0.31)
205 // return 1;
206 //return abs(C.z);
207
208 packKey(CSZToKey(C.z), fragment.gb);
209 //packKey(CSZToKey(C.z), bilateralKey);
210
211 float randomPatternRotationAngle = 1.0;
212 #ifdef SHADER_API_D3D11
213 int2 ssCInt = ssC.xy * _MainTex_TexelSize.zw;
214 randomPatternRotationAngle = (3 * ssCInt.x ^ ssCInt.y + ssCInt.x * ssCInt.y) * 10;
215 #else
216 // TODO: make dx9 rand better
217 randomPatternRotationAngle = tex2D(_Rand, i.uv*12.0).x * 1000.0;
218 #endif
219
220 // Reconstruct normals from positions. These will lead to 1-pixel black lines
221 // at depth discontinuities, however the blur will wipe those out so they are not visible
222 // in the final image.
223 float3 n_C = ReconstructCSFaceNormal(C);
224
225 //return float4((n_C),0);
226
227 // Choose the screen-space sample _Radius
228 // proportional to the projected area of the sphere
229 float ssDiskRadius = -_Radius / C.z; // -projScale * _Radius / C.z; // <:::::
230
231 float sum = 0.0;
232 for (int l = 0; l < NUM_SAMPLES; ++l) {
233 sum += SampleAO(ssC, C, n_C, (ssDiskRadius), l, randomPatternRotationAngle);
234 }
235
236 float temp = _Radius2 * _Radius;
237 sum /= temp * temp;
238
239 float A = max(0.0, 1.0 - sum * _Intensity * (5.0 / NUM_SAMPLES));
240 fragment.ra = float2(A,A);
241
242 return fragment;
243 }
244
245 float4 fragUpsample (v2f i) : SV_Target
246 {
247 float4 fragment = fixed4(1,1,1,1);
248
249 // View space point being shaded
250 float3 C = GetPosition(i.uv.xy);
251
252 packKey(CSZToKey(C.z), fragment.gb);
253 fragment.ra = tex2D(_MainTex, i.uv.xy).ra;
254
255 return fragment;
256 }
257
258 float4 fragApply (v2f i) : SV_Target
259 {
260 float4 ao = tex2D(_AOTex, i.uv2.xy);
261 return tex2D(_MainTex, i.uv.xy) * ao.rrrr;
262 }
263
264 float4 fragApplySoft (v2f i) : SV_Target
265 {
266 float4 color = tex2D(_MainTex, i.uv.xy);
267
268 float ao = tex2D(_AOTex, i.uv2.xy).r;
269 ao += tex2D(_AOTex, i.uv2.xy + _MainTex_TexelSize.xy * 0.75).r;
270 ao += tex2D(_AOTex, i.uv2.xy - _MainTex_TexelSize.xy * 0.75).r;
271 ao += tex2D(_AOTex, i.uv2.xy + _MainTex_TexelSize.xy * float2(-0.75,0.75)).r;
272 ao += tex2D(_AOTex, i.uv2.xy - _MainTex_TexelSize.xy * float2(-0.75,0.75)).r;
273
274 return color * float4(ao,ao,ao,5)/5;
275 }
276
277 float4 fragBlurBL (v2f i) : SV_Target
278 {
279 float4 fragment = float4(1,1,1,1);
280
281 float2 ssC = i.uv.xy;
282
283 float4 temp = tex2Dlod(_MainTex, float4(i.uv.xy,0,0));
284
285 float2 passthrough2 = temp.gb;
286 float key = UnpackKey(passthrough2);
287
288 float sum = temp.r;
289
290 /*
291 if (key >= 0.999) {
292 // Sky pixel (if you aren't using depth keying, disable this test)
293 fragment.gb = passthrough2;
294 return fragment;
295 }
296 */
297
298 // Base weight for depth falloff. Increase this for more blurriness, decrease it for better edge discrimination
299
300 float BASE = gaussian[0] * 0.5; // ole: i decreased
301 float totalWeight = BASE;
302 sum *= totalWeight;
303
304 for (int r = -R; r <= R; ++r) {
305 // We already handled the zero case above. This loop should be unrolled and the branch discarded
306 if (r != 0) {
307 temp = tex2Dlod(_MainTex, float4(ssC + _Axis * _MainTex_TexelSize.xy * (r * SCALE),0,0) );
308 float tapKey = UnpackKey(temp.gb);
309 float value = temp.r;
310
311 // spatial domain: offset gaussian tap
312 float weight = 0.3 + gaussian[abs(r)];
313
314 // range domain (the "bilateral" weight). As depth difference increases, decrease weight.
315 weight *= max(0.0, 1.0 - (2000.0 * EDGE_SHARPNESS) * abs(tapKey - key));
316
317 sum += value * weight;
318 totalWeight += weight;
319 }
320 }
321
322 const float epsilon = 0.0001;
323 fragment = sum / (totalWeight + epsilon);
324
325 fragment.gb = passthrough2;
326
327 return fragment;
328 }
329
330 ENDCG
331
332 SubShader {
333
334 // 0: get ao
335 Pass {
336 ZTest Always Cull Off ZWrite Off
337
338 CGPROGRAM
339
340 #pragma vertex vert
341 #pragma fragment fragAO
342 #pragma target 3.0
343
344 ENDCG
345 }
346
347 // 1: bilateral blur
348 Pass {
349 ZTest Always Cull Off ZWrite Off
350
351 CGPROGRAM
352
353 #pragma vertex vert
354 #pragma fragment fragBlurBL
355 #pragma target 3.0
356
357 ENDCG
358 }
359
360 // 2: apply ao
361 Pass {
362 ZTest Always Cull Off ZWrite Off
363
364 CGPROGRAM
365
366 #pragma vertex vert
367 #pragma fragment fragApply
368 #pragma target 3.0
369
370 ENDCG
371 }
372
373 // 3: apply with a slight box filter
374 Pass {
375 ZTest Always Cull Off ZWrite Off
376
377 CGPROGRAM
378
379 #pragma vertex vert
380 #pragma fragment fragApplySoft
381 #pragma target 3.0
382
383 ENDCG
384 }
385
386 // 4: in case you want to blur in high rez for nicer z borders
387 Pass {
388 ZTest Always Cull Off ZWrite Off
389
390 CGPROGRAM
391
392 #pragma vertex vert
393 #pragma fragment fragUpsample
394 #pragma target 3.0
395
396 ENDCG
397 }
398 }
399
400 Fallback off
401
402 }