Another SSAO thread.
Well, you're doing it differently to me. But if you notice in the one I posted, I am grabbing the depth from world space - not world view projection space. Maybe there is something in that. Good luck :D.
I still have absolutely no clue what my problem could be. I have done everything I have read about doing for the last month and I have taken every suggestion and still my shader code seems wrong yet I can not figure out why. Could it actually be the way I render everything? I render everything once (just the depth pass of the shader), then on the second pass I set everything to use the depth pass (AfterAmbient technique). It is still the same way that I said I was doing a few posts earlier. Here is my current shader code. Does anyone know what the problem is?
float4x4 worldViewProjectionMatrix;float4x4 worldMatrix;float4x4 viewMatrix;texture tDepthTexture;sampler2D sDepth = sampler_state{ Texture = <tDepthTexture>; MagFilter = NONE; MinFilter = NONE; MipFilter = NONE;};struct VS_INPUT{ float3 position : POSITION; float2 texCoord : TEXCOORD0; float3 normal : NORMAL;};struct VS_OUTPUT{ float4 position : TEXCOORD0; float2 texCoord : TEXCOORD1;};VS_OUTPUT VS(VS_INPUT IN, out float4 outPos : POSITION){ VS_OUTPUT OUT; outPos = mul(float4(IN.position, 1.0f), worldViewProjectionMatrix); OUT.position = outPos; OUT.texCoord = IN.texCoord; return OUT;}float4 DepthPass(VS_OUTPUT IN) : COLOR{ float fDC = IN.position.z / IN.position.w; return float4(fDC, fDC, fDC, 1.0f);}float4 RegularPass(VS_OUTPUT IN) : COLOR{ float4 ambientColor = tex2D(sDepth, IN.texCoord); float4 returnColor = float4(ambientColor.r, ambientColor.r, ambientColor.r, 1.0f); return returnColor;}technique DepthPass{ pass { VertexShader = compile vs_2_0 VS(); PixelShader = compile ps_2_0 DepthPass(); }}technique AfterAmbient{ pass { VertexShader = compile vs_2_0 VS(); PixelShader = compile ps_2_0 RegularPass(); }}
I think I may have found a flaw in the way I am doing it, although I am not sure. As I mentioned before, I send everything through the depth pass and render to target the screens target (for a texture). However, I then put that texture on every object. Does that sound wrong to anyone else?
Yes, you shouldn't be texturing anything( apart from your quad ). It's post-processing shader.
Here is the 4 that I have. Although, I still don't have the bias in.
Depth:
SSAO:
Blur:
Composite:
Edit: Forgot to add. For these shaders, I had no need to modify the co-ords because the engine I am using for this sample does it for me. You may very well need to do this yourself. Anyway, I hope the code helps somewhat. I had a bitch of a time getting it up, it seems a twitchy technique. Good luck. :)
[Edited by - AriusMyst on June 14, 2008 9:29:28 AM]
Here is the 4 that I have. Although, I still don't have the bias in.
Depth:
float4x4 World : WORLDVIEW; float4x4 wViewProj: WORLDVIEWPROJ; struct a2v { float4 Position: POSITION0; float3 Normal : NORMAL; }; struct v2f { float4 Position: POSITION0; float Test : TEXCOORD0; float3 Normal : TEXCOORD1; }; struct f2s { float4 Colour : COLOR0; }; void vp(in a2v IN, out v2f OUT) { OUT.Position = mul(IN.Position, World); OUT.Test = length(OUT.Position.xyz); OUT.Position = mul(IN.Position, wViewProj); OUT.Normal = IN.Normal; } void fp(in v2f IN, out f2s OUT) { float Deep = IN.Test; float3 N = normalize(IN.Normal); OUT.Colour = float4(N.r, N.g, N.b, Deep); } technique { pass Pass0 { VertexShader = compile vs_1_1 vp(); PixelShader = compile ps_2_0 fp(); } }
SSAO:
float4x4 Projection: PROJECTION; texture Deepa; texture Rand; float SampleRadius = 0.84; float DistanceScale = 3.5; float OccludeDist = 7.5; float3 Direction; sampler2D DeepSample = sampler_state { Texture = (Deepa); MIPFILTER = LINEAR; MAGFILTER = LINEAR; MINFILTER = LINEAR; }; sampler2D RandSample = sampler_state { Texture = (Rand); MIPFILTER = LINEAR; MAGFILTER = LINEAR; MINFILTER = LINEAR; }; struct app2vp { float4 Position: POSITION; float2 UV : TEXCOORD0; }; struct vp2fp { float4 Position: POSITION; float2 UV : TEXCOORD0; float3 Corner : TEXCOORD1; }; struct fp2s { float4 Colour : COLOR0; }; void vp( in app2vp IN, out vp2fp OUT ) { OUT.Position = IN.Position; OUT.UV = IN.UV; float2 ScreenPos = IN.UV; ScreenPos.y = 1.0f - ScreenPos.y; ScreenPos = ScreenPos * 2 - 1; OUT.Corner = float3(IN.Position.xy, 1.0f) * Direction; } void fp( in vp2fp IN, out fp2s OUT ) { float4 Samples[16] = { float4(0.355512, -0.709318, -0.102371, 0.0 ), float4(0.534186, 0.71511, -0.115167, 0.0 ), float4(-0.87866, 0.157139, -0.115167, 0.0 ), float4(0.140679, -0.475516, -0.0639818, 0.0 ), float4(-0.0796121, 0.158842, -0.677075, 0.0 ), float4(-0.0759516, -0.101676, -0.483625, 0.0 ), float4(0.12493, -0.0223423, -0.483625, 0.0 ), float4(-0.0720074, 0.243395, -0.967251, 0.0 ), float4(-0.207641, 0.414286, 0.187755, 0.0 ), float4(-0.277332, -0.371262, 0.187755, 0.0 ), float4(0.63864, -0.114214, 0.262857, 0.0 ), float4(-0.184051, 0.622119, 0.262857, 0.0 ), float4(0.110007, -0.219486, 0.435574, 0.0 ), float4(0.235085, 0.314707, 0.696918, 0.0 ), float4(-0.290012, 0.0518654, 0.522688, 0.0 ), float4(0.0975089, -0.329594, 0.609803, 0.0 ) }; float fColour = 0.0f; float2 nUV = IN.UV; float Deep = tex2D(DeepSample, nUV).a; float3 Norm = tex2D(RandSample, IN.UV * 200).rgb; float3 SE = (normalize(IN.Corner) * Deep); for(int i = 0; i < 16; i++) { float3 Ray = reflect(Samples.xyz, Norm) * SampleRadius; float4 Sample = float4(Ray + SE, 1); float4 SS = mul(Sample, Projection); float2 sUV = 0.5 * (SS.xy/SS.w) + float2(0.5, 0.5); sUV.y = 1-sUV.y; float sDeep = tex2D(DeepSample, sUV).a; //this check is hacky, change it when you have time. if(length(Deep - sDeep) > OccludeDist) { fColour++; } else { float Occlude = DistanceScale * max(Deep - sDeep, 0); fColour += 1 / (1 + (Occlude * Occlude) * 0.1); } } fColour = fColour/16; OUT.Colour = float4(fColour, fColour, fColour, 1); } technique null { pass Pass0 { VertexShader = compile vs_3_0 vp(); PixelShader = compile ps_3_0 fp(); } }
Blur:
float2 Direction = float2(1.0f/1024.0f, 0); texture Deepa; texture SSAO; sampler2D DeepSample = sampler_state { Texture = (Deepa); MIPFILTER = LINEAR; MAGFILTER = LINEAR; MINFILTER = LINEAR; }; sampler2D SSAOSample = sampler_state { Texture = (SSAO); MIPFILTER = LINEAR; MAGFILTER = LINEAR; MINFILTER = LINEAR; }; struct a2v { float4 Position: POSITION0; float2 UV : TEXCOORD0; }; struct v2f { float4 Position: POSITION0; float2 UV : TEXCOORD0; }; void vp(in a2v IN, out v2f OUT) { OUT.Position = IN.Position; OUT.UV = IN.UV; } float4 fp(in v2f IN): COLOR0 { float Deep = tex2D(DeepSample, IN.UV).r; float3 Norm = tex2D(DeepSample, IN.UV).rgb; float AO = tex2D(SSAOSample, IN.UV).r; float Num = 1; int Sam = 32; for(int i = -Sam/2; i <= Sam/2; i+=1) { float2 nUV = float2(IN.UV + i * Direction.xy); float Sample = tex2D(SSAOSample, nUV).r; float3 sNorm = tex2D(DeepSample, nUV).rgb; if(dot(Norm, sNorm) > 0.99) { Num += (Sam/2 - abs(i)); AO += Sample * (Sam/2 - abs(i)); } } return AO / Num; }
Composite:
texture SSAOTex;texture SceneTexture;float2 PixelOffset = float2( 0.001953125, 0.001953125 );sampler2D ScreenSampler: TEXUNIT0 = sampler_state{ Texture = (SceneTexture);};sampler2D SSAOSampler: TEXUNIT0 = sampler_state{ Texture = (SSAOTex);};struct a2v{ float4 Position: POSITION; float2 UV : TEXCOORD0;};struct v2p{ float4 Position: POSITION; float2 UV : TEXCOORD0;};struct p2s{ float4 Colour : COLOR0;};void vp( in a2v IN, out v2p OUT ){ OUT.Position = IN.Position; OUT.UV = IN.UV;}void fp( in v2p IN, out p2s OUT ){ float4 Scene = tex2D(ScreenSampler, IN.UV); float4 SSAO = tex2D(SSAOSampler, IN.UV); float4 Grin = SSAO.r * Scene; OUT.Colour = Grin; OUT.Colour.a = 1;}technique null{ pass Pass0 { VertexShader = compile vs_1_1 vp(); PixelShader = compile ps_2_0 fp(); }}
Edit: Forgot to add. For these shaders, I had no need to modify the co-ords because the engine I am using for this sample does it for me. You may very well need to do this yourself. Anyway, I hope the code helps somewhat. I had a bitch of a time getting it up, it seems a twitchy technique. Good luck. :)
[Edited by - AriusMyst on June 14, 2008 9:29:28 AM]
Quote:Original post by Hurp
I think I may have found a flaw in the way I am doing it, although I am not sure. As I mentioned before, I send everything through the depth pass and render to target the screens target (for a texture). However, I then put that texture on every object. Does that sound wrong to anyone else?
I do the SSAO pass before rendering all the scene objects with their lighting/material shaders, and I pass the SSAO output texture to those shaders. Then I sample the SSAO buffer and multiply it with the ambient, and also attenuate the sunlight a bit.
Sorry for being dense here, but I do not understand exactly what the view frustum corners are used for? Are they just for reconstructing view-space position from depth? If SM 3.0+ is the only target platform, can VPOS be used instead?
Quote:Original post by rgoer
Sorry for being dense here, but I do not understand exactly what the view frustum corners are used for? Are they just for reconstructing view-space position from depth? If SM 3.0+ is the only target platform, can VPOS be used instead?
Yes, that's what they're used for. You could use VPOS to do this, but you also need to know the parameters of the perspective projection in order to actually determine the frustum ray. You could get these parameters from the projection matrix or by sending them as shader constants, but you'll use more instructions in the creation of calculation of the ray (by passing in the frustum corners you get the ray "for free" through interpolation). Of course...on SM30 hardware 2 or 3 arithmetic instructions probably don't mean very much.
Some additional discussion rgoer and I had via PM...
Yes, this is a per-pixel view vector. To visualize this vector, picture a ray that starts at the camera's position and goes all the way to the far plane of the view frustum, interesecting at the point representing the current pixel that the shader is calculating. There are a few ways to get this ray...what AriusMyst does is he takes the position of the upper-right far frustum corner in view-space and multiplies it with the normalized-device-coordinate of the current pixel. This effectively gives you the ray I'm talking about, since NCD's are of the range [-1,1]. So If you multiply the upper right corner by the NCD of the upper right pixel (1,1,1) you get the upper right frustum coordinate (which you want). If you multiply by the NCD of the bottom left pixel you'd get the opposite X and Y, which again what you want. This works for every pixel. Another common approach (and what I do) is in each vertex of the quad, I figure out the position of the corresponding frustum corner and pass it on to the pixel shader (but don't normalize it!). This way the right position is given to you though interpolation.
Now the whole purpose of this is to calculate the original view-space position of a given pixel using the depth buffer. The idea is this: you have a depth value of the range [0,1], with 0 corresponding to a view-space Z value of 0 and 1 corresponding to the a view-space Z value of the far clipping plane. So try to picture this: you already have that ray going all way from the camera position to the back of the frustum, and you just need to know how "far along" that ray you need to go in order to find the right position: this is what the value from the depth buffer tells you. When you multiply the depth with the ray, you get view-space position
Yes. The idea here, is that you have this sample position and you want to compare the depth of this sample position to the depth in the depth buffer. So to do this we simply have to sample the depth buffer at the texel corrosponding to this new sample position. In order to figure out the UV address of this texel, we have to figure out the clip-space position of this new sample position by multiplying the view-space position with the original projection matrix. I'm guessing the "SS" here stands for "screen-space"...even though what we get isn't really screen-space you can think of it that way.
Once we have the clip-space position, we perform the usual conversion to texture coordinates. This is that last part you asked about...we perform homogeneous divide by w in order to get normalized device coordinates. However like I said before NCD's are of the range [-1,1], and texture coordinates are of the range [0,1]. So we multiply by 0.5 and add 0.5 to correct that. The last step, where V is calculated by taking 1 and subtracting y, is to account for the fact that the Y of an NCD is backwards compared to the V of a texture coordinate. V is equal to 0 at the top of the screen and equal to 1 at the bottom, while the Y of an NCD is equal to 1 at the top of the screen and -1 at the bottom.
Quote:
1. He passes "direction" into the SSAO pixel shader as a global. Is this just the per-pixel view vector? Or is this some other "direction"? I'm not sure if this is something obvious, or a pecularity of his specific shader.
2. He calculates the position of the frustum corners, using the input vertex position and the direction global. Then further down he calculates "SE" (?) by multiplying the corner position by the depth value for a given pixel. What exactly is going on here? Why does the position of the frustum corner matter? And what is he calculating by multiplying it by depth? Is this just to get view-space position data for each pixel of the depth buffer? If so, would you mind explaining a little bit of why it is done this way (I have read many of the threads about reconstructing view-space position from depth, but I still don't quite get it, haha)?
Yes, this is a per-pixel view vector. To visualize this vector, picture a ray that starts at the camera's position and goes all the way to the far plane of the view frustum, interesecting at the point representing the current pixel that the shader is calculating. There are a few ways to get this ray...what AriusMyst does is he takes the position of the upper-right far frustum corner in view-space and multiplies it with the normalized-device-coordinate of the current pixel. This effectively gives you the ray I'm talking about, since NCD's are of the range [-1,1]. So If you multiply the upper right corner by the NCD of the upper right pixel (1,1,1) you get the upper right frustum coordinate (which you want). If you multiply by the NCD of the bottom left pixel you'd get the opposite X and Y, which again what you want. This works for every pixel. Another common approach (and what I do) is in each vertex of the quad, I figure out the position of the corresponding frustum corner and pass it on to the pixel shader (but don't normalize it!). This way the right position is given to you though interpolation.
Now the whole purpose of this is to calculate the original view-space position of a given pixel using the depth buffer. The idea is this: you have a depth value of the range [0,1], with 0 corresponding to a view-space Z value of 0 and 1 corresponding to the a view-space Z value of the far clipping plane. So try to picture this: you already have that ray going all way from the camera position to the back of the frustum, and you just need to know how "far along" that ray you need to go in order to find the right position: this is what the value from the depth buffer tells you. When you multiply the depth with the ray, you get view-space position
Quote:
3. "Sample" is a view-space position, offset by the random ray, correct? Why does he mul Sample by the projection matrix? What is "SS"?
4. sUV looks like it does perspective projection (dividing by w) then applies scale/bias to get from -1..1 to 0..1 space. What is this about? And why does the y channel of sUV get inverted?
Yes. The idea here, is that you have this sample position and you want to compare the depth of this sample position to the depth in the depth buffer. So to do this we simply have to sample the depth buffer at the texel corrosponding to this new sample position. In order to figure out the UV address of this texel, we have to figure out the clip-space position of this new sample position by multiplying the view-space position with the original projection matrix. I'm guessing the "SS" here stands for "screen-space"...even though what we get isn't really screen-space you can think of it that way.
Once we have the clip-space position, we perform the usual conversion to texture coordinates. This is that last part you asked about...we perform homogeneous divide by w in order to get normalized device coordinates. However like I said before NCD's are of the range [-1,1], and texture coordinates are of the range [0,1]. So we multiply by 0.5 and add 0.5 to correct that. The last step, where V is calculated by taking 1 and subtracting y, is to account for the fact that the Y of an NCD is backwards compared to the V of a texture coordinate. V is equal to 0 at the top of the screen and equal to 1 at the bottom, while the Y of an NCD is equal to 1 at the top of the screen and -1 at the bottom.
I have a general question about SSAO rendering. Is this the order in which I am suppose to render everything?
1) Render everything in a depth pass to a render target.
2) Get that full screen render target, then do the actual ambient occlusion to that render target.
3) In a third pass take that render target and apply lighting/texturing to a new render target?
4) Render the third render target.
This seems more like deferred shading though, is there a way to do it without?
1) Render everything in a depth pass to a render target.
2) Get that full screen render target, then do the actual ambient occlusion to that render target.
3) In a third pass take that render target and apply lighting/texturing to a new render target?
4) Render the third render target.
This seems more like deferred shading though, is there a way to do it without?
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement