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

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