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

Revision 3231, 13.8 KB checked in by mattausch, 16 years ago (diff)

worked on sampling pattern

Line 
1#include "../shaderenv.h"
2#include "common.h"
3
4////////////////////
5// Screen Spaced Ambient Occlusion shader
6// based on shader of Alexander Kusternig
7
8
9#define USE_EYESPACE_DEPTH 1
10
11
12struct fragment
13{
14        float2 texCoord: TEXCOORD0;
15        float3 view: TEXCOORD1;
16};
17
18
19struct pixel
20{
21        float4 illum_col: COLOR0;
22};
23
24
25inline float occlusionPower(float radius, float dist)
26{
27        return 6.283185307179586476925286766559f * (1.0f - cos(asin(radius / dist)));
28}
29
30
31
32// reconstruct world space position
33inline float3 ReconstructSamplePos(float eyeSpaceDepth,
34                                                                   float2 texcoord,
35                                                                   float3 bl, float3 br, float3 tl, float3 tr)
36{
37        float3 viewVec = Interpol(texcoord, bl, br, tl, tr);
38        float3 samplePos = -viewVec * eyeSpaceDepth;
39
40        return samplePos;
41}
42
43
44
45/** This shader computes the reprojection and stores
46        the ssao value of the old pixel as well as the
47        weight of the pixel in the new frame.
48*/
49inline float2 temporalSmoothing(float4 worldPos,
50                                                                float eyeSpaceDepth,
51                                                                float2 texcoord0,
52                                                                float3 oldEyePos,
53                                                                sampler2D oldTex,
54                                                                float4x4 oldModelViewProj,
55                                                                sampler2D colors,
56                                                                float3 projPos,
57                                                                float invW,
58                                                                float3 oldbl,
59                                                                float3 oldbr,
60                                                                float3 oldtl,
61                                                                float3 oldtr,
62                                                                float3 diffVec
63                                                                )
64{
65        // compute position from old frame for dynamic objects + translational portion
66        const float3 translatedPos = diffVec - oldEyePos + worldPos.xyz;
67
68
69        /////////////////
70        //-- reproject into old frame and calculate texture position of sample in old frame
71
72        // note: the old model view matrix only holds the view orientation part
73        float4 backProjPos = mul(oldModelViewProj, float4(translatedPos, 1.0f));
74        backProjPos /= backProjPos.w;
75       
76        // fit from unit cube into 0 .. 1
77        const float2 oldTexCoords = backProjPos.xy * 0.5f + 0.5f;
78        // retrieve the sample from the last frame
79        const float4 oldPixel = tex2Dlod(oldTex, float4(oldTexCoords, .0f, .0f));
80
81        // the ssao value in the old frame
82        const float ssao = oldPixel.x;
83
84        // calculate eye space position of sample in old frame
85        const float oldEyeSpaceDepth = oldPixel.w;
86
87        // vector from eye pos to old sample
88        const float3 viewVec = Interpol(oldTexCoords, oldbl, oldbr, oldtl, oldtr);
89        const float invLen = 1.0f / length(viewVec);
90        const float projectedEyeSpaceDepth = invLen * length(translatedPos);
91        //const float projectedEyeSpaceDepth = length(translatedPos);
92       
93        const float depthDif = abs(1.0f - oldEyeSpaceDepth / projectedEyeSpaceDepth);
94
95        // the weight of the accumulated samples from the previous frames
96        float w;
97
98        //////////////
99        //-- reuse old value only if it was still valid in the old frame
100
101        if (1
102                && (oldTexCoords.x > 0) && (oldTexCoords.x < 1.0f)
103                && (oldTexCoords.y > 0) && (oldTexCoords.y < 1.0f)
104                && (depthDif <= MIN_DEPTH_DIFF)
105                )
106        {
107                // pixel valid => retrieve the convergence weight
108                w = oldPixel.y;
109        }
110        else
111        {       
112                w = 0.0f;
113        }
114
115        return float2(ssao, w);
116}
117
118
119/** The ssao shader returning the an intensity value between 0 and 1
120        This version of the ssao shader uses the dotproduct between pixel and
121        sample normal as weight.
122*/
123float3 ssao2(fragment IN,
124                         sampler2D colors,
125                         sampler2D noiseTex,
126                         float2 samples[NUM_SAMPLES],
127                         float3 normal,
128                         float3 centerPosition,
129                         float scaleFactor,
130                         float3 bl,
131                         float3 br,
132                         float3 tl,
133                         float3 tr,
134                         float3 viewDir,
135                         sampler2D normalTex,
136                         float sampleIntensity
137                         )
138{
139        float total_ao = .0f;
140        float numSamples = .0f;
141        float validSamples = .0f;
142
143        for (int i = 0; i < NUM_SAMPLES; ++ i)
144        {
145                const float2 offset = samples[i];
146
147#if 1
148                ////////////////////
149                //-- add random noise: reflect around random normal vector (rather slow!)
150
151                const float2 mynoise = tex2Dlod(noiseTex, float4(IN.texCoord * 4.0f, 0, 0)).xy;
152                const float2 offsetTransformed = myreflect(offset, mynoise);
153#else
154                const float2 offsetTransformed = offset;
155#endif
156                // weight with projected coordinate to reach similar kernel size for near and far
157                //const float2 texcoord = IN.texCoord.xy + offsetTransformed * scaleFactor + jitter;
158                const float2 texcoord = IN.texCoord.xy + offsetTransformed * scaleFactor;
159
160                //if ((texcoord.x <= 1.0f) && (texcoord.x >= 0.0f) && (texcoord.y <= 1.0f) && (texcoord.y >= 0.0f)) ++ numSamples;
161                float4 sampleColor = tex2Dlod(colors, float4(texcoord, 0, 0));
162
163                const float3 samplePos = ReconstructSamplePos(sampleColor.w, texcoord, bl, br, tl, tr);
164                // the normal of the current sample
165                const float3 sampleNormal = tex2Dlod(normalTex, float4(texcoord, 0, 0)).xyz;
166
167
168                ////////////////
169                //-- compute contribution of sample using the direction and angle
170
171                float3 dirSample = samplePos - centerPosition;
172
173                const float sqrLen = max(SqrLen(dirSample), 1e-2f);
174                const float lengthToSample = sqrt(sqrLen);
175                //const float lengthToSample = max(length(dirSample), 1e-6f);
176
177                dirSample /= lengthToSample; // normalize
178
179                // angle between current normal and direction to sample controls AO intensity.
180                float cosAngle = .5f + dot(sampleNormal, -normal) * 0.5f;
181                // use binary decision to cull samples that are behind current shading point
182                cosAngle *= step(0.0f, dot(dirSample, normal));
183       
184                const float aoContrib = sampleIntensity / sqrLen;
185                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
186
187                total_ao += cosAngle * aoContrib;
188
189                // check if the samples have been valid in the last frame
190                validSamples += (1.0f - step(1.0f, lengthToSample)) * sampleColor.x;
191
192                ++ numSamples;
193        }
194
195        total_ao /= numSamples;
196
197#if 1
198        // if surface normal perpenticular to view dir, approx. half of the samples will not count
199        // => compensate for this (on the other hand, projected sampling area could be larger!)
200        const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
201        total_ao += cosAngle * aoContrib * viewCorrection;
202
203#endif
204
205        return float3(max(0.0f, 1.0f - total_ao), validSamples, numSamples);
206}
207
208
209/** The ssao shader returning the an intensity value between 0 and 1.
210        This version of the ssao shader uses the dotproduct between
211        pixel-to-sample direction and sample normal as weight.
212
213    The algorithm works like the following:
214        1) Check in a circular area around the current position.
215        2) Shoot vectors to the positions there, and check the angle to these positions.
216        3) Summing up these angles gives an estimation of the occlusion at the current position.
217*/
218float3 ssao(fragment IN,
219                        sampler2D colors,
220                        sampler2D noiseTex,
221                        float2 samples[NUM_SAMPLES],
222                        float3 normal,
223                        float3 centerPosition,
224                        float scaleFactor,
225                        float3 bl,
226                        float3 br,
227                        float3 tl,
228                        float3 tr,
229                        float3 viewDir,
230                        float convergence,
231                        float sampleIntensity,
232                        bool isMovingObject
233                        )
234{
235        float total_ao = .0f;
236        float validSamples = .0f;
237        float numSamples = .0f;
238
239        for (int i = 0; i < NUM_SAMPLES; ++ i)
240        {
241                float2 offset;
242
243                ////////////////////
244                //-- add random noise: reflect around random normal vector
245                //-- (affects performance for some reason!)
246
247                if (1)//convergence < 700)
248                {
249                        float2 mynoise = tex2Dlod(noiseTex, float4(IN.texCoord * 4.0f, 0, 0)).xy;
250                        //offsetTransformed = myreflect(offset, mynoise);
251                        offset = myrotate(samples[i], mynoise.x);
252                }
253                else
254                {
255                        offset = samples[i];
256                }
257               
258                // weight with projected coordinate to reach similar kernel size for near and far
259                const float2 texcoord = IN.texCoord.xy + offset * scaleFactor;
260
261                const float4 sampleColor = tex2Dlod(colors, float4(texcoord, .0f, .0f));
262                const float3 samplePos = ReconstructSamplePos(sampleColor.w, texcoord, bl, br, tl, tr);
263               
264
265                ////////////////
266                //-- compute contribution of sample using the direction and angle
267
268                float3 dirSample = samplePos - centerPosition;
269
270                //const float sqrLen = max(SqrLen(dirSample), 1e-2f);
271                //const float lengthToSample = sqrt(sqrLen);
272                const float lengthToSample =  max(length(dirSample), 1e-2f);
273
274                dirSample /= lengthToSample; // normalize
275
276                // angle between current normal and direction to sample controls AO intensity.
277                float cosAngle = dot(dirSample, normal);
278
279                //const float aoContrib = sampleIntensity / sqrLen;
280                const float aoContrib = sampleIntensity / lengthToSample;
281                //const float aoContrib = (1.0f > lengthToSample) ? occlusionPower(9e-2f, DISTANCE_SCALE + lengthToSample): .0f;
282
283                total_ao += max(cosAngle, 0) * aoContrib;
284
285                ++ numSamples;
286
287                // check if the samples have been valid in the last frame
288                // only mark sample as invalid if in the last / current frame
289                // they possibly have any influence on the ao
290                const float changeFactor = sampleColor.y;
291                const float pixelValid = sampleColor.x;
292
293                // we check if the sample could have been near enough to the current pixel
294                // to have any influence in the current or last frame
295                const float tooFarAway = step(0.5f, lengthToSample - changeFactor);
296                validSamples = max(validSamples, (1.0f - tooFarAway) * pixelValid * step(-0.1f, cosAngle));
297
298#ifdef USE_GTX
299                // we can bail out early and use a minimal #samples)
300                // if some conditions are met as long as the hardware supports it
301                if (numSamples >= MIN_SAMPLES)
302                {
303                        //break;
304                        // if the pixel belongs to a static object and all the samples stay valid in the current frame
305                        if (!isMovingObject && (validSamples < 1.0f)) break;
306                        // if the pixel belongs to a dynamic object but the #accumulated samples for this pixel is sufficiently high
307                        // (=> there was no discontinuity recently)
308                        else if (isMovingObject && (convergence > NUM_SAMPLES * 5)) break;
309                }
310#endif
311        }
312
313        // "normalize" ao contribution
314        total_ao /= numSamples;
315
316#if 1
317        // if surface normal perpenticular to view dir, approx. half of the samples will not count
318        // => compensate for this (on the other hand, projected sampling area could be larger!)
319        const float viewCorrection = 1.0f + VIEW_CORRECTION_SCALE * max(dot(viewDir, normal), 0.0f);
320        total_ao *= viewCorrection;
321#endif
322
323        return float3(total_ao, validSamples, numSamples);
324}
325
326
327
328/** The mrt shader for screen space ambient occlusion
329*/
330pixel main(fragment IN,
331                   uniform sampler2D colors,
332                   uniform sampler2D normals,
333                   uniform sampler2D noiseTex,
334                   uniform float2 samples[NUM_SAMPLES],
335                   uniform sampler2D oldTex,
336                   uniform float4x4 modelViewProj,
337                   uniform float4x4 oldModelViewProj,
338                   uniform float temporalCoherence,
339                   uniform float3 bl,
340                   uniform float3 br,
341                   uniform float3 tl,
342                   uniform float3 tr,
343                   uniform float3 oldEyePos,
344                   uniform float3 oldbl,
345                   uniform float3 oldbr,
346                   uniform float3 oldtl,
347                   uniform float3 oldtr,
348                   uniform sampler2D attribsTex,
349                   uniform float kernelRadius,
350                   uniform float sampleIntensity
351                   )
352{
353        pixel OUT;
354
355        //const float3 normal = normalize(tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz);
356        const float3 normal = tex2Dlod(normals, float4(IN.texCoord, 0 ,0)).xyz;
357
358        // reconstruct position from the eye space depth
359        const float3 viewDir = IN.view;
360        const float eyeSpaceDepth = tex2Dlod(colors, float4(IN.texCoord, 0, 0)).w;
361        const float4 eyeSpacePos = float4(-viewDir * eyeSpaceDepth, 1.0f);
362
363        float3 diffVec = tex2Dlod(attribsTex, float4(IN.texCoord, 0, 0)).xyz;
364       
365
366        ////////////////
367        //-- calculcate the current projected posiion (also used for next frame)
368       
369        float4 projPos = mul(modelViewProj, eyeSpacePos);
370        const float invw = 1.0f / projPos.w;
371        projPos *= invw;
372        float scaleFactor = kernelRadius * invw;
373
374        const float sqrMoveSpeed = SqrLen(diffVec);
375        const bool isMovingObject = (sqrMoveSpeed > DYNAMIC_OBJECTS_THRESHOLD);
376
377       
378        /////////////////
379        //-- compute temporal reprojection
380
381        float2 temporalVals = temporalSmoothing(eyeSpacePos, eyeSpaceDepth, IN.texCoord, oldEyePos,
382                                                oldTex, oldModelViewProj,
383                                                                                        colors,
384                                                                                        projPos.xyz,
385                                                                                        invw,
386                                                                                        oldbl, oldbr, oldtl, oldtr,
387                                                                                        diffVec
388                                                                                        );
389
390        const float oldSsao = temporalVals.x;
391        float oldWeight = temporalVals.y;
392       
393        float3 ao;
394
395        // cull background note: this should be done with the stencil buffer
396        if (eyeSpaceDepth < 1e10f)
397        {
398                ao = ssao(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor, bl, br, tl, tr, normalize(viewDir), oldWeight, sampleIntensity, isMovingObject);
399                //ao = ssao2(IN, colors, noiseTex, samples, normal, eyeSpacePos.xyz, scaleFactor, bl, br, tl, tr, normalize(viewDir), normals, sampleIntensity);
400        }
401        else
402        {
403                 ao = float3(1.0f, 1.0f, 1.0f);
404        }
405
406       
407        ///////////
408        //-- check if we have to reset pixel because one of the sample points was invalid
409        //-- only do this if the current pixel does not belong to a moving object
410
411        // the weight equals the number of sampled shot in this pass
412        const float newWeight = ao.z;
413
414        // completely reset the ao in this pixel
415        const float completelyResetThres = 4.0f;
416        // don't fully reset the ao in this pixel, but give low weight to old solution
417        const float partlyResetThres = 1.0f;
418       
419        if (!isMovingObject)
420        {
421                if (ao.y > completelyResetThres)
422                {
423                        oldWeight = .0f;
424                }
425                else if (ao.y > partlyResetThres)
426                {
427                        oldWeight = min(oldWeight, 4.0f * newWeight);
428                        //oldWeight = .0f;
429                }
430        }
431
432        //////////
433        //-- blend ao between old and new samples (and avoid division by zero)
434
435        OUT.illum_col.x = (ao.x * newWeight + oldSsao * oldWeight);// / (newWeight + oldWeight);//max(1e-6f, newWeight + oldWeight);
436
437        OUT.illum_col.x /= (newWeight + oldWeight);
438
439        // the new weight for the next frame
440        const float combinedWeight = clamp(newWeight + oldWeight, .0f, temporalCoherence);
441
442        OUT.illum_col.y = combinedWeight;
443        // can be used to check if this pixel belongs to a moving object
444        OUT.illum_col.z = SqrLen(diffVec);
445        OUT.illum_col.w = eyeSpaceDepth;
446
447        //OUT.illum_col.yzw = diffVec;
448
449        return OUT;
450}
Note: See TracBrowser for help on using the repository browser.