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

Revision 3330, 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                const float cosAngle2 = .5f + dot(sampleNormal, -normal) * 0.5f;
254
255                dirSample *= minDist;
256                const float aoContrib = sampleIntensity * sampleWeight;
257
258                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
259                total_ao += max(cosAngle, .0f) * max(cosAngle2, .0f) * aoContrib;
260
261                ++ numSamples;
262
263                // check if the samples have been valid in the last frame
264                // only mark sample as invalid if in the last / current frame
265                // they possibly have any influence on the ao
266
267                const float changeFactor = sampleColor.y;
268                const float pixelValid = sampleColor.x;
269
270                // hack:
271                // we check if the sample could have been near enough
272                // to the current pixel or if the angle is small enough
273                // to have any influence in the current or last frame
274#if 1
275                const float tooFarAway = step(0.5f, lengthToSample - changeFactor);
276
277                const float partlyResetThres = 1.0f;
278
279                if (pixelValid <= partlyResetThres)
280                        validSamples = max(validSamples, pixelValid * (1.0f - tooFarAway) * step(-0.1f, cosAngle));
281                else
282                        validSamples = max(validSamples, pixelValid);
283#endif
284
285#ifdef USE_GTX
286                // we can bail out early and use a minimal #samples)
287                // if some conditions are met as long as the hardware supports it
288                if (numSamples >= MIN_SAMPLES)
289                {
290                        //break;
291                        // if the pixel belongs to a static object and all the samples stay valid in the current frame
292                        if (!isMovingObject && (validSamples < 1.0f) && (convergence > NUM_SAMPLES)) break;
293                        // if the pixel belongs to a dynamic object but the #accumulated samples for this pixel is sufficiently high
294                        // (=> there was no discontinuity recently)
295                        //else if (isMovingObject && (convergence > SSAO_CONVERGENCE_THRESHOLD)) break;
296                        else if (isMovingObject && (convergence > NUM_SAMPLES * 5)) break;
297                }
298#endif
299        }
300
301        // "normalize" ao contribution
302        total_ao /= numSamples;
303
304#if 1
305        // if surface normal perpenticular to view dir, approx. half of the samples will not count
306        // => compensate for this (on the other hand, projected sampling area could be larger!)
307        const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
308        total_ao *= viewCorrection;
309#endif
310
311        //return float3(total_ao, validSamples, numSamples);
312        return float3(min(1.0f, total_ao), validSamples, numSamples);
313}
314
315
316/** The ssao shader returning the an intensity value between 0 and 1.
317        This version of the ssao shader uses the dotproduct between
318        pixel-to-sample direction and sample normal as weight.
319
320    The algorithm works like the following:
321        1) Check in a circular area around the current position.
322        2) Shoot vectors to the positions there, and check the angle to these positions.
323        3) Summing up these angles gives an estimation of the occlusion at the current position.
324*/
325float3 ssao(fragment IN,
326                        sampler2D colors,
327                        sampler2D noiseTex,
328                        sampler2D samples,
329                        float3 normal,
330                        float3 centerPosition,
331                        float scaleFactor,
332                        float3 bl,
333                        float3 br,
334                        float3 tl,
335                        float3 tr,
336                        float3 viewDir,
337                        float convergence,
338                        float sampleIntensity,
339                        bool isMovingObject,
340                        float oldIdx
341                        )
342{
343        float total_ao = .0f;
344        float validSamples = .0f;
345        float numSamples = .0f;
346
347        for (int i = 0; i < NUM_SAMPLES; ++ i)
348        {
349                float2 offset;
350
351                const float2 ssaoOffset =
352                        tex2Dlod(samples, float4((0.5f + i + oldIdx) / NUM_PRECOMPUTED_SAMPLES, 0.5f, .0f, .0f)).xy;
353
354                ////////////////////
355                //-- add random noise: reflect around random normal vector
356                //-- (affects performance for some reason!)
357
358                if (convergence < SSAO_CONVERGENCE_THRESHOLD)
359                {
360                        float2 mynoise = tex2Dlod(noiseTex, float4(IN.texCoord * 4.0f, 0, 0)).xy;
361                        //offset = myreflect(samples[i], mynoise);
362                        //offset = myrotate(samples[i], mynoise.x);
363                        offset = myrotate(ssaoOffset, mynoise.x);
364                }
365                else
366                {
367                        offset = ssaoOffset;
368                }
369
370
371                // weight with projected coordinate to reach similar kernel size for near and far
372                const float2 texcoord = IN.texCoord.xy + offset * scaleFactor;
373
374                const float4 sampleColor = tex2Dlod(colors, float4(texcoord, .0f, .0f));
375                const float3 samplePos = ReconstructSamplePos(sampleColor.w, texcoord, bl, br, tl, tr);
376               
377
378                ////////////////
379                //-- compute contribution of sample using the direction and angle
380
381                float3 dirSample = samplePos - centerPosition;
382
383                const float minDist = 1e-6f;
384                const float scaleFactor = 1e-3f;
385
386                const float lengthToSample = length(dirSample);
387                const float sampleWeight = 1.0f / (lengthToSample + scaleFactor);
388
389                dirSample /= max(length(dirSample), minDist); // normalize
390
391                // angle between current normal and direction to sample controls AO intensity.
392                const float cosAngle = dot(dirSample, normal);
393
394                //const float aoContrib = sampleIntensity / sqrLen;
395                const float aoContrib = sampleIntensity * sampleWeight;
396                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
397
398                total_ao += max(cosAngle, .0f) * aoContrib;
399
400                ++ numSamples;
401
402                // check if the samples have been valid in the last frame
403                // only mark sample as invalid if in the last / current frame
404                // they possibly have any influence on the ao
405
406                const float changeFactor = sampleColor.y;
407                const float pixelValid = sampleColor.x;
408
409                // hack:
410                // we check if the sample could have been near enough to the current pixel
411                // or if the angle is small enough
412                // to have any influence in the current or last frame
413#if 1
414                const float partlyResetThres = 1.0f;
415
416                const float tooFarAway = step(0.5f, lengthToSample - changeFactor);
417                if (0)//pixelValid <= partlyResetThres)
418                        validSamples = max(validSamples, pixelValid * (1.0f - tooFarAway) * step(-0.1f, cosAngle));
419                else
420                        validSamples = max(validSamples, pixelValid);
421#endif
422
423#ifdef USE_GTX
424                // we can bail out early and use a minimal #samples)
425                // if some conditions are met as long as the hardware supports it
426                if (numSamples >= MIN_SAMPLES)
427                {
428                        //break;
429                        // if the pixel belongs to a static object and all the samples stay valid in the current frame
430                        if (!isMovingObject && (validSamples < 1.0f) && (convergence > NUM_SAMPLES)) break;
431                        // if the pixel belongs to a dynamic object but the #accumulated samples for this pixel is sufficiently high
432                        // (=> there was no discontinuity recently)
433                        //else if (isMovingObject && (convergence > SSAO_CONVERGENCE_THRESHOLD)) break;
434                        else if (isMovingObject && (convergence > NUM_SAMPLES * 5)) break;
435                }
436#endif
437        }
438
439        // "normalize" ao contribution
440        total_ao /= numSamples;
441
442#if 1
443        // if surface normal perpenticular to view dir, approx. half of the samples will not count
444        // => compensate for this (on the other hand, projected sampling area could be larger!)
445        const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
446        total_ao *= viewCorrection;
447#endif
448
449        //return float3(total_ao, validSamples, numSamples);
450        return float3(min(1.0f, total_ao), validSamples, numSamples);
451}
452
453
454
455/** The mrt shader for screen space ambient occlusion
456*/
457pixel2 main(fragment IN,
458                        uniform sampler2D colors,
459                        uniform sampler2D normals,
460                        uniform sampler2D noiseTex,
461                        uniform sampler2D samples,
462                        uniform sampler2D oldTex,
463                        uniform float4x4 modelViewProj,
464                        uniform float4x4 oldModelViewProj,
465                        uniform float temporalCoherence,
466                        uniform float3 bl,
467                        uniform float3 br,
468                        uniform float3 tl,
469                        uniform float3 tr,
470                        uniform float3 oldEyePos,
471                        uniform float3 oldbl,
472                        uniform float3 oldbr,
473                        uniform float3 oldtl,
474                        uniform float3 oldtr,
475                        uniform sampler2D attribsTex,
476                        uniform float kernelRadius,
477                        uniform float sampleIntensity
478                        )
479{
480        pixel2 OUT;
481
482        //const float3 normal = normalize(tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz);
483        const float3 normal = tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz;
484
485        // reconstruct position from the eye space depth
486        const float3 viewDir = IN.view;
487        const float eyeSpaceDepth = tex2Dlod(colors, float4(IN.texCoord, 0, 0)).w;
488        const float4 eyeSpacePos = float4(-viewDir * eyeSpaceDepth, 1.0f);
489
490        float3 diffVec = tex2Dlod(attribsTex, float4(IN.texCoord, 0, 0)).xyz;
491       
492
493        ////////////////
494        //-- calculcate the current projected posiion (also used for next frame)
495       
496        float4 projPos = mul(modelViewProj, eyeSpacePos);
497        const float invw = 1.0f / projPos.w;
498        projPos *= invw;
499        float scaleFactor = kernelRadius * invw;
500
501        const float sqrMoveSpeed = SqrLen(diffVec);
502        const bool isMovingObject = (sqrMoveSpeed > DYNAMIC_OBJECTS_THRESHOLD);
503
504       
505        /////////////////
506        //-- compute temporal reprojection
507
508        float3 temporalVals = Reproject(eyeSpacePos, eyeSpaceDepth, IN.texCoord, oldEyePos,
509                                        oldTex, oldModelViewProj,
510                                                                        colors,
511                                                                        projPos.xyz,
512                                                                        invw,
513                                                                        oldbl, oldbr, oldtl, oldtr,
514                                                                        diffVec
515                                                                        );
516
517        const float oldSsao = temporalVals.x;
518       
519        float oldWeight = temporalVals.y;
520        float oldIdx = temporalCoherence > 1 ? temporalVals.z : 0;
521       
522        float3 ao;
523
524        // cull background note: this should be done with the stencil buffer
525        if (eyeSpaceDepth < DEPTH_THRESHOLD)
526        {
527                if (0)
528                {
529                        ao = ssao(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz,
530                                      scaleFactor, bl, br, tl, tr, normalize(viewDir),
531                                          oldWeight, sampleIntensity, isMovingObject, oldIdx);
532                }
533                else
534                {
535                        ao = ssao2(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor,
536                                   bl, br, tl, tr, normalize(viewDir), oldWeight, sampleIntensity,
537                                           isMovingObject, normals, oldIdx);
538                }
539        }
540        else
541        {
542                 ao = float3(1.0f, 1.0f, 1.0f);
543        }
544
545       
546        ///////////
547        //-- check if we have to reset pixel because one of the sample points was invalid
548        //-- only do this if the current pixel does not belong to a moving object
549
550        // the weight equals the number of sampled shot in this pass
551        const float newWeight = ao.z;
552
553        // completely reset the ao in this pixel
554        const float completelyResetThres = 20.0f;
555        // don't fully reset the ao in this pixel, but give low weight to old solution
556        const float partlyResetThres = 1.0f;
557       
558        // don't check for moving objects, otherwise almost no coherence
559        if (!isMovingObject)
560        {
561                if (ao.y > completelyResetThres)
562                {
563                        oldWeight = .0f;
564                        oldIdx = .0f;
565                }
566                else if (ao.y > partlyResetThres)
567                {
568                        oldWeight = min(oldWeight, 4.0f * newWeight);
569                        //oldWeight = .0f;
570                        //oldIdx = .0f;
571                }
572        }
573
574
575        //////////
576        //-- blend ao between old and new samples (and avoid division by zero)
577
578        OUT.illum_col.x = (ao.x * newWeight + oldSsao * oldWeight);
579        OUT.illum_col.x /= (newWeight + oldWeight);
580
581        // the new weight for the next frame
582        const float combinedWeight = clamp(newWeight + oldWeight, .0f, temporalCoherence);
583
584        OUT.illum_col.y = combinedWeight;
585        OUT.illum_col.z = oldIdx + newWeight; // the new index
586        OUT.illum_col.w = eyeSpaceDepth;
587
588        //if (OUT.illum_col.z > 1000) OUT.illum_col.z = 0;
589
590        // this value can be used to check if this pixel belongs to a moving object
591        OUT.col.x = SqrLen(diffVec);
592        //OUT.illum_col.z = SqrLen(diffVec);
593
594        return OUT;
595}
Note: See TracBrowser for help on using the repository browser.