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

Revision 3311, 14.4 KB checked in by mattausch, 15 years ago (diff)

worked on sampling method for hierarchical poisson

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