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

Revision 3328, 18.6 KB checked in by mattausch, 15 years ago (diff)

found error, working ok

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