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

Revision 3327, 18.8 KB checked in by mattausch, 15 years ago (diff)

probably found error with texture

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