Skip to main content
gamedev.net
🔒 Locked

[HLSL] Diffuse lighting oddities...

Started by Yeshua666 8 replies 3.4k views
Original Post
Yeshua666
Yeshua666
Hello, I've spent the last couple of days trying to get into HLSL and to eventually use fancy shaders in my future games. Currently, I'm following Frank D. Luna's excellent book entitled, "Introduction to 3D Game Programming w/ DirectX 9: A Shader Approach". So far, I'm at chapter 10--this chapter discusses various lighting techniques including: ambient, diffuse, specular, point, spotlight, phong shading, etc. I've ran into an unusual problem with my 3D application, unfortunately. The solution is probably simple and perhaps I'm overlooking something small, or perhaps my math isn't what it's supposed to be. In my 3D application I have four rows of spheres laid out in a grid. This grid of spheres are all located int the positive xz plane. In my shader I set up a directional diffuse light that points along the positive z-axis (0, 0, 1}. Additionally, my movable camera is set up in the position {0, 0, -10} with the view target at {0, 0, 0} (looking towards the grid of spheres on the xz plane). With the aforementioned details, the expected result is for all of the spheres in the grid to be lit on the {0, 0, -1} side. However, the result is quite opposite. The side of the spheres that aren't supposed to be lit are lit, and vice-versa for the opposite side. Here is my diffuse lighting shader in its entirety:

struct InputVS
{
	
	float4 position : POSITION ;
	float4 normal   : NORMAL   ;	
} ;

struct OutputVS
{
	
	float4 position : POSITION ;
	float4 diffuse  : COLOR    ;
} ;

uniform extern float4x4 mtrxWVP      : WorldViewProjection   ;
uniform extern float4x4 mtrxWorld_IT : WorldInverseTranspose ;

// General light attributes
const float4 vLightDir      = { 0.0f, 0.0f, 1.0f, 0.0f } ;

// Ambient lighting attributes
uniform extern float4 ligAmbient   ;
uniform extern float4 mtrlAmbient  ;

// Diffuse lighting attributes
uniform extern float4 ligDiffuse   ;
uniform extern float4 mtrlDiffuse  ;

OutputVS MainVS( InputVS input )
{

	OutputVS output = (OutputVS)0 ;
	float3 ambient, diffuse ;
	float diffuse_co = 0.0f ;

	input.position.w = 1.0f ;
	input.normal.w   = 0.0f ;

	output.position = mul( input.position, mtrxWVP ) ;

	input.normal = mul( input.normal, mtrxWorld_IT ) ;
	input.normal = normalize( input.normal ) ;

	diffuse_co = max( dot( vLightDir, input.normal ), 0 ) ;

	ambient  = (ligAmbient * mtrlAmbient).rgb              ;
	diffuse  = diffuse_co * (ligDiffuse * mtrlDiffuse).rgb ;

	output.diffuse.rgb = ambient + diffuse ;
	output.diffuse.a   = 1.0f ;

	return output ;
}

float4 MainPS( float4 diffuse : COLOR0 ) : COLOR 
{
	
	return diffuse ;
}

technique MainFX
{
	
	pass p0 
	{

	        AlphaBlendEnable = TRUE        ;
                Ambient          = float4( 1.0f, 1.0f, 1.0f, 1.0f ) ;
                CullMode         = CCW         ; 
		DestBlend        = INVSRCALPHA ;
		FillMode         = SOLID       ;
		Lighting         = FALSE       ;
		NormalizeNormals = FALSE        ;
		ShadeMode        = GOURAUD     ;
		SpecularEnable   = FALSE       ;
		SrcBlend         = SRCALPHA    ;
		StencilEnable    = FALSE       ;
		ZEnable          = TRUE        ;
		ZFunc            = LESSEQUAL   ;
    
		VertexShader = compile vs_2_0 MainVS() ;
		PixelShader  = compile ps_2_0 MainPS() ;
	}
}



Here is the c++ code snippet detailing how the spheres are rendered:

void CDemo::DrawSpheres( float fCount, float fSpacing )
{

	float fRoot = sqrt( fCount ) ;

	if( !isWholeNumber( fRoot ) )
		return ;

	D3DXVECTOR4 mtrlAmbient( 0.5f, 0.0f, 0.0f, 1.0f ) ;
	_pEffect->SetVector( _hmtrlAmbient, &mtrlAmbient ) ;

	D3DXVECTOR4 mtrlDiffuse( 0.9f, 0.0f, 0.0f, 1.0f ) ;	
	_pEffect->SetVector( _hmtrlDiffuse, &mtrlDiffuse ) ;

	D3DXMATRIX mtrxWorldViewProj, mtrxWorld_IT ;
	D3DXMATRIX mtrxWorld, mtrxView, mtrxProj ;

	_pGDevice->GetTransform( D3DTS_VIEW, &mtrxView ) ;
	_pGDevice->GetTransform( D3DTS_PROJECTION, &mtrxProj ) ;

	for( unsigned int j = 0 ; j < (unsigned int)fRoot ; j++ )
	{

		for( unsigned int k = 0 ; k < (unsigned int)fRoot ; k++ )
		{

	                D3DXMatrixTranslation( &mtrxWorld, k * (5.0f + fSpacing), 2.5f, j * (5.0f + fSpacing) ) ;
			
			mtrxWorldViewProj = mtrxWorld * mtrxView * mtrxProj ;
			_pEffect->SetMatrix( _hmtrxWVP, &mtrxWorldViewProj ) ;

			D3DXMatrixInverse( &mtrxWorld_IT, 0, &mtrxWorld ) ;
			D3DXMatrixTranspose( &mtrxWorld_IT, &mtrxWorld_IT ) ;
			_pEffect->SetMatrix( _hmtrxWorld_IT, &mtrxWorld_IT ) ;
			
			_pEffect->CommitChanges() ;			
			_mshBall->DrawSubset( 0 ) ;
		}
	}
}

//************************************************************



Now for the screenshots of my 3D application. The first screen shows the grid of spheres rendered when the shader variable, 'vLightDir', is set to {0, 0, 1}. Please note that the front of spheres are supposed to be lit, not the back (which is my problem): This 2nd screenshot shows the application at work while the shader variable, 'vLightDir', is set to {0, 0, -1}. Note how the spheres are lit, very unusual. Also note how the first sphere in the grid (lower left corner) is perfectly lit; very bizarre. [Edited by - Yeshua666 on March 18, 2009 7:50:06 PM]
MJP
MJP
This is very simple. When you calculate the diffuse lighting term by doing the dot-product (N (dot) L), L is supposed to be a vector pointing from the the vertex to the light source. In your shader you're using the vector that points from the light source to the vertex, which is backwards. Therefore you're getting opposite results.

Typically with a directional light you'll just negate the direction either in the shader or when setting the shader constant.
Yeshua666
Yeshua666
Quote:
Original post by MJP
This is very simple. When you calculate the diffuse lighting term by doing the dot-product (N (dot) L), L is supposed to be a vector pointing from the the vertex to the light source. In your shader you're using the vector that points from the light source to the vertex, which is backwards. Therefore you're getting opposite results.

Typically with a directional light you'll just negate the direction either in the shader or when setting the shader constant.


Hello, thank you for your response. I see this as an unusual solution considering all of the articles, tutorials and books I've skimmed through never negate the light direction anywhere when dealing with directional diffuse lighting. But I'll still take your suggestion under consideration.

But how do you explain the 2nd screenshot? It's very unusual that only one sphere is lit, and it's even more unusual how the other spheres in the grid are lit (it could be that the light source is at the origin so the sphere closest to it receives the most light. But observe how the other spheres in the grid have a bright vertical streak running across them).
livengood
livengood
DirectX is a left handed coordinate system. The Z+ goes into the screen. I didn't look through your entire post but maybe you are thinking in GL?
Yeshua666
Yeshua666
Quote:
Original post by livengood
DirectX is a left handed coordinate system. The Z+ goes into the screen. I didn't look through your entire post but maybe you are thinking in GL?


Nope.
Namethatnobodyelsetook
Namethatnobodyelsetook
Change your normal input from float4 to float3. Transforming a float3 by a matrix won't include translation. Right now your normals will be quite screwy.

And yes, you need to negate either the light direction or the normal. A dot product of a normal facing your camera (0,0,-1), and your light facing the spheres (0,0,1) will be -1. (x1*x2+y1*y2+z1*z2) = 0+0-1 = -1.
Yeshua666
Yeshua666
Quote:
Original post by Namethatnobodyelsetook
Change your normal input from float4 to float3. Transforming a float3 by a matrix won't include translation. Right now your normals will be quite screwy.

And yes, you need to negate either the light direction or the normal. A dot product of a normal facing your camera (0,0,-1), and your light facing the spheres (0,0,1) will be -1. (x1*x2+y1*y2+z1*z2) = 0+0-1 = -1.


Thank you for your post. Both you and MJP were very helpful as I've fixed the problem per your suggesting. I've also added in specular lighting. Now I can move onto the next chapter!

I've give both of you favorable user ratings as a result for your help.
Yeshua666
Yeshua666
I have some additional questions however as I proceed further into this book. And I'd prefer a non-scientific response to these questions because I've googled like crazy and can't wrap my head around some of these terminologies.

Phong Shading
From what I've read Phong Shading is a vertex shading technique which involves interpolating vertex normals during rasterization. When you send the vertex data from the vertex shader to the pixel shader, sometimes, an interpolated normal can become un-normalized.

1) In animating with Flash CS3 I've learned that interpolating means to approximate data between two key frames. Is the process of interpolating vertex normals computationally expensive?

2) Sometimes non-interpolated normals aren't normalized. Normalizing interpolated normals in the pixel shader doesn't seem like a good idea since the square-root (a slow function from what I can gather) function is required when normalizing vectors. Is there a better way in ensuring that all interpolated normals are already normalized after the vertex shader is done with the data?

3) In using phong shading my geometry appears smoother. From that I conclude it to be a superior shading method to my old ambient-diffuse-specular lighting shader. Is the phong shading technique an industry standard/norm? Or is there a superior technique I'll learn about later on?

Transformations

1) What exactly is homogeneous space? From what I can tell all of the points in a homogeneous space have a 4th component, w, which is equal to 1 on all points (w = 1). What is the significance of this fourth component? Why is it needed? And how does a homogeneous space differ from a normal 'ole 3D space (xyz).

2) Visually, what is the distinction between transforming a point to world/model space and transforming it to World-View-Projection?

Some Shader Code

1) And finally (sorry for the questions, this is my last). This might be a dumb question but I haven't really learned much about pixel shaders up until this point. In the following code segment:

struct OutputVS{      float4 posH    : POSITION0;      float3 normalW : TEXCOORD0;      float3 posW    : TEXCOORD1;};OutputVS PhongVS(float3 posL : POSITION0, float3 normalL : NORMAL0){      // Zero out our output.      OutputVS outVS = (OutputVS)0;      // Transform normal to world space.      outVS.normalW = mul(float4(normalL, 0.0f),                          gWorldInverseTranspose).xyz;      outVS.normalW = normalize(outVS.normalW);      // Transform vertex position to world space.      outVS.posW  = mul(float4(posL, 1.0f), gWorld).xyz;      // Transform to homogeneous clip space.      outVS.posH = mul(float4(posL, 1.0f), gWVP);      // Done--return the output.      return outVS;}float4 PhongPS(float3 normalW : TEXCOORD0,               float3 posW : TEXCOORD1) : COLOR{      // Compute the color: Equation 10.3.      // Interpolated normals can become unnormal--so normalize.      normalW = normalize(normalW);      // Compute the vector from the vertex to the eye position.      float3 toEye = normalize(gEyePosW - posW);      // Compute the reflection vector.      float3 r = reflect(-gLightVecW, normalW);      // Determine how much (if any) specular light makes it      // into the eye.      float t  = pow(max(dot(r, toEye), 0.0f), gSpecularPower);      // Determine the diffuse light intensity that strikes the vertex.      float s = max(dot(gLightVecW, normalW), 0.0f);      // Compute the ambient, diffuse, and specular terms separately.      float3 spec = t*(gSpecularMtrl*gSpecularLight).rgb;      float3 diffuse = s*(gDiffuseMtrl*gDiffuseLight).rgb;      float3 ambient = gAmbientMtrl*gAmbientLight;      // Sum all the terms together and copy over the diffuse alpha.      return float4(ambient + diffuse + spec, gDiffuseMtrl.a);}


In the struct OutputVS, why are 'normalW' and 'posW' labeled with TEXCOORD0 and TEXCOORD1 respectively? From what I can gather, these are called registers and they tell the GPU how the variables are to be used. But I don't understand why these two variables are passed onto the pixel shader as texture coordinates. Texture coordinates usually have {u, v} points but these variables are defined with float3 (x, y, z).
Namethatnobodyelsetook
Namethatnobodyelsetook
The term Phong has two uses in lighting. The Phong lighting model refers to how we normally compute diffuse and specular contributions of light. This is what practically everyone uses. There are numerous other lighting models, all of which require more computation.

Phong Shading is applying the Phong lighting model at a pixel level.

Gouraud Shading is applying the Phong lighting model at a vertex level and interpolating the colors.

1) The GPU automatically interpolates all vertex shader outputs before passing them as inputs to the pixel shader. There is a minimal cost to using more interpolators, so don't pass data you're not using between the shaders.

2) You can normalize in the vertex shader to have normalized outputs, but you'll still need to normalize in the pixel shader (See example below). If all your normals are going to be scaled similarly, you can skip the vertex shader normalization. If the scale is going to vary from vertex to vertex, it's best to normalize in both locations. The GPU uses an approximation of a square root, and it takes only a few cycles.

If we interpolate between two normalized vectors:
(0.707, 0.707,0 ) and (-0.707, 0.707, 0)
when we're halfway across this face, the interpolated values will be:
(0, 0.707, 0)
Which clearly isn't normalized anymore. Therefore we must normalize in the pixel shader.

3) It's the standard for basic lighting. Some games are doing slight additions to it with a Screen Space Amibent Occlusion post-process. Some games may be using Spherical Harmonics (SH), or PreComputed Radiance Transfer (PRT), but they're rare. Some games, like Killzone 2, take another approach to lighting, called Deferred Shading. In this system you render to multiple textures at once, writing not lit colors, but positions, normals, specular power, and the diffuse texture. Later in post-processing passes you uses these texture as inputs to compute lighting. Phong is standard for pretty much all but the AAA titles, and they all use their own approaches, often mixed with Phong.

I'll leave the rest for another time (or another person)
SeraphLance
SeraphLance
Phong Shading

Quote:
1) In animating with Flash CS3 I've learned that interpolating means to approximate data between two key frames. Is the process of interpolating vertex normals computationally expensive?


Nope. Graphics cards are very good at this sort of thing. In fact, anything you pass into the pixel shader gets interpolated as a matter of course. If you've ever colored by vertex, you can easily spot this.

Quote:
2) Sometimes non-interpolated normals aren't normalized. Normalizing interpolated normals in the pixel shader doesn't seem like a good idea since the square-root (a slow function from what I can gather) function is required when normalizing vectors. Is there a better way in ensuring that all interpolated normals are already normalized after the vertex shader is done with the data?


You can normalize in the vertex shader, but then you'll essentially be doing per-vertex lighting. Per-pixel lighting is naturally much more expensive to do. As far as I'm aware, there's not much you can optimize on this end. For this reason and others, many games bake in lighting where and how they can.

Quote:
3) In using phong shading my geometry appears smoother. From that I conclude it to be a superior shading method to my old ambient-diffuse-specular lighting shader. Is the phong shading technique an industry standard/norm? Or is there a superior technique I'll learn about later on?


I can't comment on whether it's an "industry standard" or not (though my gut tells me otherwise, phong-lit items tend to look very "fake"). The "right" lighting model depends very much on the material you need to represent. There are plenty out there; Minnaert, Oren-Nayar, Cook-Torrance, and a whole slew of Anisotropic specular shaders just to name a few. I don't see where your old shader does it's specular calculations, but if it's anything like your diffuse, the reason is that you're doing the lighting per-pixel now, as opposed to per-vertex or "goraud" shading. The Wikipedia article on Goraud Shading helps show the difference on a more obvious scale.

Transformations

Quote:
1) What exactly is homogeneous space? From what I can tell all of the points in a homogeneous space have a 4th component, w, which is equal to 1 on all points (w = 1). What is the significance of this fourth component? Why is it needed? And how does a homogeneous space differ from a normal 'ole 3D space (xyz).


The w doesn't have to equal 1 (though I'm not sure of any scenarios where that's a good idea). In any case, it's used because the world/view/projection matrices are generally 4x4 in size. You can't multiply a 4x4 matrix by a 1x3 vector. I think there a few other beneficial uses as well, but that's one of the big ones.

Quote:
2) Visually, what is the distinction between transforming a point to world/model space and transforming it to World-View-Projection?


World - transforming positions the object and rotates it according to it's actual position in the "world". Useful for transforming normals and position, and probably a few other things. Keep in mind that model space is not the same as world space. Think of Model space as "untransformed" space.

View - Transforms from the world relative to the camera. Quite a few things can use world-view for transforms.

Projection - "Squishes" the view frustum into a cube I think.

Basically, all three together transform an object into projection space, what you mostly see (not quite the same as screen space though, which is a simple 2D plane).

Quote:
In the struct OutputVS, why are 'normalW' and 'posW' labeled with TEXCOORD0 and TEXCOORD1 respectively? From what I can gather, these are called registers and they tell the GPU how the variables are to be used. But I don't understand why these two variables are passed onto the pixel shader as texture coordinates. Texture coordinates usually have {u, v} points but these variables are defined with float3 (x, y, z).


If you try changing PosW to POSITION1, it won't work. I actually posted a topic the other day asking about that, in fact (though I should've known better). You set it to TEXCOORD so the pixel shader will interpolate it. That's how per-pixel lighting works (as far as the normals go; the position is used to accurately calculate from the surface of the model instead of the center).

Whew, longest post I've made. I'm no guru at HLSL either, so hopefully I'm just stupid enough that I had to use simple terms to explain.

Topic Locked

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

Sign in to reply to this topic.