// Upgrade NOTE: commented out 'float4x4 _WorldToCamera', a built-in variable // Upgrade NOTE: replaced '_WorldToCamera' with 'unity_WorldToCamera' #ifndef __AMBIENT_OCCLUSION__ #define __AMBIENT_OCCLUSION__ #include "UnityCG.cginc" #include "Common.cginc" // -------- // Options for further customization // -------- // By default, a 5-tap Gaussian with the linear sampling technique is used // in the bilateral noise filter. It can be replaced with a 7-tap Gaussian // with adaptive sampling by enabling the macro below. Although the // differences are not noticeable in most cases, it may provide preferable // results with some special usage (e.g. NPR without textureing). // #define BLUR_HIGH_QUALITY // By default, a fixed sampling pattern is used in the AO estimator. Although // this gives preferable results in most cases, a completely random sampling // pattern could give aesthetically better results. Disable the macro below // to use such a random pattern instead of the fixed one. #define FIX_SAMPLING_PATTERN // The SampleNormal function normalizes samples from G-buffer because // they're possibly unnormalized. We can eliminate this if it can be said // that there is no wrong shader that outputs unnormalized normals. // #define VALIDATE_NORMALS // The constant below determines the contrast of occlusion. This allows // users to control over/under occlusion. At the moment, this is not exposed // to the editor because it�s rarely useful. static const float kContrast = 0.6; // The constant below controls the geometry-awareness of the bilateral // filter. The higher value, the more sensitive it is. static const float kGeometryCoeff = 0.8; // The constants below are used in the AO estimator. Beta is mainly used // for suppressing self-shadowing noise, and Epsilon is used to prevent // calculation underflow. See the paper (Morgan 2011 http://goo.gl/2iz3P) // for further details of these constants. static const float kBeta = 0.002; // -------- // System built-in variables sampler2D _CameraGBufferTexture2; sampler2D_float _CameraDepthTexture; sampler2D _CameraDepthNormalsTexture; float4 _CameraDepthTexture_ST; // Sample count #if !defined(SHADER_API_GLES) int _SampleCount; #else // GLES2: In many cases, dynamic looping is not supported. static const int _SampleCount = 3; #endif // Source texture properties sampler2D _OcclusionTexture; float4 _OcclusionTexture_TexelSize; // Other parameters half _Intensity; float _Radius; float _Downsample; float3 _FogParams; // x: density, y: start, z: end // Accessors for packed AO/normal buffer fixed4 PackAONormal(fixed ao, fixed3 n) { return fixed4(ao, n * 0.5 + 0.5); } fixed GetPackedAO(fixed4 p) { return p.r; } fixed3 GetPackedNormal(fixed4 p) { return p.gba * 2.0 - 1.0; } // Boundary check for depth sampler // (returns a very large value if it lies out of bounds) float CheckBounds(float2 uv, float d) { float ob = any(uv < 0) + any(uv > 1); #if defined(UNITY_REVERSED_Z) ob += (d <= 0.00001); #else ob += (d >= 0.99999); #endif return ob * 1e8; } // Depth/normal sampling functions float SampleDepth(float2 uv) { #if defined(SOURCE_GBUFFER) || defined(SOURCE_DEPTH) float d = LinearizeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv)); #else float4 cdn = tex2D(_CameraDepthNormalsTexture, uv); float d = DecodeFloatRG(cdn.zw); #endif return d * _ProjectionParams.z + CheckBounds(uv, d); } float3 SampleNormal(float2 uv) { #if defined(SOURCE_GBUFFER) float3 norm = tex2D(_CameraGBufferTexture2, uv).xyz; norm = norm * 2 - any(norm); // gets (0,0,0) when norm == 0 norm = mul((float3x3)unity_WorldToCamera, norm); #if defined(VALIDATE_NORMALS) norm = normalize(norm); #endif return norm; #else float4 cdn = tex2D(_CameraDepthNormalsTexture, uv); return DecodeViewNormalStereo(cdn) * float3(1.0, 1.0, -1.0); #endif } float SampleDepthNormal(float2 uv, out float3 normal) { #if defined(SOURCE_GBUFFER) || defined(SOURCE_DEPTH) normal = SampleNormal(uv); return SampleDepth(uv); #else float4 cdn = tex2D(_CameraDepthNormalsTexture, uv); normal = DecodeViewNormalStereo(cdn) * float3(1.0, 1.0, -1.0); float d = DecodeFloatRG(cdn.zw); return d * _ProjectionParams.z + CheckBounds(uv, d); #endif } // Normal vector comparer (for geometry-aware weighting) half CompareNormal(half3 d1, half3 d2) { return smoothstep(kGeometryCoeff, 1.0, dot(d1, d2)); } // Common vertex shader struct VaryingsMultitex { float4 pos : SV_POSITION; half2 uv : TEXCOORD0; // Original UV half2 uv01 : TEXCOORD1; // Alternative UV (supports v-flip case) half2 uvSPR : TEXCOORD2; // Single pass stereo rendering UV }; VaryingsMultitex VertMultitex(AttributesDefault v) { half2 uvAlt = v.texcoord.xy; #if UNITY_UV_STARTS_AT_TOP if (_MainTex_TexelSize.y < 0.0) uvAlt.y = 1.0 - uvAlt.y; #endif VaryingsMultitex o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord.xy; o.uv01 = uvAlt; o.uvSPR = UnityStereoTransformScreenSpaceTex(uvAlt); return o; } // Trigonometric function utility float2 CosSin(float theta) { float sn, cs; sincos(theta, sn, cs); return float2(cs, sn); } // Pseudo random number generator with 2D coordinates float UVRandom(float u, float v) { float f = dot(float2(12.9898, 78.233), float2(u, v)); return frac(43758.5453 * sin(f)); } // Check if the camera is perspective. // (returns 1.0 when orthographic) float CheckPerspective(float x) { return lerp(x, 1.0, unity_OrthoParams.w); } // Reconstruct view-space position from UV and depth. // p11_22 = (unity_CameraProjection._11, unity_CameraProjection._22) // p13_31 = (unity_CameraProjection._13, unity_CameraProjection._23) float3 ReconstructViewPos(float2 uv, float depth, float2 p11_22, float2 p13_31) { return float3((uv * 2.0 - 1.0 - p13_31) / p11_22 * CheckPerspective(depth), depth); } // Sample point picker float3 PickSamplePoint(float2 uv, float index) { // Uniformaly distributed points on a unit sphere http://goo.gl/X2F1Ho #if defined(FIX_SAMPLING_PATTERN) float gn = GradientNoise(uv * _Downsample); // FIXME: This was added to avoid a NVIDIA driver issue. // vvvvvvvvvvvv float u = frac(UVRandom(0.0, index + uv.x * 1e-10) + gn) * 2.0 - 1.0; float theta = (UVRandom(1.0, index + uv.x * 1e-10) + gn) * UNITY_PI_2; #else float u = UVRandom(uv.x + _Time.x, uv.y + index) * 2.0 - 1.0; float theta = UVRandom(-uv.x - _Time.x, uv.y + index) * UNITY_PI_2; #endif float3 v = float3(CosSin(theta) * sqrt(1.0 - u * u), u); // Make them distributed between [0, _Radius] float l = sqrt((index + 1.0) / _SampleCount) * _Radius; return v * l; } // Fog handling in forward half ComputeFog(float z) { half fog = 0.0; #if FOG_LINEAR fog = (_FogParams.z - z) / (_FogParams.z - _FogParams.y); #elif FOG_EXP fog = exp2(-_FogParams.x * z); #else // FOG_EXP2 fog = _FogParams.x * z; fog = exp2(-fog * fog); #endif return saturate(fog); } float ComputeDistance(float depth) { float dist = depth * _ProjectionParams.z; dist -= _ProjectionParams.y; return dist; } // // Distance-based AO estimator based on Morgan 2011 http://goo.gl/2iz3P // half4 FragAO(VaryingsMultitex i) : SV_Target { float2 uv = i.uv; // Parameters used in coordinate conversion float3x3 proj = (float3x3)unity_CameraProjection; float2 p11_22 = float2(unity_CameraProjection._11, unity_CameraProjection._22); float2 p13_31 = float2(unity_CameraProjection._13, unity_CameraProjection._23); // View space normal and depth float3 norm_o; float depth_o = SampleDepthNormal(UnityStereoScreenSpaceUVAdjust(uv, _CameraDepthTexture_ST), norm_o); #if defined(SOURCE_DEPTHNORMALS) // Offset the depth value to avoid precision error. // (depth in the DepthNormals mode has only 16-bit precision) depth_o -= _ProjectionParams.z / 65536; #endif // Reconstruct the view-space position. float3 vpos_o = ReconstructViewPos(i.uv01, depth_o, p11_22, p13_31); float ao = 0.0; for (int s = 0; s < _SampleCount; s++) { // Sample point #if defined(SHADER_API_D3D11) // This 'floor(1.0001 * s)' operation is needed to avoid a NVidia // shader issue. This issue is only observed on DX11. float3 v_s1 = PickSamplePoint(uv, floor(1.0001 * s)); #else float3 v_s1 = PickSamplePoint(uv, s); #endif v_s1 = faceforward(v_s1, -norm_o, v_s1); float3 vpos_s1 = vpos_o + v_s1; // Reproject the sample point float3 spos_s1 = mul(proj, vpos_s1); float2 uv_s1_01 = (spos_s1.xy / CheckPerspective(vpos_s1.z) + 1.0) * 0.5; // Depth at the sample point float depth_s1 = SampleDepth(UnityStereoScreenSpaceUVAdjust(uv_s1_01, _CameraDepthTexture_ST)); // Relative position of the sample point float3 vpos_s2 = ReconstructViewPos(uv_s1_01, depth_s1, p11_22, p13_31); float3 v_s2 = vpos_s2 - vpos_o; // Estimate the obscurance value float a1 = max(dot(v_s2, norm_o) - kBeta * depth_o, 0.0); float a2 = dot(v_s2, v_s2) + EPSILON; ao += a1 / a2; } ao *= _Radius; // intensity normalization // Apply other parameters. ao = pow(ao * _Intensity / _SampleCount, kContrast); // Apply fog when enabled (forward-only) #if !FOG_OFF float d = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv)); d = ComputeDistance(d); ao *= ComputeFog(d); #endif return PackAONormal(ao, norm_o); } // Geometry-aware separable bilateral filter half4 FragBlur(VaryingsMultitex i) : SV_Target { #if defined(BLUR_HORIZONTAL) // Horizontal pass: Always use 2 texels interval to match to // the dither pattern. float2 delta = float2(_MainTex_TexelSize.x * 2.0, 0.0); #else // Vertical pass: Apply _Downsample to match to the dither // pattern in the original occlusion buffer. float2 delta = float2(0.0, _MainTex_TexelSize.y / _Downsample * 2.0); #endif #if defined(BLUR_HIGH_QUALITY) // High quality 7-tap Gaussian with adaptive sampling fixed4 p0 = tex2D(_MainTex, i.uvSPR); fixed4 p1a = tex2D(_MainTex, i.uvSPR - delta); fixed4 p1b = tex2D(_MainTex, i.uvSPR + delta); fixed4 p2a = tex2D(_MainTex, i.uvSPR - delta * 2.0); fixed4 p2b = tex2D(_MainTex, i.uvSPR + delta * 2.0); fixed4 p3a = tex2D(_MainTex, i.uvSPR - delta * 3.2307692308); fixed4 p3b = tex2D(_MainTex, i.uvSPR + delta * 3.2307692308); #if defined(BLUR_SAMPLE_CENTER_NORMAL) fixed3 n0 = SampleNormal(i.uvSPR); #else fixed3 n0 = GetPackedNormal(p0); #endif half w0 = 0.37004405286; half w1a = CompareNormal(n0, GetPackedNormal(p1a)) * 0.31718061674; half w1b = CompareNormal(n0, GetPackedNormal(p1b)) * 0.31718061674; half w2a = CompareNormal(n0, GetPackedNormal(p2a)) * 0.19823788546; half w2b = CompareNormal(n0, GetPackedNormal(p2b)) * 0.19823788546; half w3a = CompareNormal(n0, GetPackedNormal(p3a)) * 0.11453744493; half w3b = CompareNormal(n0, GetPackedNormal(p3b)) * 0.11453744493; half s; s = GetPackedAO(p0) * w0; s += GetPackedAO(p1a) * w1a; s += GetPackedAO(p1b) * w1b; s += GetPackedAO(p2a) * w2a; s += GetPackedAO(p2b) * w2b; s += GetPackedAO(p3a) * w3a; s += GetPackedAO(p3b) * w3b; s /= w0 + w1a + w1b + w2a + w2b + w3a + w3b; #else // Fater 5-tap Gaussian with linear sampling fixed4 p0 = tex2D(_MainTex, i.uvSPR); fixed4 p1a = tex2D(_MainTex, i.uvSPR - delta * 1.3846153846); fixed4 p1b = tex2D(_MainTex, i.uvSPR + delta * 1.3846153846); fixed4 p2a = tex2D(_MainTex, i.uvSPR - delta * 3.2307692308); fixed4 p2b = tex2D(_MainTex, i.uvSPR + delta * 3.2307692308); #if defined(BLUR_SAMPLE_CENTER_NORMAL) fixed3 n0 = SampleNormal(i.uvSPR); #else fixed3 n0 = GetPackedNormal(p0); #endif half w0 = 0.2270270270; half w1a = CompareNormal(n0, GetPackedNormal(p1a)) * 0.3162162162; half w1b = CompareNormal(n0, GetPackedNormal(p1b)) * 0.3162162162; half w2a = CompareNormal(n0, GetPackedNormal(p2a)) * 0.0702702703; half w2b = CompareNormal(n0, GetPackedNormal(p2b)) * 0.0702702703; half s; s = GetPackedAO(p0) * w0; s += GetPackedAO(p1a) * w1a; s += GetPackedAO(p1b) * w1b; s += GetPackedAO(p2a) * w2a; s += GetPackedAO(p2b) * w2b; s /= w0 + w1a + w1b + w2a + w2b; #endif return PackAONormal(s, n0); } // Gamma encoding (only needed in gamma lighting mode) half EncodeAO(half x) { half x_g = 1.0 - max(1.055 * pow(1.0 - x, 0.416666667) - 0.055, 0.0); // ColorSpaceLuminance.w == 0 (gamma) or 1 (linear) return lerp(x_g, x, unity_ColorSpaceLuminance.w); } // Geometry-aware bilateral filter (single pass/small kernel) half BlurSmall(sampler2D tex, float2 uv, float2 delta) { fixed4 p0 = tex2D(tex, uv); fixed4 p1 = tex2D(tex, uv + float2(-delta.x, -delta.y)); fixed4 p2 = tex2D(tex, uv + float2(+delta.x, -delta.y)); fixed4 p3 = tex2D(tex, uv + float2(-delta.x, +delta.y)); fixed4 p4 = tex2D(tex, uv + float2(+delta.x, +delta.y)); fixed3 n0 = GetPackedNormal(p0); half w0 = 1.0; half w1 = CompareNormal(n0, GetPackedNormal(p1)); half w2 = CompareNormal(n0, GetPackedNormal(p2)); half w3 = CompareNormal(n0, GetPackedNormal(p3)); half w4 = CompareNormal(n0, GetPackedNormal(p4)); half s; s = GetPackedAO(p0) * w0; s += GetPackedAO(p1) * w1; s += GetPackedAO(p2) * w2; s += GetPackedAO(p3) * w3; s += GetPackedAO(p4) * w4; return s / (w0 + w1 + w2 + w3 + w4); } // Final composition shader half4 FragComposition(VaryingsMultitex i) : SV_Target { float2 delta = _MainTex_TexelSize.xy / _Downsample; half ao = BlurSmall(_OcclusionTexture, i.uvSPR, delta); half4 color = tex2D(_MainTex, i.uvSPR); #if !defined(DEBUG_COMPOSITION) color.rgb *= 1.0 - EncodeAO(ao); #else color.rgb = 1.0 - EncodeAO(ao); #endif return color; } // Final composition shader (ambient-only mode) VaryingsDefault VertCompositionGBuffer(AttributesDefault v) { VaryingsDefault o; o.pos = v.vertex; #if UNITY_UV_STARTS_AT_TOP o.uv = v.texcoord.xy * float2(1.0, -1.0) + float2(0.0, 1.0); #else o.uv = v.texcoord.xy; #endif o.uvSPR = UnityStereoTransformScreenSpaceTex(o.uv); return o; } #if !SHADER_API_GLES // excluding the MRT pass under GLES2 struct CompositionOutput { half4 gbuffer0 : SV_Target0; half4 gbuffer3 : SV_Target1; }; CompositionOutput FragCompositionGBuffer(VaryingsDefault i) { // Workaround: _OcclusionTexture_Texelsize hasn't been set properly // for some reasons. Use _ScreenParams instead. float2 delta = (_ScreenParams.zw - 1.0) / _Downsample; half ao = BlurSmall(_OcclusionTexture, i.uvSPR, delta); CompositionOutput o; o.gbuffer0 = half4(0.0, 0.0, 0.0, ao); o.gbuffer3 = half4((half3)EncodeAO(ao), 0.0); return o; } #else fixed4 FragCompositionGBuffer(VaryingsDefault i) : SV_Target0 { return 0.0; } #endif #endif // __AMBIENT_OCCLUSION__