source: GTP/trunk/App/Demos/Vis/FriendlyCulling/src/shaders/ssao.cg @ 3209

Revision 3209, 13.6 KB checked in by mattausch, 16 years ago (diff)

restricted ssao to 0.1 so tone mapping artifacts for completely black surfaces can be avoided

Line 
1#include "../shaderenv.h"
2
3
4////////////////////
5// Screen Spaced Ambient Occlusion shader
6// based on shader of Alexander Kusternig
7
8
9#define USE_EYESPACE_DEPTH 1
10
11
12struct fragment
13{
14        float2 texCoord: TEXCOORD0;
15        float3 view: TEXCOORD1;
16};
17
18
19struct pixel
20{
21        float4 illum_col: COLOR0;
22};
23
24
25inline float occlusionPower(float radius, float dist)
26{
27        return 6.283185307179586476925286766559f * (1.0f - cos(asin(radius / dist)));
28}
29
30
31inline float SqrLen(float3 v)
32{
33        return v.x * v.x + v.y * v.y + v.z * v.z;
34}
35
36
37inline float2 myreflect(float2 pt, float2 n)
38{
39        // distance to plane
40        float d = dot(n, pt);
41        // reflect around plane
42        float2 rpt = pt - d * 2.0f * n;
43
44        return rpt;
45}
46
47
48inline float3 Interpol(float2 w, float3 bl, float3 br, float3 tl, float3 tr)
49{
50        float3 x1 = lerp(bl, tl, w.y);
51        float3 x2 = lerp(br, tr, w.y);
52        float3 v = lerp(x1, x2, w.x);
53
54        return v;
55}
56
57
58// reconstruct world space position
59inline float3 ReconstructSamplePos(float eyeSpaceDepth,
60                                                                   float2 texcoord,
61                                                                   float3 bl, float3 br, float3 tl, float3 tr)
62{
63        float3 viewVec = Interpol(texcoord, bl, br, tl, tr);
64        float3 samplePos = -viewVec * eyeSpaceDepth;
65
66        return samplePos;
67}
68
69
70
71/** This shader computes the reprojection and stores
72        the ssao value of the old pixel as well as the
73        weight of the pixel in the new frame.
74*/
75inline float2 temporalSmoothing(float4 worldPos,
76                                                                float eyeSpaceDepth,
77                                                                float2 texcoord0,
78                                                                float3 oldEyePos,
79                                                                sampler2D oldTex,
80                                                                float4x4 oldModelViewProj,
81                                                                sampler2D colors,
82                                                                float3 projPos,
83                                                                float invW,
84                                                                float3 oldbl,
85                                                                float3 oldbr,
86                                                                float3 oldtl,
87                                                                float3 oldtr,
88                                                                float3 diffVec
89                                                                )
90{
91        // compute position from old frame for dynamic objects + translational portion
92        const float3 translatedPos = diffVec - oldEyePos + worldPos.xyz;
93
94
95        /////////////////
96        //-- reproject into old frame and calculate texture position of sample in old frame
97
98        // note: the old model view matrix only holds the view orientation part
99        float4 backProjPos = mul(oldModelViewProj, float4(translatedPos, 1.0f));
100        backProjPos /= backProjPos.w;
101       
102        // fit from unit cube into 0 .. 1
103        const float2 oldTexCoords = backProjPos.xy * 0.5f + 0.5f;
104        // retrieve the sample from the last frame
105        const float4 oldPixel = tex2Dlod(oldTex, float4(oldTexCoords, .0f, .0f));
106
107        // the ssao value in the old frame
108        const float ssao = oldPixel.x;
109
110#if USE_EYESPACE_DEPTH
111
112        // calculate eye space position of sample in old frame
113        const float oldEyeSpaceDepth = oldPixel.w;
114
115        // vector from eye pos to old sample
116        const float3 viewVec = Interpol(oldTexCoords, oldbl, oldbr, oldtl, oldtr);
117        const float invLen = 1.0f / length(viewVec);
118        const float projectedEyeSpaceDepth = invLen * length(translatedPos);
119        //const float projectedEyeSpaceDepth = length(translatedPos);
120       
121        const float depthDif = abs(1.0f - oldEyeSpaceDepth / projectedEyeSpaceDepth);
122
123#else
124
125        // calculate eye space position of sample in old frame
126        const float oldDepth = oldPixel.w;
127        // the depth projected into the old frame
128        const float projectedDepth = projPos.z;
129        // calculate depth difference
130        const float depthDif = abs(projectedDepth - oldDepth);
131
132#endif
133
134        const float xOffs = 1.0f / 1024.0f;
135        const float yOffs = 1.0f / 768.0f;
136        const float eps = 1e-6f;
137
138        // the weight of the old value
139        float w;
140
141        //////////////
142        //-- reuse old value only if it was still valid in the old frame
143
144        if (1
145                && (oldTexCoords.x + eps >= xOffs) && (oldTexCoords.x <= 1.0f - xOffs + eps)
146                && (oldTexCoords.y + eps >= yOffs) && (oldTexCoords.y <= 1.0f - yOffs + eps)
147                && (depthDif <= MIN_DEPTH_DIFF)
148                )
149        {
150                // pixel valid => retrieve the convergence weight
151                w = oldPixel.y;
152        }
153        else
154        {       
155                w = 0.0f;
156        }
157
158        return float2(ssao, w);
159}
160
161
162/** The ssao shader returning the an intensity value between 0 and 1
163        This version of the ssao shader uses the dotproduct between pixel and
164        sample normal as weight.
165*/
166float3 ssao2(fragment IN,
167                         sampler2D colors,
168                         sampler2D noiseTex,
169                         float2 samples[NUM_SAMPLES],
170                         float3 normal,
171                         float3 centerPosition,
172                         float scaleFactor,
173                         float3 bl,
174                         float3 br,
175                         float3 tl,
176                         float3 tr,
177                         float3 viewDir,
178                         sampler2D normalTex
179                         )
180{
181        float total_ao = .0f;
182        float numSamples = .0f;
183        float validSamples = .0f;
184
185        for (int i = 0; i < NUM_SAMPLES; ++ i)
186        {
187                const float2 offset = samples[i];
188
189#if 1
190                ////////////////////
191                //-- add random noise: reflect around random normal vector (rather slow!)
192
193                const float2 mynoise = tex2Dlod(noiseTex, float4(IN.texCoord * 4.0f, 0, 0)).xy;
194                const float2 offsetTransformed = myreflect(offset, mynoise);
195#else
196                const float2 offsetTransformed = offset;
197#endif
198                // weight with projected coordinate to reach similar kernel size for near and far
199                //const float2 texcoord = IN.texCoord.xy + offsetTransformed * scaleFactor + jitter;
200                const float2 texcoord = IN.texCoord.xy + offsetTransformed * scaleFactor;
201
202                //if ((texcoord.x <= 1.0f) && (texcoord.x >= 0.0f) && (texcoord.y <= 1.0f) && (texcoord.y >= 0.0f)) ++ numSamples;
203                float4 sampleColor = tex2Dlod(colors, float4(texcoord, 0, 0));
204
205                const float3 samplePos = ReconstructSamplePos(sampleColor.w, texcoord, bl, br, tl, tr);
206                // the normal of the current sample
207                const float3 sampleNormal = tex2Dlod(normalTex, float4(texcoord, 0, 0)).xyz;
208
209
210                ////////////////
211                //-- compute contribution of sample using the direction and angle
212
213                float3 dirSample = samplePos - centerPosition;
214
215                const float sqrLen = max(SqrLen(dirSample), 1e-2f);
216                const float lengthToSample = sqrt(sqrLen);
217                //const float lengthToSample = max(length(dirSample), 1e-6f);
218
219                dirSample /= lengthToSample; // normalize
220
221                // angle between current normal and direction to sample controls AO intensity.
222                float cosAngle = .5f + dot(sampleNormal, -normal) * 0.5f;
223                // use binary decision to cull samples that are behind current shading point
224                cosAngle *= step(0.0f, dot(dirSample, normal));
225       
226                const float aoContrib = SAMPLE_INTENSITY / sqrLen;
227                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
228
229#if 1
230                // if surface normal perpenticular to view dir, approx. half of the samples will not count
231                // => compensate for this (on the other hand, projected sampling area could be larger!)
232
233                const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
234                total_ao += cosAngle * aoContrib * viewCorrection;
235#else
236                total_ao += cosAngle * aoContrib;
237#endif
238                // check if the samples have been valid in the last frame
239                validSamples += (1.0f - step(1.0f, lengthToSample)) * sampleColor.x;
240
241                ++ numSamples;
242        }
243
244        total_ao /= numSamples;
245
246        return float3(max(0.0f, 1.0f - total_ao), validSamples, numSamples);
247}
248
249
250/** The ssao shader returning the an intensity value between 0 and 1.
251        This version of the ssao shader uses the dotproduct between
252        pixel-to-sample direction and sample normal as weight.
253
254    The algorithm works like the following:
255        1) Check in a circular area around the current position.
256        2) Shoot vectors to the positions there, and check the angle to these positions.
257        3) Summing up these angles gives an estimation of the occlusion at the current position.
258*/
259float3 ssao(fragment IN,
260                        sampler2D colors,
261                        sampler2D noiseTex,
262                        float2 samples[NUM_SAMPLES],
263                        float3 normal,
264                        float3 centerPosition,
265                        float scaleFactor,
266                        float3 bl,
267                        float3 br,
268                        float3 tl,
269                        float3 tr,
270                        float3 viewDir,
271                        float newWeight,
272                        bool isMovingObject
273                        )
274{
275        float total_ao = .0f;
276        float validSamples = .0f;
277        float numSamples = .0f;
278
279        for (int i = 0; i < NUM_SAMPLES; ++ i)
280        {
281                const float2 offset = samples[i];
282
283#if 1
284                ////////////////////
285                //-- add random noise: reflect around random normal vector
286                //-- (slows down the computation for some reason!)
287
288                float2 mynoise = tex2Dlod(noiseTex, float4(IN.texCoord * 4.0f, 0, 0)).xy;
289                const float2 offsetTransformed = myreflect(offset, mynoise);
290#else
291                const float2 offsetTransformed = offset;
292#endif
293                // weight with projected coordinate to reach similar kernel size for near and far
294                const float2 texcoord = IN.texCoord.xy + offsetTransformed * scaleFactor;
295
296                const float4 sampleColor = tex2Dlod(colors, float4(texcoord, .0f, .0f));
297                const float3 samplePos = ReconstructSamplePos(sampleColor.w, texcoord, bl, br, tl, tr);
298               
299
300                ////////////////
301                //-- compute contribution of sample using the direction and angle
302
303                float3 dirSample = samplePos - centerPosition;
304
305                const float sqrLen = max(SqrLen(dirSample), 1e-2f);
306                const float lengthToSample = sqrt(sqrLen);
307
308                dirSample /= lengthToSample; // normalize
309
310                // angle between current normal and direction to sample controls AO intensity.
311                const float cosAngle = max(dot(dirSample, normal), .0f);
312                const float aoContrib = SAMPLE_INTENSITY / sqrLen;
313                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
314
315#if 1
316                // if surface normal perpenticular to view dir, approx. half of the samples will not count
317                // => compensate for this (on the other hand, projected sampling area could be larger!)
318
319                const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
320                total_ao += cosAngle * aoContrib * viewCorrection;
321#else
322                total_ao += cosAngle * aoContrib;
323#endif
324
325                ++ numSamples;
326               
327                // check if the samples have been valid in the last frame
328                // only mark sample as invalid if taking it into account can have influence the ssao:
329                // hence we also check if the sample or the same sample in the previous frame
330                // had any chance to be near the current sample
331                const float changeFactor = sampleColor.y;
332                const float pixelValid = sampleColor.x;
333
334                // hack: cedistance measure can fail in some cases => choose something different
335                const float tooFarAway = step(0.5f, lengthToSample - changeFactor);
336                validSamples = max(validSamples, (1.0f - tooFarAway) * pixelValid);
337                //validSamples += sampleColor.x;
338
339                //if ((validSamples < 1.0f) && (newWeight > 200) && (numSamples >= 8)) break;
340                if ((validSamples < 1.0f) && (numSamples >= 8) && !isMovingObject
341                        || (newWeight > NUM_SAMPLES * 5) && (numSamples >= 8) && isMovingObject
342                        )
343                {
344                        break;
345                }
346        }
347
348        total_ao /= numSamples;
349
350        //return float3(max(0.0f, 1.0f - total_ao), validSamples, numSamples);
351        return float3(total_ao, validSamples, numSamples);
352}
353
354
355
356/** The mrt shader for screen space ambient occlusion
357*/
358pixel main(fragment IN,
359                   uniform sampler2D colors,
360                   uniform sampler2D normals,
361                   uniform sampler2D noiseTex,
362                   uniform float2 samples[NUM_SAMPLES],
363                   uniform sampler2D oldTex,
364                   uniform float4x4 modelViewProj,
365                   uniform float4x4 oldModelViewProj,
366                   uniform float temporalCoherence,
367                   uniform float3 bl,
368                   uniform float3 br,
369                   uniform float3 tl,
370                   uniform float3 tr,
371                   uniform float3 oldEyePos,
372                   uniform float3 oldbl,
373                   uniform float3 oldbr,
374                   uniform float3 oldtl,
375                   uniform float3 oldtr,
376                   uniform sampler2D attribsTex
377                   )
378{
379        pixel OUT;
380
381        //const float3 normal = normalize(tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz);
382        const float3 normal = tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz;
383
384        // reconstruct position from the eye space depth
385        const float3 viewDir = IN.view;
386        const float eyeSpaceDepth = tex2Dlod(colors, float4(IN.texCoord, 0, 0)).w;
387        const float4 eyeSpacePos = float4(-viewDir * eyeSpaceDepth, 1.0f);
388
389        float3 diffVec = tex2Dlod(attribsTex, float4(IN.texCoord, 0, 0)).xyz;
390       
391
392        ////////////////
393        //-- calculcate the current projected posiion (also used for next frame)
394       
395        float4 projPos = mul(modelViewProj, eyeSpacePos);
396        const float invw = 1.0f / projPos.w;
397        projPos *= invw;
398        float scaleFactor = SAMPLE_RADIUS * invw;
399
400        const float sqrMoveSpeed = SqrLen(diffVec);
401        const bool isMovingObject = (sqrMoveSpeed > DYNAMIC_OBJECTS_THRESHOLD);
402
403       
404        /////////////////
405        //-- compute temporal reprojection
406
407        float2 temporalVals = temporalSmoothing(eyeSpacePos, eyeSpaceDepth, IN.texCoord, oldEyePos,
408                                                oldTex, oldModelViewProj,
409                                                                                        colors,
410                                                                                        projPos.xyz,
411                                                                                        invw,
412                                                                                        oldbl, oldbr, oldtl, oldtr,
413                                                                                        diffVec
414                                                                                        );
415
416        const float oldSsao = temporalVals.x;
417        float oldWeight = temporalVals.y;
418       
419        float3 ao;
420
421        // cull background note: this should be done with the stencil buffer
422        if (eyeSpaceDepth < 1e10f)
423        {
424                ao = ssao(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor, bl, br, tl, tr, normalize(viewDir), oldWeight, isMovingObject);
425                //ao = ssao2(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor, bl, br, tl, tr, normalize(viewDir), normals);
426        }
427        else
428        {
429                 ao = float3(1.0f, 1.0f, 1.0f);
430        }
431
432        // equals the number of sampled shot in this pass
433        const float newWeight = ao.z;
434
435        // check if we have to reset pixel because one of the sample points was invalid
436        if (!isMovingObject)
437    //if (sqrMoveSpeed <= DYNAMIC_OBJECTS_THRESHOLD)
438        {
439                if (ao.y > 4.0f)
440                        oldWeight = 0;
441                else if (ao.y > 1.0f)
442                        oldWeight = min(oldWeight, 4.0f * newWeight);
443        }
444
445        const float combinedWeight = clamp(newWeight + oldWeight, .0f, temporalCoherence);
446
447       
448        // blend between old and new samples (and avoid division by zero)
449        OUT.illum_col.x = (ao.x * newWeight + oldSsao * oldWeight) / max(1e-6f, newWeight + oldWeight);
450        OUT.illum_col.z = SqrLen(diffVec);
451        OUT.illum_col.y = combinedWeight;
452        OUT.illum_col.w = eyeSpaceDepth;
453
454        return OUT;
455}
Note: See TracBrowser for help on using the repository browser.