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

CG: normal mapping w/ simple diffuse lighting

Started by d h k May 26, 2009 at 2:59 PM 6 replies 3.8k views
Original Post
d h k
d h k
I know these kinds of questions get asked all the time around here, and I'm really sorry, but I sort of reached a point where I don't know how to progress any further... I'm running OpenGL and NVidia CG 2.2 on a GeForce GTX 260. Take a look at these two shaders: vertex shader:

void main (	in float4 inPosition : POSITION,
			in float2 inTexCoords : TEXCOORD0,
				
			out float4 outPosition : POSITION,
			out float2 outTexCoords : TEXCOORD0,
			out float4 outLightVector : TEXCOORD1,
				
			uniform float4x4 worldViewProj,
			uniform float4 lightPosition )
{
	outPosition = mul ( worldViewProj, inPosition );
	outLightVector = normalize ( lightPosition - outPosition );
	outTexCoords = inTexCoords;
}

pixel shader:

void main ( in float2 inTexCoords : TEXCOORD0,
			in float4 inLightVector : TEXCOORD1,

			out float4 outColor : COLOR,
			
			uniform sampler2D texture : TEXUNIT0,
			uniform sampler2D normalmap : TEXUNIT1,
			
			uniform float4x4 worldViewProj )
{
	// look up diffuse color from texture
	float3 texture_color = tex2D ( texture, inTexCoords );
	
	// look up normal and clamp it into proper range
	float4 normal_vector = tex2D ( normalmap, inTexCoords );
	normal_vector.rgb = 2.0 * ( normal_vector - 0.5 ).rgb;
	
	// move normal into worldspace
	normal_vector = mul ( worldViewProj, normal_vector );
	
	// calculate diffuse-factor
	float3 diffuse = dot ( inLightVector.rgb, normal_vector.rgb );

	// return calculated rgb and full alpha
	outColor.rgb = diffuse * texture_color;
	outColor.a = 1.0;
}

In the vertex shader, I'm moving the vertex position into world space by multiplying it with the world projection matrix. I also calculate a normalized light vector from the vertex position (the one already in world space) to the light position (supplied to the shader in world space of course). Then, in the pixel shader, I'm moving the normal vector (read from the normal map) into world space as well (again, multiplying by the world projection matrix) and then calculating the diffuse term (dot product of light and normal vector). For some reason, all I get is pure black. I'm drawing a model 100 units into the screen (at 0/0/-100 in world space coordinates), I have a light set to be at 10 units into the screen (at 0/0/-10 in world space coordinates) - how can that give me a black mesh? Here is what I do in my application:

void Shader::AddParameter ( std::string name )
{
	CGparameter parameter = cgGetNamedParameter ( program, name.c_str ( ) );
	check_for_cg_error ( "Failed to add parameter to shader");

	this->parameter.push_back ( parameter );
}

void Shader::SetMatrixParameter ( std::string name, CGGLenum matrix, CGGLenum transform )
{
	cgGLSetStateMatrixParameter ( cgGetNamedParameter ( program, name.c_str ( ) ), matrix, transform );
	check_for_cg_error ( "Failed to set matrix parameter for shader" );
}

void Shader::SetVectorParameter ( std::string name, Vector3 vector )
{
	cgGLSetParameter4f ( cgGetNamedParameter ( program, name.c_str ( ) ), vector.x, vector.y, vector.z, 1.0f );
	check_for_cg_error ( "Failed to set vector parameter for shader" );
}

// then, when a material is applied, before the mesh is rendered
light.position.Set ( 0.0f, 0.0f, -10.0f );

// set parameters for shaders
vertex_shader.SetMatrixParameter ( "worldViewProj", CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY );
	vertex_shader.SetVectorParameter ( "lightPosition", light.position );
	pixel_shader.SetMatrixParameter ( "worldViewProj", CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY );

So, first of all, I'd appreciate if somebody could look at the shaders at maybe see something wrong there? Some problem with the spaces etc.? I'm still new to this all. Second, what else could be causing this? Any ideas? If I need to post more code I'm happy to do so, just let me know. Thanks ahead of time!
AverageJoeSSU
AverageJoeSSU
try outputting your normal map in the fragment shader... to see if it looks right.
if so, check your other variables that way... your shader code looks like it would give non black to me.
------------------------------ redwoodpixel.com
d h k
d h k
Thanks for the answer, I tried out a bunch of things, here's what I noticed:

In the pixel shader (compare to shader posted in OP):
outColor.rgb = texture_color;

This textures the mesh correctly with the texture (TEXUNIT0), as expected.

outColor.rgb = normal_vector;

Now with this, I'm not quite sure what to expect, I can see the normal map and it does seem to work correctly, shifting colors as the mesh rotates in the blueish, light purpleish region, depending on the angle, I can make out the details of the normal map. Seems okay, dunno.

outColor.rgb = diffuse;

This one is definitely wrong, it's just pure black.

For the last one: if I change the order of arguments in the...

normal_vector = mul ( worldViewProj, normal_vector );


...line, I do get mostly black but then sometimes a grayscale version of the normalmap. Doesn't really look right though...

Any further ideas?

EDIT: If I combine this output "outColor.rgb = diffuse * texture_color" with the inverted parameters ("normal_vector = mul ( normal_vector, worldViewProj )"), I get something that roughly looks normal mapped, except that sometimes it still turns all white, sometimes all black (with some mesh angles)... Is this a space error in the pipeline somewhere?

[Edited by - d h k on May 26, 2009 4:52:00 PM]
d h k
d h k
Well, inLightVector (from the pixel shader) comes from the vertex shader where it's calculated like this:

outLightVector = normalize ( lightPosition - outPosition );


lightPosition and outPosition should both be in world space, lightPosition being set manually to 0/0/-10 (as it says in the OP) and outPosition which was just moved to world space in the line above the one I posted.

That would be the way it should be, right? Seeing how the normal is also in world space and then I calculate the dot product between both vectors in world space?
Digitalfragment
Digitalfragment
d h k: you say,

"I'm moving the vertex position into world space by multiplying it with the world projection matrix."

but what you are doing is transforming the vertex position into clip-space, which is post perspective-projection. Its not in world-space at all, and so any other math that expects it to be world space is not going to work.

Your output position from a vertex shader needs to be in this coordinate space, however that doesnt stop you from supplying the world position through another tex coord. to get the data in world coordinates, multiply it through *just* the world matrix.
d h k
d h k
Okay, I think I'm getting what you're saying... :)

So, I came up with this vertex shader:

void main (	in float4 inPosition : POSITION,			in float2 inTexCoords : TEXCOORD0,							out float4 outPosition : POSITION,			out float2 outTexCoords : TEXCOORD0,			out float4 outLightVector : TEXCOORD1,							uniform float4x4 worldViewProj,			uniform float4x4 worldMatrix,			uniform float4 lightPosition ){	// move from object to clip space (has to be done by vertex shader)	outPosition = mul ( worldViewProj, inPosition );		// also move from object to world space...	inPosition = mul ( worldMatrix, inPosition );		// ...so that we can calculate the light vector in world space	outLightVector = normalize ( lightPosition - inPosition );		// pass texture coordinates right through	outTexCoords = inTexCoords;}


I now pass it an additional world matrix, which is used to take the input position (I assume this is in object space in the beginning, right?) and multiply it by the world matrix to obtain the vertex position in world space. Then do the rest as usual.

Am I heading in the right direction now?

One more problem, how do I get the world matrix from CG/OpenGL? I'm using cgGLSetStateMatrixParameter like this at the moment:

// this calls "cgGLSetStateMatrixParameter" with the last two arguments and the parameter handleshader->SetMatrixParameter ( "worldMatrix", CG_GL_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY );


Is CG_GL_PROJECTION_MATRIX the world matrix? Doesn't sound like it. MODELVIEW_MATRIX doesn't work either... BTW: I decided against calculating the world position in the main program and sending it to the vertex shader because I wasn't quite sure on how to do that using displaylists. I could figure a way out, I guess, but first I'd like to hear whether the things I said in the first part of this post were on the right tracks at all, or not... :p

More clarification would be appreciated but thanks for the help so far!
Digitalfragment
Digitalfragment
Yeah thats in the right track.

Well, OpenGL has no concept of a world matrix, you would have to implement this yourself. Which is partially the reason why OpenGL performs all of its lighting in view-space not world space.

Topic Locked

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

Sign in to reply to this topic.