Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

[D3D9] Deferred Rendering: Point Lights (includes PICTURES)

Started by d h k Nov 8, 2011 at 7:58 PM 35 replies 9.6k views
Original Post
d h k
d h k
I have ambient and directional lighting working just fine in my deferred renderer, now it's time for the slightly (to my eyes, vastly) more complex point lights! The only resource I managed to find that actually goes through the motions of implementing point lights (and doesn't just end on: "now you have the g-buffer, all that's left to do is to render light geometry: quad for directional, sphere for point and cone for spotlight") was this tutorial. My first issue is that I don't fully understand the idea of rendering a sphere for a point light, take a look at this shot right from my program: defrend_point1.png This is my point light geometry (a sphere) rendered normally as if it was real geometry like the level around it. It's a sphere with radius 1, centered on the point light's position, scaled to the light's size. Here's my point light shader, taken straight from the aforementioned tutorial minus the specular component stuff: [source lang="cpp"] float4x4 mat_world; float4x4 mat_view; float4x4 mat_projection; //this is used to compute the world-position float4x4 mat_invviewproj; //color of the light float3 light_color; //this is the position of the light float3 light_position; //how far does this light reach float light_radius; //control the brightness of the light float light_intensity; texture tex_diffuse; sampler2D smp_diffuse = sampler_state { Texture = ; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; }; texture tex_normal; sampler2D smp_normal = sampler_state { Texture = ; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; }; struct VertexShaderInput { float3 position : POSITION0; }; struct VertexShaderOutput { float4 position : POSITION0; float4 screen_position : TEXCOORD0; }; VertexShaderOutput MyVertexShader ( VertexShaderInput input ) { VertexShaderOutput output; //processing geometry coordinates float4 world_position = mul(float4(input.position,1), mat_world); float4 view_position = mul(world_position, mat_view); output.position = mul(view_position, mat_projection); output.screen_position = output.position; return output; } float4 MyPixelShader(VertexShaderOutput input) : COLOR0 { //obtain screen position input.screen_position.xy /= input.screen_position.w; //obtain textureCoordinates corresponding to the current pixel //the screen coordinates are in [-1,1]*[1,-1] //the texture coordinates need to be in [0,1]*[0,1] float2 tex_coord = 0.5f * (float2(input.screen_position.x,-input.screen_position.y) + 1); //get normal data from the normalMap float4 normal_data = tex2D(smp_normal,tex_coord); //tranform normal back into [-1,1] range float3 normal = 2.0f * normal_data.xyz - 1.0f; //read depth float depth_val = tex2D(smp_normal,tex_coord).a; //compute screen-space position float4 position; position.xy = input.screen_position.xy; position.z = depth_val; position.w = 1.0f; //transform to world space position = mul(position, mat_invviewproj); position /= position.w; //surface-to-light vector float3 light_vector = light_position - position; //compute attenuation based on distance - linear attenuation float attenuation = saturate(1.0f - length(light_vector)/light_radius); //normalize light vector light_vector = normalize(light_vector); //compute diffuse light float ndl = max(0,dot(normal,light_vector)); float3 diffuse_light = ndl * light_color.rgb; //take into account attenuation and lightIntensity. return attenuation * light_intensity * float4(diffuse_light.rgb,1.0f); } [/source] And this is how I render the light geometry: [source lang="cpp"] D3DXMATRIX scaling_matrix; D3DXMatrixScaling ( &scaling_matrix, 5.0f, 5.0f, 5.0f ); D3DXMATRIX rotation_matrix_x; D3DXMatrixRotationX ( &rotation_matrix_x, D3DXToRadian ( 0.0f ) ); D3DXMATRIX rotation_matrix_y; D3DXMatrixRotationY ( &rotation_matrix_y, D3DXToRadian ( 0.0f ) ); D3DXMATRIX rotation_matrix_z; D3DXMatrixRotationZ ( &rotation_matrix_z, D3DXToRadian ( 0.0f ) ); D3DXMATRIX translation_matrix; D3DXMatrixTranslation ( &translation_matrix, 0.0f, -15.0f, 0.0f ); D3DXMATRIX srt = scaling_matrix * ( rotation_matrix_x * rotation_matrix_y * rotation_matrix_z ) * translation_matrix; window->GetDevice ( )->SetTransform ( D3DTS_WORLD, &srt ); D3DXMATRIX w; window->GetDevice ( )->GetTransform ( D3DTS_WORLD, &w ); D3DXMATRIX v; window->GetDevice ( )->GetTransform ( D3DTS_VIEW, &v ); D3DXMATRIX p; window->GetDevice ( )->GetTransform ( D3DTS_PROJECTION, &p ); D3DXMATRIX ivp; D3DXMatrixMultiply ( &ivp, &v, &p ); D3DXMatrixInverse ( &ivp, NULL, &ivp ); shader_point_light->effect->SetMatrix ( "mat_world", &w ); shader_point_light->effect->SetMatrix ( "mat_view", &v ); shader_point_light->effect->SetMatrix ( "mat_projection", &p ); shader_point_light->effect->SetMatrix ( "mat_invviewproj", &ivp ); shader_point_light->effect->SetVector ( "light_color", &D3DXVECTOR4 ( 0.18f, 0.96f, 0.91f, 1.0f ) ); D3DXVECTOR4 lightpos ( 0.0f, -15.0f, 0.0f, 0.0f ); shader_point_light->effect->SetVector ( "light_position", &lightpos ); shader_point_light->effect->SetFloat ( "light_radius", 5.0f ); shader_point_light->effect->SetFloat ( "light_intensity", 1.0f ); shader_point_light->effect->SetTexture ( "tex_diffuse", g_buffer.texture[0] ); shader_point_light->effect->SetTexture ( "tex_normal", g_buffer.texture[1] ); shader_point_light->effect->CommitChanges ( ); window->GetDevice ( )->SetRenderTarget ( 0, back_buffer ); RenderGeometryDetails details; LPDIRECT3DVERTEXBUFFER9 vb; LPDIRECT3DINDEXBUFFER9 ib; sphere->GetVertexBuffer ( &vb ); sphere->GetIndexBuffer ( &ib ); details.primitive_type = D3DPT_TRIANGLELIST; details.num_vertices = sphere->GetNumVertices ( ); details.num_primitives = sphere->GetNumFaces ( ); details.vertex_format = sphere->GetFVF ( ); details.vertex_bytesize = sphere->GetNumBytesPerVertex ( ); details.vertex_buffer = vb; details.index_buffer = ib; details.texture = NULL; details.alpha_blend_mode = AlphaBlend_Blend; float camera_to_center = D3DXVec3Length ( &D3DXVECTOR3 ( lightpos.x - camera->position.x, lightpos.y - camera->position.y, lightpos.z - camera->position.z ) ); if ( camera_to_center < 5.0f ) details.cull_mode = Cull_Clockwise; else details.cull_mode = Cull_CounterClockwise; details.z_test = true; RenderGeometry ( details ); [/source] And this is the output I'm getting: defrend_point2.png You can clearly see that it's like I'm looking through a transparent sphere where everything behind it gets shaded, almost like additional directional lighting. This is my first deferred renderer, I'm still fairly new to this all and I can't quite keep up with it yet. I'm having trouble understanding what could be wrong here. Any help would be greatly appreciated? What looks iffy to you here? Am I missing an entire step?
d h k
d h k
I'll bump this one single time as it has been 36 hours, almost 100 views and the thread is about to fall off the front page. Am I asking too much here? Not posting enough code? Does everything look correct code- and shader-wise? I'm not sure why the silence...

If you need more information or code, just let me know.
iedoc
iedoc
I'll keep looking at this for a bit, but my first suggestion is to just use the world position passed in from the vertex shader instead of trying to compute the world matrix from the view matrix, since that just adds in extra complex computation that you don't need.

turn this:


struct VertexShaderOutput
{
float4 position : POSITION0;
float4 screen_position : TEXCOORD0;
};

to this:


struct VertexShaderOutput
{
float4 worldPos;
float4 position : POSITION0;
float4 screen_position : TEXCOORD0;
};

and this:

[font="Consolas,"]

VertexShaderOutput MyVertexShader ( VertexShaderInput input )

[/font][font="Consolas,"]

{

[/font][font="Consolas,"]

VertexShaderOutput output;

[/font][font="Consolas,"][/font][font="Consolas,"]

//processing geometry coordinates

[/font][font="Consolas,"]

float4 world_position = mul(float4(input.position,1), mat_world);

[/font][font="Consolas,"]

float4 view_position = mul(world_position, mat_view);

[/font][font="Consolas,"]

output.position = mul(view_position, mat_projection);

[/font][font="Consolas,"]

output.screen_position = output.position;

[/font][font="Consolas,"][/font][font="Consolas,"]

return output;

[/font][font="Consolas,"]

}

[/font]

to this:

[font="Consolas,"]

VertexShaderOutput MyVertexShader ( VertexShaderInput input )

[/font][font="Consolas,"]

{

[/font][font="Consolas,"]

VertexShaderOutput output;

[/font][font="Consolas,"][/font][font="Consolas,"]

//processing geometry coordinates

[/font][font="Consolas,"]

float4 world_position = mul(float4(input.position,1), mat_world);

[/font][font="Consolas,"]

float4 view_position = mul(world_position, mat_view);

[/font][font="Consolas,"]

output.[color="#1C2837"]worldPos = world_position;

[/font][font="Consolas,"]

output.position = mul(view_position, mat_projection);

[/font][font="Consolas,"]

output.screen_position = output.position;

[/font][font="Consolas,"][/font][font="Consolas,"]

return output;

[/font][font="Consolas,"]

}

[/font]
turn this:
[font="Consolas,"]

//transform to world space

[/font][font="Consolas,"]

position = mul(position, mat_invviewproj);

[/font][font="Consolas,"]

position /= position.w;

[/font]

to this:

[font="Consolas,"]

//transform to world space

[/font][font="Consolas,"]

position = input.[color="#1C2837"]worldPos;

[/font][font="Consolas,"]


[/font][font="Consolas,"]

Hope that helps



Oh yeah, and one other thing, to tell you the truth, I can't quite see whats wrong with your picture, it looks fine to me except maybe the walls and ceiling turn blue far off, is that what the problem is?

[/font]
d h k
d h k
First of all, thanks a TON for writing! Really helps a lot.

I committed those changes but it didn't change anything (thanks for the simplification though). I guess I can also delete these lines?


position.xy = input.screen_position.xy;
position.z = depth_val;
position.w = 1.0f;


And I had to bind your float4 worldPos to TEXCOORD1 in order to get the shader to compile, I guess that's okay?

I have investigated further and I'm pretty sure the problem lies with the attenuation! That's the term that should make sure the stuff in the back, as can be seen in the shot in my first post, should NEVER be lit (as that stuff is too far away from the light and its radius) and it should also make sure the floor itself, which never gets lit currently, SHOULD be lit, right?

To answer your question, that's what is not working and making me think there's something wrong, the light sphere sits right on the floor plane of the environment, but it lights the background like a see-through sphere instead of leaving the background and point-lighting that floor.

Inverting the depth (which is stored in the alpha channel of my view-space normal render target in the G-Buffer just fine, I can check it in NVidia PerfHUD) like this doesn't change anything surprisingly:


float depth_val = tex2D(smp_normal,tex_coord).a;
depth_val = -depth_val + 1;


My G-Buffer fill shader stores the depth like this:


// in the vertex shader
// get the position of the vertex in view-space, retrieve depth and pass it on
float3 position_view = mul ( input.position, mat_worldview );
output.depth = position_view.z;

// in the pixel shader
// linear depth
output.gbuffertex2.a = input.depth / render_distance;


Since my render distance is large and that environment pretty small, the depth values stored in the G-Buffer are, according to PerfHUD, 0.00 and sometimes 0.01 - is that maybe a problem? I can lower the render distance and get much more variety between these numbers.
TomKQT
TomKQT

This might help.


This thread is about deferred rendering, while the link talks about classic dynamic lighting using 4 point lights.
Also - the thread is DX9, link DX10.
d h k
d h k
Yes, that link wasn't very helpful I'm afraid... Thanks for trying though!

Any other ideas, anyone?
mancubit
mancubit
have a look at catalins xna blog here

i also made my baby-steps in deferred rendering based on his tutorial and it's really well written and rather easy to understand
Burnt_Fyr
Burnt_Fyr

have a look at catalins xna blog here

i also made my baby-steps in deferred rendering based on his tutorial and it's really well written and rather easy to understand


This is even less helpful than the previous response. You have linked the same blog the OP was following.

@ op; I know how frustrating it is seeing the view count go up and the reply count stand dormant. I've looked at your post but as I'm still forward rendering I won't be as helpful as those with time in the trenches.

First, have you tried shrinking the size of your view frustum, there by increasing resolution in the Depth buffer? you had mentioned it in a previous post, and it struck me that this could be a depth issue( especially with the grainy vertical blue lines on the right of the second picture, although upon closer inspection it appears to be the pleats of the curtains). If you have and it wasn't the issue than I would try some simpler geometry that we can solve by pen and paper if need be. Maybe a sphere light with a simple box around it?
d h k
d h k
I limited the render distance to 150.0f. This means I get more 'reasonable' depth values in my scenes. From ~0.0 (real close) to ~0.35 in the following shots. Didn't make a difference, however.

I have simplified the geometry and this should indeed help us pinpoint the problem. Take a look at these for images from different angles:

defrend_point3.png
defrend_point4.png
defrend_point5.png
defrend_point6.png

I have taken out the attenuation term entirely for the moment, if I don't I see nothing of the point light. Looks like the attenuation ends up very close to 0.0 at the moment. But that's something for the next step, currently the angle of camera makes a difference when it comes to lighting. That should not be the case! A point light should light surrounding faces no matter where the camera is facing. This makes me think there is a problem with the spaces in my shader code maybe? Here's the point light shader again:

[source lang="cpp"]
float4x4 mat_world;
float4x4 mat_view;
float4x4 mat_projection;

//this is used to compute the world-position
float4x4 mat_invviewproj;

//color of the light
float3 light_color;

//this is the position of the light
float3 light_position;

//how far does this light reach
float light_radius;

//control the brightness of the light
float light_intensity;


struct VertexShaderInput
{
float3 position : POSITION0;
};

struct VertexShaderOutput
{
float4 position : POSITION0;
float4 screen_position : TEXCOORD0;
float4 worldPos : TEXCOORD1;
};

VertexShaderOutput MyVertexShader ( VertexShaderInput input )
{
VertexShaderOutput output;

//processing geometry coordinates
float4 world_position = mul(float4(input.position,1), mat_world);
float4 view_position = mul(world_position, mat_view);
output.worldPos = world_position; output.position = mul(view_position, mat_projection);
output.screen_position = output.position;
return output;
}

float4 MyPixelShader(VertexShaderOutput input) : COLOR0
{
//obtain screen position
input.screen_position.xy /= input.screen_position.w;

//obtain textureCoordinates corresponding to the current pixel
//the screen coordinates are in [-1,1]*[1,-1]
//the texture coordinates need to be in [0,1]*[0,1]
float2 tex_coord = 0.5f * (float2(input.screen_position.x,-input.screen_position.y) + 1);

//get normal data from the normalMap
float4 normal_data = tex2D(smp_normal,tex_coord);

//tranform normal back into [-1,1] range
float3 normal = 2.0f * normal_data.xyz - 1.0f;

//read depth
float depth_val = tex2D(smp_normal,tex_coord).a;

//compute screen-space position
float4 position;

//transform to world space
position = input.worldPos;

//surface-to-light vector
float3 light_vector = light_position - position;

//normalize light vector
light_vector = normalize(light_vector);

//compute diffuse light
float ndl = max(0,dot(normal,light_vector));
float3 diffuse_light = ndl * light_color.rgb;

//take into account attenuation and lightIntensity.
return light_intensity * float4(diffuse_light.rgb,1.0f);
}
[/source]

The light vector is calculated in world space as light_position as well as position is in world space, right? Wouldn't the ndl (normal dotp light_vector ) cause a problem as the normal data is in view space (read from the G-Buffer)?

I'm just not experienced enough with this kind of stuff. Any insight anyone?
Matias Goldberg
Matias Goldberg
Hi, I haven't read your code, but I will answer your basic question; which is more important for you to understand your problem.

My first issue is that I don't fully understand the idea of rendering a sphere for a point light, take a look at this shot right from my program



Point lights have attenuation, at some point attenuation is so strong, that the light contribution is nearly zero.

Because of this, a sphere big enough that encloses until the last pixel that is lit by the light becomes a performance optimization.

Suppose you have a 1440x900 image, that's 1.296.000 pixels to parse. If the point light is very small (high attenuation), the sphere will also be very small; and if it's seen from far away; it will probably only lit a few thousand pixels. So the pixel shader is run on those pixels, rather on more than a million just to lit a few thousands.




NOW, if your attenuation parameters aren't carefully chosen, your point light will probably contribute light to the entire scene. Because of this, you'll need a very big sphere; which will probably occupy the entire screen. When this happens, then there's no actual performance gain. In terms of performance, Many little lights = One big light

Apparently in your screenshot, your attenuation is too weak (or the formula is wrong), and the sphere is too small for those parameters.

Furthermore very big spheres introduce another problem, which is what happens when the camera is inside the sphere. To account for that, you'll need to identify the situation from your C++ code and reverse the culling mode and set the Z function to Greater or equal for that sphere(s). But let's just solve one problem at a time.




Also note when the opposite happens, that is you have very strong attenuation and a big sphere; you may not notice it but you'll be wasting GPU cycles.

Cheers

Dark Sylinc
d h k
d h k
@Matias Goldberg: Thanks for clearing that up. Makes sense now. That's actually the way I thought it would have to work but I wasn't sure. The sphere simply limits the amount of pixels the point light shader is run on and to avoid getting incorrect results (since a sphere isn't a circle) you use attenuation. That way you are only affecting the intersecting part of the sphere and the geometry it lights. I already have code in place to switch the culling mode when the camera is inside the point light. Thinking about this, wouldn't I have to render the sphere without z-testing? Always render it out in its entirety? Otherwise, when the camera is in the sphere, the wall in the pictures in my last post would hide the sphere in the parts we're interested in, right?

@phil_t: Yeah I should definitely do a debug run using PIX, thanks for the tip. I will report back after having done so.


I have a couple of questions regarding the spaces that my calculations are in as I suspect my problems lie in that general area:

In the pixel shader, when I calculate the light_vector:


//surface-to-light vector
float3 light_vector = light_position - position;

[font="Arial"]
light_position is passed to from the game to the shader in world space of course, position is also supposed to be in world space as it gets passed from the vertex shader like so:[/font]


float4 position = mul(float4(input.position,1), mat_world);


So that means the resulting light_vector is also in world space if I'm not mistaken.

[font="Arial"]light_vector is then used to calculate the attenuation:[/font]


float attenuation = saturate(1.0f - length(light_vector)/light_radius);


[font="Arial"]I assume this formula works with a vector being in world space?

Then, light_vector is normalized and used in the following fashion to calculate the diffuse term:[/font]


float ndl = max(0,dot(normal,light_vector));


[font="Arial"]And this is where I spot a problem. normal is the normal data for the texel looked up from the G-Buffer. The G-Buffer stores view-space normals, as is the standard, but light_vector is still in world space! Don't you need to use the same space for both parameters when doing a dot product? I tried to bring the light_vector into view space as well right before calculating the dot product like this:[/font]


light_vector = mul ( light_vector, mat_view );


[font="Arial"]Without using attenuation I still get the same result as in the shots in my last post. You can see that the light vector is definitely NOT calculated correctly there as there is no change in brightness closer to the center of the light (which should be the case even without attenuation because of the dot product between normal and light vector). When I add in attenuation I get full attenuation (a value of 0.0f) throughout and can't see the point light at all.

I found out that, when I leave the position, right before calculating the light_vector, in screen space and without attenuation I actually get a somewhat correct looking point light that is brighter close to the center of the light. Of course it is cut off at the edges of the sphere because I leave out attenuation and it also appears to shift the center of the light along with where I point the camera. That's definitely the case because leaving position in screen space is not the right way to go but it does look more correct than position in world space.

So my light_vector is in world space. Before calculating dot(normal, light_vector) I try to bring the light vector into view space like the normals are by multiplying it with the view matrix. Unfortunately that doesn't seem to work at all as the result gives me the same spread out light color you can see in the images I put in my last post in this thread. What am I doing wrong here? Any further help is greatly appreciated!
[/font]
Matias Goldberg
Matias Goldberg
Thinking about this, wouldn't I have to render the sphere without z-testing? Always render it out in its entirety? Otherwise, when the camera is in the sphere, the wall in the pictures in my last post would hide the sphere in the parts we're interested in, right?

Hi! If you render without Z-testing; then when you'll have for example thousands of lights behind a mountain; you'll get a performance hit even though none of those lights can be seen because you there is a huge mountain in the middle.



//surface-to-light vector
float3 light_vector = light_position - position;

[font="Arial"]
light_position is passed to from the game to the shader in world space of course, position is also supposed to be in world space as it gets passed from the vertex shader like so:[/font]


float4 position = mul(float4(input.position,1), mat_world);


So that means the resulting light_vector is also in world space if I'm not mistaken.

Yes, assuming mat_world contains a correct world transform.

[font="Arial"]light_vector is then used to calculate the attenuation:[/font]


[font="Arial"]I assume this formula works with a vector being in world space?[/font]

Yes

[font="Arial"]Then, light_vector is normalized and used in the following fashion to calculate the diffuse term:[/font]


float ndl = max(0,dot(normal,light_vector));


[font="Arial"]And this is where I spot a problem. normal is the normal data for the texel looked up from the G-Buffer. The G-Buffer stores view-space normals, as is the standard, but light_vector is still in world space! Don't you need to use the same space for both parameters when doing a dot product? I tried to bring the light_vector into view space as well right before calculating the dot product like this:
[/font]

Yup, you can't mix vectors in different spaces. As a quick fix:
normal = normalize( mul( normal, (float3x3)(inv_mat_view) ) );
That should convert your normal from view space back into world space. If it works after that, you'll have your problem spotted.

BTW, it's a quick fix because it would be a lot faster to ensure everything is in the same space. Alsothere are faster ways to get the position from a G-Buffer, but let's just start solving the first problem.

Cheers
Dark Sylinc
d h k
d h k
Okay that makes sense so far. Take a look at this new point light shader:

[source lang="cpp"]
struct VertexShaderOutput
{
float4 position : POSITION0;
float4 screen_position : TEXCOORD0;
float4 world_position : TEXCOORD1;
};

VertexShaderOutput MyVertexShader ( VertexShaderInput input )
{
VertexShaderOutput output;

output.world_position = mul(float4(input.position,1), mat_world);
float4 view_position = mul(output.world_position, mat_view);
output.position = mul(view_position, mat_projection);
output.screen_position = output.position;
return output;
}

float4 MyPixelShader(VertexShaderOutput input) : COLOR0
{
//obtain screen position
input.screen_position.xy /= input.screen_position.w;

//obtain textureCoordinates corresponding to the current pixel
//the screen coordinates are in [-1,1]*[1,-1]
//the texture coordinates need to be in [0,1]*[0,1]
float2 tex_coord = 0.5f * (float2(input.screen_position.x,-input.screen_position.y) + 1);

//get normal data from the normalMap
float4 normal_data = tex2D(smp_normal,tex_coord);

//tranform normal back into [-1,1] range
float3 normal = 2.0f * normal_data.xyz - 1.0f;

//surface-to-light vector (in world space!)
float3 light_vector = light_position - input.world_position.xyz;

//compute attenuation based on distance (in world space!)
float attenuation = saturate(1.0f - length(light_vector)/light_radius);

//normalize light vector
light_vector = normalize(light_vector);

// bring normals back world space
normal = normalize( mul( normal, (float3x3)(mat_invview) ) );

//compute diffuse light (in world space!)
float ndl = max(0,dot(normal,light_vector));
float3 diffuse_light = ndl * light_color.rgb;

//take into account attenuation and lightIntensity.
return attenuation * float4(diffuse_light,1.0f);
}
[/source]

Should be really straightforward, unfortunately, like that, I don't see a thing. And it is the attenuation term. I can't understand what could possibly wrong in those relatively easy calculations but something is very wrong. If I comment out the attenuation in the last line of the shader, I do get some visual output. What could be the problem with the attenuation calculation? I'm totally out of ideas!

Thanks a ton for your help so far, Matias. Would be awesome if we could sort this out!

EDIT: Another question: When I send all the matrices (that is, the world-, the view-, the projection- and the inverted view matrix) to the point light shader, what is the correct order? I have the part that positions the sphere in the world:


D3DXMATRIX scaling_matrix;
D3DXMatrixScaling ( &scaling_matrix, 10.0f, 10.0f, 10.0f );

D3DXMATRIX rotation_matrix_x;
D3DXMatrixRotationX ( &rotation_matrix_x, D3DXToRadian ( 0.0f ) );
D3DXMATRIX rotation_matrix_y;
D3DXMatrixRotationY ( &rotation_matrix_y, D3DXToRadian ( 0.0f ) );
D3DXMATRIX rotation_matrix_z;
D3DXMatrixRotationZ ( &rotation_matrix_z, D3DXToRadian ( 0.0f ) );

D3DXMATRIX translation_matrix;
D3DXMatrixTranslation ( &translation_matrix, 0.0f, 0.0f, 50.0f );

D3DXMATRIX srt = scaling_matrix * ( rotation_matrix_x * rotation_matrix_y * rotation_matrix_z ) * translation_matrix;
window->GetDevice ( )->SetTransform ( D3DTS_WORLD, &srt );


And the part that gets the matrices and sends them to the shader:


D3DXMATRIX w;
window->GetDevice ( )->GetTransform ( D3DTS_WORLD, &w );
D3DXMATRIX v;
window->GetDevice ( )->GetTransform ( D3DTS_VIEW, &v );
D3DXMATRIX p;
window->GetDevice ( )->GetTransform ( D3DTS_PROJECTION, &p );

D3DXMATRIX iv;
D3DXMatrixInverse ( &iv, NULL, &v );

shader_point_light->effect->SetMatrix ( "mat_world", &w );
shader_point_light->effect->SetMatrix ( "mat_view", &v );
shader_point_light->effect->SetMatrix ( "mat_projection", &p );
shader_point_light->effect->SetMatrix ( "mat_invview", &iv );


I've always had it in the way I posted them, first setting the world matrix up for rendering the sphere and then getting the matrices for the shader. But is that correct? If I reverse the parts I actually get what looks like proper attenuation. Of course, the sphere then doesn't actually move with the rest of the box when I move the camera... Throughly confused.
JustChris
JustChris
Shouldn't input.ScreenPosition in the pixel shader be modified so that it is transformed into world space? ScreenPosition is still actually a world position, because it's a direct copy of the output position of the vertex shader. Add the following after dividing Screen_position.xy by its w component:


float4 position;
position.xy = input.screen_position.xy;

// Sample floating point value from GBuffer depth
position.z = tex2D(smp_depth, tex_coord).r
position.w = 1.0f;

// Transform back into world space
// use the inverse of the camera's view matrix * projection matrix
position = mul(position, inverseViewProjection);
position /= position.w;


Now here's where the rest of your code differs. Replace input.world_position.xyz with the position variable we just created:

float3 light_vector = light_position - position;

And everything else should come into place now. For the inverse view projection matrix, you could calculate this in the CPU as well.

[edit]
I just noticed that what I typed is almost the same code that you had in the beginning, and you are sampling the depth. Right now you're not seeing the attenuation term because there is no actual depth to compare it against. The only big difference I see is that you're packing the depth into an 8-bit value in the alpha channel of the GBuffer's normal map (I'm assuming you're using a 32-bit color render target). I'm not sure if you'd want to do this. Have you considered drawing the depth to a separate R32F texture? You'd get a lot more precision by doing it this way.
Electronic Meteor - My experiences with XNA and game development
d h k
d h k
First of all, big thinks for going through my code!

I'm not sure I understand, however. input.screen_position is sent from vertex to pixel shader in projection space (which, to the best of my knowledge, is equivalent to screen space), it's the position sent to the vertex shader multiplied by the world, the view and then the projection matrix.

And input.world_position is sent from vertex to pixel shader in world space, that is a direct copy of the incoming position in the vertex shader multiplied by the world matrix.

Shouldn't that sent me a proper position of the pixel in screen and world space? Why do we need that additional step you're describing where we take the screen_position, sample in the depth and then convert that to world space in the pixel shader?

Here's what I have right now:

[source lang="cpp"]
struct VertexShaderOutput
{
float4 position : POSITION0;
float4 screen_position : TEXCOORD0;
float4 world_position : TEXCOORD1;
};

VertexShaderOutput MyVertexShader ( VertexShaderInput input )
{
VertexShaderOutput output;

output.world_position = mul(float4(input.position,1), mat_world);
float4 view_position = mul(output.world_position, mat_view);
output.position = mul(view_position, mat_projection);
output.screen_position = output.position;
return output;
}

float4 MyPixelShader(VertexShaderOutput input) : COLOR0
{
//obtain screen position
input.screen_position.xy /= input.screen_position.w;

//obtain textureCoordinates corresponding to the current pixel
//the screen coordinates are in [-1,1]*[1,-1]
//the texture coordinates need to be in [0,1]*[0,1]
float2 tex_coord = 0.5f * (float2(input.screen_position.x,-input.screen_position.y) + 1);

float4 position;
position.xy = input.screen_position.xy;

// Sample floating point value from GBuffer depth
position.z = tex2D(smp_normal, tex_coord).a;
position.w = 1.0f;

// Transform back into world space
// use the inverse of the camera's view matrix * projection matrix
position = mul(position, mat_invviewproj);
position /= position.w;

//get normal data from the normalMap
float4 normal_data = tex2D(smp_normal,tex_coord);

//tranform normal back into [-1,1] range
float3 normal = 2.0f * normal_data.xyz - 1.0f;

//surface-to-light vector (in world space!)
float3 light_vector = light_position - position;//input.world_position.xyz;

//compute attenuation based on distance (in world space!)
float attenuation = saturate(1.0f - length(light_vector)/light_radius);

//normalize light vector
light_vector = normalize(light_vector);

// bring normals back world space
normal = normalize( mul( normal, (float3x3)(mat_invview) ) );

//compute diffuse light (in world space!)
float ndl = max(0,dot(normal,light_vector));
float3 diffuse_light = ndl * light_color.rgb;

//take into account attenuation and lightIntensity.
return attenuation * float4(diffuse_light,1.0f);
}
[/source]

This still gives me problems. When the camera is at 0/0/0, I get a light sphere that doesn't have proper attenuation still and when I move the camera away in any direction from the origin, that sphere fades out the further I move. Still looks like a space problem to me? Any further help is greatly, greatly appreciated!!

Oh and my G-Buffer consists of two D3DFMT_A16B16G16R16F textures, the first uses RGB to store the diffuse (albedo) colors and A for specular intensity, the second uses RGB to store view-space normals and A for (linear) distance. I posted the code bit that writes depth into the G-Buffer in a post above. So that's 16-bit depth, I'm fairly certain that should be enough precision?
d h k
d h k
I'm using PIX 9.26 for single-frame capturing and I can't step seem to step through the shader. All I get is the calls the main game loop makes to D3D functions. I'm no PIX expert, how would I go about doing that?
phil_t
phil_t

I'm using PIX 9.26 for single-frame capturing and I can't step seem to step through the shader. All I get is the calls the main game loop makes to D3D functions. I'm no PIX expert, how would I go about doing that?


There are two ways to get to the shader debugger:
1) VS only: select a draw call on the left, then click on the mesh tab on the right. In the PostVS tab you can click on a vertex to debug it.
2) VS and PS: right click on a pixel in the image in the Render tab, and choose "Debug pixel". That brings you to a list of all the pixel shaders that contributed to the value of the pixel at that point. Find the one that you want (the event ids may help here) and choose "Debug pixel" (or "Debug Vertex 0/1/2" if you want the vertex shader).

I'm working on a series of video tutorials for PIX. I've only put one up so far (here it is), and it's a bit of a broad overview, so it might not be detailed enough for you.






Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.