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

Revision 3320, 18.1 KB checked in by mattausch, 15 years ago (diff)
Line 
1#include "../shaderenv.h"
2#include "common.h"
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// this function is inspired from the paper of shamulgaan in order
25// to get a physical expression for the occlusion culling
26inline float occlusionPower(float radius, float dist)
27{
28        return 6.283185307179586476925286766559f * (1.0f - cos(asin(radius / dist)));
29}
30
31
32
33// reconstruct world space position
34inline float3 ReconstructSamplePos(float eyeSpaceDepth,
35                                                                   float2 texcoord,
36                                                                   float3 bl, float3 br, float3 tl, float3 tr)
37{
38        float3 viewVec = Interpol(texcoord, bl, br, tl, tr);
39        float3 samplePos = -viewVec * eyeSpaceDepth;
40
41        return samplePos;
42}
43
44
45float ComputeConvergence(uniform sampler2D tex, float2 texCoord, float2 res)
46{
47        // get the minimum convergence by exactly sampling the 4 surrounding
48        // texels in the old texture, otherwise flickering because convergence
49        // will be interpolated when upsampling and filter size does not match!
50
51        float4 texelCenterConv;
52        const float2 invRes = float2(1.0f / res.x, 1.0f / res.y);
53
54        // get position exactly between texel centers
55        float2 center = (floor(texCoord * res) + float2(.5f)) * texelCenterConv;
56        //center.x = (floor(texCoord.x * res.x - .5f) + 1.0f) / res.x;
57        //center.y = (floor(texCoord.y * res.y - .5f) + 1.0f) / res.y;
58        //center.y = (floor(texCoord.y * res.y) + .5f) * yOffs;
59
60        /*texelCenterConv.x = tex2Dlod(tex, float4(center + float2( xoffs,  yoffs), 0, 0)).y;
61        texelCenterConv.y = tex2Dlod(tex, float4(center + float2( xoffs, -yoffs), 0, 0)).y;
62        texelCenterConv.z = tex2Dlod(tex, float4(center + float2(-xoffs, -yoffs), 0, 0)).y;
63        texelCenterConv.w = tex2Dlod(tex, float4(center + float2(-xoffs,  yoffs), 0, 0)).y;
64
65        const float m1 = min(texelCenterConv.x, texelCenterConv.y);
66        const float m2 = min(texelCenterConv.z, texelCenterConv.w);
67
68        const float convergence = min(m1, m2);*/
69
70        //const float convergence = tex2Dlod(tex, float4(center, 0, 0)).y;
71        const float convergence = tex2Dlod(tex, float4(texCoord, 0, 0)).y;
72
73        return convergence;
74}
75
76/** This shader computes the reprojection and stores
77        the ssao value of the old pixel as well as the
78        weight of the pixel in the new frame.
79*/
80inline float2 Reproject(float4 worldPos,
81                                                                float eyeSpaceDepth,
82                                                                float2 texcoord0,
83                                                                float3 oldEyePos,
84                                                                sampler2D oldTex,
85                                                                float4x4 oldModelViewProj,
86                                                                sampler2D colors,
87                                                                float3 projPos,
88                                                                float invW,
89                                                                float3 oldbl,
90                                                                float3 oldbr,
91                                                                float3 oldtl,
92                                                                float3 oldtr,
93                                                                float3 diffVec
94                                                                )
95{
96        // compute position from old frame for dynamic objects + translational portion
97        const float3 translatedPos = diffVec - oldEyePos + worldPos.xyz;
98
99
100        /////////////////
101        //-- reproject into old frame and calculate texture position of sample in old frame
102
103        // note: the old model view matrix only holds the view orientation part
104        float4 backProjPos = mul(oldModelViewProj, float4(translatedPos, 1.0f));
105        backProjPos /= backProjPos.w;
106       
107        // fit from unit cube into 0 .. 1
108        const float2 oldTexCoords = backProjPos.xy * 0.5f + 0.5f;
109        // retrieve the sample from the last frame
110        const float4 oldPixel = tex2Dlod(oldTex, float4(oldTexCoords, .0f, .0f));
111
112        // the ssao value in the old frame
113        const float ssao = oldPixel.x;
114
115        // calculate eye space position of sample in old frame
116        const float oldEyeSpaceDepth = oldPixel.w;
117
118        // vector from eye pos to old sample
119        const float3 viewVec = Interpol(oldTexCoords, oldbl, oldbr, oldtl, oldtr);
120        const float invLen = 1.0f / length(viewVec);
121        const float projectedEyeSpaceDepth = invLen * length(translatedPos);
122        //const float projectedEyeSpaceDepth = length(translatedPos);
123       
124        const float depthDif = abs(1.0f - oldEyeSpaceDepth / projectedEyeSpaceDepth);
125
126        // the weight of the accumulated samples from the previous frames
127        float w;
128
129        //////////////
130        //-- reuse old value only if it was still valid in the old frame
131
132        if (1
133                && (oldTexCoords.x > 0) && (oldTexCoords.x < 1.0f)
134                && (oldTexCoords.y > 0) && (oldTexCoords.y < 1.0f)
135                && (depthDif <= MIN_DEPTH_DIFF)
136                )
137        {
138                // pixel valid => retrieve the convergence weight
139                /*float w1 = tex2Dlod(oldTex, float4(oldTexCoords + float2(0.5f / 1024.0f, 0), .0f, .0f)).y;
140                float w2 = tex2Dlod(oldTex, float4(oldTexCoords - float2(0.5f / 1024.0f, 0), .0f, .0f)).y;
141                float w3 = tex2Dlod(oldTex, float4(oldTexCoords + float2(0, 0.5f / 768.0f), .0f, .0f)).y;
142                float w4 = tex2Dlod(oldTex, float4(oldTexCoords - float2(0, 0.5f / 768.0f), .0f, .0f)).y;
143
144                w = min(min(w1, w2), min(w3, w4));*/
145               
146                //w = ComputeConvergence(oldTex, oldTexCoords, float2(1024.0f, 768.0f));
147                w = oldPixel.y;
148        }
149        else
150        {       
151                w = 0.0f;
152        }
153
154        return float2(ssao, w);
155}
156
157
158/** The ssao shader returning the an intensity value between 0 and 1.
159        This version of the ssao shader uses the dotproduct between
160        pixel-to-sample direction and sample normal as weight.
161
162    The algorithm works like the following:
163        1) Check in a circular area around the current position.
164        2) Shoot vectors to the positions there, and check the angle to these positions.
165        3) Summing up these angles gives an estimation of the occlusion at the current position.
166*/
167float3 ssao2(fragment IN,
168                         sampler2D colors,
169                         sampler2D noiseTex,
170                         float2 samples[NUM_SAMPLES],
171                         float3 normal,
172                         float3 centerPosition,
173                         float scaleFactor,
174                         float3 bl,
175                         float3 br,
176                         float3 tl,
177                         float3 tr,
178                         float3 viewDir,
179                         float convergence,
180                         float sampleIntensity,
181                         bool isMovingObject,
182                         sampler2D normalTex
183                         )
184{
185        float total_ao = .0f;
186        float validSamples = .0f;
187        float numSamples = .0f;
188
189        for (int i = 0; i < NUM_SAMPLES; ++ i)
190        {
191                float2 offset;
192
193                ////////////////////
194                //-- add random noise: reflect around random normal vector
195                //-- (affects performance for some reason!)
196
197                if (convergence < SSAO_CONVERGENCE_THRESHOLD)
198                {
199                        float2 mynoise = tex2Dlod(noiseTex, float4(IN.texCoord * 4.0f, 0, 0)).xy;
200                        //offset = myreflect(samples[i], mynoise);
201                        offset = myrotate(samples[i], mynoise.x);
202                }
203                else
204                {
205                        offset = samples[i];
206                }
207               
208                // weight with projected coordinate to reach similar kernel size for near and far
209                const float2 texcoord = IN.texCoord.xy + offset * scaleFactor;
210
211                const float4 sampleColor = tex2Dlod(colors, float4(texcoord, .0f, .0f));
212                const float3 samplePos = ReconstructSamplePos(sampleColor.w, texcoord, bl, br, tl, tr);
213               
214
215                ////////////////
216                //-- compute contribution of sample using the direction and angle
217
218                float3 dirSample = samplePos - centerPosition;
219
220                //const float minDist = 9e-1f;
221                const float minDist = 1e-2f;
222
223                //const float sqrLen = max(SqrLen(dirSample), minDist);
224                //const float lengthToSample = sqrt(sqrLen);
225                const float lengthToSample =  max(length(dirSample), minDist);
226
227                dirSample /= lengthToSample; // normalize
228
229                // angle between current normal and direction to sample controls AO intensity.
230                float cosAngle = dot(dirSample, normal);
231
232                // the normal of the current sample
233                const float3 sampleNormal = tex2Dlod(normalTex, float4(texcoord, 0, 0)).xyz;
234
235                // angle between current normal and direction to sample controls AO intensity.
236                float cosAngle2 = .5f + dot(sampleNormal, -normal) * 0.5f;
237               
238                //const float aoContrib = sampleIntensity / sqrLen;
239                const float aoContrib = sampleIntensity / lengthToSample;
240
241                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
242                total_ao += max(cosAngle, .0f) * aoContrib * cosAngle2;
243
244                ++ numSamples;
245
246                // check if the samples have been valid in the last frame
247                // only mark sample as invalid if in the last / current frame
248                // they possibly have any influence on the ao
249
250                const float changeFactor = sampleColor.y;
251                const float pixelValid = sampleColor.x;
252
253                // hack:
254                // we check if the sample could have been near enough
255                // to the current pixel or if the angle is small enough
256                // to have any influence in the current or last frame
257#if 1
258                const float tooFarAway = step(0.5f, lengthToSample - changeFactor);
259                if (pixelValid < 2.0f)
260                        validSamples = max(validSamples, pixelValid * (1.0f - tooFarAway) * step(-0.1f, cosAngle));
261                else
262                        validSamples = max(validSamples, pixelValid);
263#else
264                validSamples = max(validSamples, pixelValid);
265#endif
266
267#ifdef USE_GTX
268                // we can bail out early and use a minimal #samples)
269                // if some conditions are met as long as the hardware supports it
270                if (numSamples >= MIN_SAMPLES)
271                {
272                        //break;
273                        // if the pixel belongs to a static object and all the samples stay valid in the current frame
274                        if (!isMovingObject && (validSamples < 1.0f) && (convergence > NUM_SAMPLES)) break;
275                        // if the pixel belongs to a dynamic object but the #accumulated samples for this pixel is sufficiently high
276                        // (=> there was no discontinuity recently)
277                        //else if (isMovingObject && (convergence > SSAO_CONVERGENCE_THRESHOLD)) break;
278                        //else if (isMovingObject && (convergence > NUM_SAMPLES * 5)) break;
279                }
280#endif
281        }
282
283        // "normalize" ao contribution
284        total_ao /= numSamples;
285
286#if 1
287        // if surface normal perpenticular to view dir, approx. half of the samples will not count
288        // => compensate for this (on the other hand, projected sampling area could be larger!)
289        const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
290        total_ao *= viewCorrection;
291#endif
292
293        //return float3(total_ao, validSamples, numSamples);
294        return float3(min(1.0f, total_ao), validSamples, numSamples);
295}
296
297
298/** The ssao shader returning the an intensity value between 0 and 1.
299        This version of the ssao shader uses the dotproduct between
300        pixel-to-sample direction and sample normal as weight.
301
302    The algorithm works like the following:
303        1) Check in a circular area around the current position.
304        2) Shoot vectors to the positions there, and check the angle to these positions.
305        3) Summing up these angles gives an estimation of the occlusion at the current position.
306*/
307float3 ssao(fragment IN,
308                        sampler2D colors,
309                        sampler2D noiseTex,
310                        float2 samples[NUM_SAMPLES],
311                        float3 normal,
312                        float3 centerPosition,
313                        float scaleFactor,
314                        float3 bl,
315                        float3 br,
316                        float3 tl,
317                        float3 tr,
318                        float3 viewDir,
319                        float convergence,
320                        float sampleIntensity,
321                        bool isMovingObject
322                        )
323{
324        float total_ao = .0f;
325        float validSamples = .0f;
326        float numSamples = .0f;
327
328        for (int i = 0; i < NUM_SAMPLES; ++ i)
329        {
330                float2 offset;
331
332                ////////////////////
333                //-- add random noise: reflect around random normal vector
334                //-- (affects performance for some reason!)
335
336                if (convergence < SSAO_CONVERGENCE_THRESHOLD)
337                {
338                        float2 mynoise = tex2Dlod(noiseTex, float4(IN.texCoord * 4.0f, 0, 0)).xy;
339                        //offset = myreflect(samples[i], mynoise);
340                        offset = myrotate(samples[i], mynoise.x);
341                }
342                else
343                {
344                        offset = samples[i];
345                }
346               
347                // weight with projected coordinate to reach similar kernel size for near and far
348                const float2 texcoord = IN.texCoord.xy + offset * scaleFactor;
349
350                const float4 sampleColor = tex2Dlod(colors, float4(texcoord, .0f, .0f));
351                const float3 samplePos = ReconstructSamplePos(sampleColor.w, texcoord, bl, br, tl, tr);
352               
353
354                ////////////////
355                //-- compute contribution of sample using the direction and angle
356
357                float3 dirSample = samplePos - centerPosition;
358
359                //const float minDist = 9e-1f;
360                const float minDist = 1e-2f;
361
362                //const float sqrLen = max(SqrLen(dirSample), minDist);
363                //const float lengthToSample = sqrt(sqrLen);
364                const float lengthToSample =  max(length(dirSample), minDist);
365
366                dirSample /= lengthToSample; // normalize
367
368                // angle between current normal and direction to sample controls AO intensity.
369                float cosAngle = dot(dirSample, normal);
370
371                //const float aoContrib = sampleIntensity / sqrLen;
372                const float aoContrib = sampleIntensity / lengthToSample;
373                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
374
375                total_ao += max(cosAngle, 0) * aoContrib;
376
377                ++ numSamples;
378
379                // check if the samples have been valid in the last frame
380                // only mark sample as invalid if in the last / current frame
381                // they possibly have any influence on the ao
382
383                const float changeFactor = sampleColor.y;
384                const float pixelValid = sampleColor.x;
385
386                // hack:
387                // we check if the sample could have been near enough to the current pixel
388                // or if the angle is small enough
389                // to have any influence in the current or last frame
390#if 1
391                const float tooFarAway = step(0.5f, lengthToSample - changeFactor);
392                if (pixelValid < 2.0f)
393                        validSamples = max(validSamples, pixelValid * (1.0f - tooFarAway) * step(-0.1f, cosAngle));
394                else
395                        validSamples = max(validSamples, pixelValid);
396#else
397                validSamples = max(validSamples, pixelValid);
398#endif
399
400#ifdef USE_GTX
401                // we can bail out early and use a minimal #samples)
402                // if some conditions are met as long as the hardware supports it
403                if (numSamples >= MIN_SAMPLES)
404                {
405                        //break;
406                        // if the pixel belongs to a static object and all the samples stay valid in the current frame
407                        if (!isMovingObject && (validSamples < 1.0f) && (convergence > NUM_SAMPLES)) break;
408                        // if the pixel belongs to a dynamic object but the #accumulated samples for this pixel is sufficiently high
409                        // (=> there was no discontinuity recently)
410                        //else if (isMovingObject && (convergence > SSAO_CONVERGENCE_THRESHOLD)) break;
411                        //else if (isMovingObject && (convergence > NUM_SAMPLES * 5)) break;
412                }
413#endif
414        }
415
416        // "normalize" ao contribution
417        total_ao /= numSamples;
418
419#if 1
420        // if surface normal perpenticular to view dir, approx. half of the samples will not count
421        // => compensate for this (on the other hand, projected sampling area could be larger!)
422        const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
423        total_ao *= viewCorrection;
424#endif
425
426        //return float3(total_ao, validSamples, numSamples);
427        return float3(min(1.0f, total_ao), validSamples, numSamples);
428}
429
430
431
432/** The mrt shader for screen space ambient occlusion
433*/
434pixel main(fragment IN,
435                   uniform sampler2D colors,
436                   uniform sampler2D normals,
437                   uniform sampler2D noiseTex,
438                   uniform float2 samples[NUM_SAMPLES],
439                   uniform sampler2D oldTex,
440                   uniform float4x4 modelViewProj,
441                   uniform float4x4 oldModelViewProj,
442                   uniform float temporalCoherence,
443                   uniform float3 bl,
444                   uniform float3 br,
445                   uniform float3 tl,
446                   uniform float3 tr,
447                   uniform float3 oldEyePos,
448                   uniform float3 oldbl,
449                   uniform float3 oldbr,
450                   uniform float3 oldtl,
451                   uniform float3 oldtr,
452                   uniform sampler2D attribsTex,
453                   uniform float kernelRadius,
454                   uniform float sampleIntensity
455                   )
456{
457        pixel OUT;
458
459        //const float3 normal = normalize(tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz);
460        const float3 normal = tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz;
461
462        // reconstruct position from the eye space depth
463        const float3 viewDir = IN.view;
464        const float eyeSpaceDepth = tex2Dlod(colors, float4(IN.texCoord, 0, 0)).w;
465        const float4 eyeSpacePos = float4(-viewDir * eyeSpaceDepth, 1.0f);
466
467        float3 diffVec = tex2Dlod(attribsTex, float4(IN.texCoord, 0, 0)).xyz;
468       
469
470        ////////////////
471        //-- calculcate the current projected posiion (also used for next frame)
472       
473        float4 projPos = mul(modelViewProj, eyeSpacePos);
474        const float invw = 1.0f / projPos.w;
475        projPos *= invw;
476        float scaleFactor = kernelRadius * invw;
477
478        const float sqrMoveSpeed = SqrLen(diffVec);
479        const bool isMovingObject = (sqrMoveSpeed > DYNAMIC_OBJECTS_THRESHOLD);
480
481       
482        /////////////////
483        //-- compute temporal reprojection
484
485        float2 temporalVals = Reproject(eyeSpacePos, eyeSpaceDepth, IN.texCoord, oldEyePos,
486                                        oldTex, oldModelViewProj,
487                                                                        colors,
488                                                                        projPos.xyz,
489                                                                        invw,
490                                                                        oldbl, oldbr, oldtl, oldtr,
491                                                                        diffVec
492                                                                        );
493
494        const float oldSsao = temporalVals.x;
495        float oldWeight = temporalVals.y;
496       
497        float3 ao;
498
499        // cull background note: this should be done with the stencil buffer
500        if (eyeSpaceDepth < DEPTH_THRESHOLD)
501        {
502                //ao = ssao(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor, bl, br, tl, tr, normalize(viewDir), oldWeight, sampleIntensity, isMovingObject);
503                ao = ssao2(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor, bl, br, tl, tr, normalize(viewDir), oldWeight, sampleIntensity, isMovingObject, normals);
504                //ao = ssao2(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor, bl, br, tl, tr, normalize(viewDir), normals, sampleIntensity);
505        }
506        else
507        {
508                 ao = float3(1.0f, 1.0f, 1.0f);
509        }
510
511       
512        ///////////
513        //-- check if we have to reset pixel because one of the sample points was invalid
514        //-- only do this if the current pixel does not belong to a moving object
515
516        // the weight equals the number of sampled shot in this pass
517        const float newWeight = ao.z;
518
519        // completely reset the ao in this pixel
520        const float completelyResetThres = 20.0f;
521        // don't fully reset the ao in this pixel, but give low weight to old solution
522        const float partlyResetThres = 1.0f;
523       
524        if (!isMovingObject)
525        {
526                if (ao.y > completelyResetThres)
527                {
528                        oldWeight = .0f;
529                }
530                else if (ao.y > partlyResetThres)
531                {
532                        oldWeight = min(oldWeight, 4.0f * newWeight);
533                        //oldWeight = .0f;
534                }
535        }
536
537        //////////
538        //-- blend ao between old and new samples (and avoid division by zero)
539
540        OUT.illum_col.x = (ao.x * newWeight + oldSsao * oldWeight);// / (newWeight + oldWeight);//max(1e-6f, newWeight + oldWeight);
541
542        OUT.illum_col.x /= (newWeight + oldWeight);
543
544        // the new weight for the next frame
545        const float combinedWeight = clamp(newWeight + oldWeight, .0f, temporalCoherence);
546
547        OUT.illum_col.y = combinedWeight;
548        // can be used to check if this pixel belongs to a moving object
549        OUT.illum_col.z = SqrLen(diffVec);
550        OUT.illum_col.w = eyeSpaceDepth;
551
552        //OUT.illum_col.yzw = diffVec;
553
554        return OUT;
555}
Note: See TracBrowser for help on using the repository browser.