Normal Mapping with multiple lights (help please)

Started by
16 comments, last by cozzie 10 years, 2 months ago

I am having problems implementing any fx for normal mapping with multiple lights (except one with multiple passes). This is going to sound like I want someone to "do my homework", but I'm to the point of desperation.....

The problem with the multi-pass approach is that I end up rendering the scene multiple times. I found a few fx's that do this in a single pass, but they don't work (I can't understand how to make them work properly). I had asked for help on one of them a few weeks ago with no luck.....

Now I'm begging for some help. I would like a link or even the actual file with FULL explanation of how to implement it (make it SUPER SIMPLE to understand). HLSL has a lot of terms I have yet to wrap my head around, so the simpler to implement the better.

Here is what I would like:

-- normal mapping

-- ambient light

-- multiple point lights

-- multiple spot lights (could be cubic lights)

-- at least one directional light

Thank you in advance.......

Advertisement

If you've tried and failed to get this working, why aren't you posting your shader code? That's the biggest reason I feel like you're asking people to do your homework for you.

If you're getting weird results, you might have forgotten to saturate the addition of the lights. Or you might be multiplying by color/texture before saturating. If you don't understand how you'd add multiple light sources together, here's a tutorial: http://www.rastertek.com/dx10tut30.html If somehow that doesn't make sense, it would really help if you elaborate a bit.

I'm using DX9....

I have already gone down this road with posting my code in an earlier thread. It got me nowhere because either the people reading didn't understand my code or didn't care to help. I'm going to post it again:



	D3DXMATRIX VP=ActorController.Actors[0].rotMatrix*GE->MyCamera.m_projMatrix;
	D3DXMATRIX compVM=ActorController.Actors[0].rotMatrix*ShipController.Ship[0].RotationMatrix;
//	D3DXMatrixInverse(&compVM,0,&compVM);
    static D3DXHANDLE hTechnique;
	static UINT totalPasses;

    hTechnique = FXManager.Effect[NormalMappingFX].FX->GetTechniqueByName("NormalMappingPointLighting");

    if (FAILED(FXManager.Effect[NormalMappingFX].FX->SetTechnique(hTechnique)))
        return;

   // Set the camera position.
D3DXVECTOR3 pos=ActorController.Actors[0].ActorLoc.Normalize();
pos*=(float)ActorController.Actors[0].ActorLoc.Length;
    FXManager.Effect[NormalMappingFX].FX->SetValue("cameraPos", &pos, sizeof(pos));

	// Set the scene global ambient term.
float g_sceneAmbient[4] = {0.1f, 0.1f, 0.1f, 1.0f};
    
    FXManager.Effect[NormalMappingFX].FX->SetValue("globalAmbient", &g_sceneAmbient, sizeof(g_sceneAmbient));

    // Set the number of active lights.
 int g_numLights=1;
    FXManager.Effect[NormalMappingFX].FX->SetValue("numLights", &g_numLights, sizeof(g_numLights));

    D3DXHANDLE hLight;
    D3DXHANDLE hLightPos;
    D3DXHANDLE hLightAmbient;
    D3DXHANDLE hLightDiffuse;
    D3DXHANDLE hLightSpecular;
    D3DXHANDLE hLightRadius;
        hLight = FXManager.Effect[NormalMappingFX].FX->GetParameterElement("lights", 0);
        
        hLightPos = FXManager.Effect[NormalMappingFX].FX->GetParameterByName(hLight, "pos");
        hLightAmbient = FXManager.Effect[NormalMappingFX].FX->GetParameterByName(hLight, "ambient");
        hLightDiffuse = FXManager.Effect[NormalMappingFX].FX->GetParameterByName(hLight, "diffuse");
        hLightSpecular = FXManager.Effect[NormalMappingFX].FX->GetParameterByName(hLight, "specular");
        hLightRadius = FXManager.Effect[NormalMappingFX].FX->GetParameterByName(hLight, "radius");
//float Lpos[3]={1,2,2};
		D3DXVECTOR3 Lpos(0,0,0);
//		Lpos-=pos;
float amb[4]={0,0,0,0};
float diff[4]={1,0,1,0};
float spec[4]={0,0,0,0};
        FXManager.Effect[NormalMappingFX].FX->SetValue(hLightPos, Lpos, sizeof(Lpos));
        FXManager.Effect[NormalMappingFX].FX->SetValue(hLightAmbient, amb, sizeof(amb));
        FXManager.Effect[NormalMappingFX].FX->SetValue(hLightDiffuse, diff, sizeof(diff));
        FXManager.Effect[NormalMappingFX].FX->SetValue(hLightSpecular, spec, sizeof(spec));
        FXManager.Effect[NormalMappingFX].FX->SetFloat(hLightRadius, 3.0f);





	Material g_dullMaterial={
    0.2f, 0.2f, 0.2f, 1.0f,
    0.8f, 0.8f, 0.8f, 1.0f,
    0.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 0.0f, 1.0f,
    100.0f
	};

    FXManager.Effect[NormalMappingFX].FX->SetValue("material.ambient", g_dullMaterial.ambient, sizeof(g_dullMaterial.ambient));
    FXManager.Effect[NormalMappingFX].FX->SetValue("material.diffuse", g_dullMaterial.diffuse, sizeof(g_dullMaterial.diffuse));
    FXManager.Effect[NormalMappingFX].FX->SetValue("material.emissive", g_dullMaterial.emissive, sizeof(g_dullMaterial.emissive));
    FXManager.Effect[NormalMappingFX].FX->SetValue("material.specular", g_dullMaterial.specular, sizeof(g_dullMaterial.specular));
    FXManager.Effect[NormalMappingFX].FX->SetFloat("material.shininess", g_dullMaterial.shininess);

	FXManager.Effect[NormalMappingFX].FX->SetTexture("colorMapTexture", TextureList[ShipTexture[1].C].Texture);
	FXManager.Effect[NormalMappingFX].FX->SetTexture("normalMapTexture", TextureList[ShipTexture[0].N].Texture);

	//do a render sort
	numInSort=0;
	for (UINT s=0;s<Section.size();s++){
		D3DXVECTOR3 v(Section[s].pos.x,Section[s].pos.y,Section[s].pos.z);
		v-=pos;
		float d=D3DXVec3Length(&v);
		SL[numInSort].Sect=s;
		SL[numInSort].dist=d;
		SO[numInSort]=0;
		numInSort++;
	}
	SO[0]=0;
	for (int i=1;i<numInSort;i++){
		int q=0;
		for (q=0;q<i;q++) if (SL[i].dist>SL[SO[q]].dist) break;
		if (q<=i) for (int q2=i;q2>q;q2--) SO[q2]=SO[q2-1];//shift everything else down the list
		SO[q]=i;
	}
bool ZR=false;
if (ActorController.Actors[0].InMesh==-1) ZR=true;
	for (int sl=0;sl<numInSort;sl++){
		int s=SL[SO[sl]].Sect;
	D3DXVECTOR3 v(Section[s].pos.x,Section[s].pos.y,Section[s].pos.z);
	v-=pos;
	FXManager.Effect[NormalMappingFX].FX->SetValue(hLightPos, Lpos, sizeof(Lpos));
	for (UINT i=0;i<Section[s].Objects.size();i++) {
		tmp=Section[s].Objects[i].matrix;
		tmp(3,0)+=v.x;
		tmp(3,1)+=v.y;
		tmp(3,2)+=v.z;

		FXManager.Effect[NormalMappingFX].FX->SetMatrix("worldMatrix", &tmp);

		D3DXMATRIX wit=tmp;
		wit(3,0)=0;
		wit(3,1)=0;
		wit(3,2)=0;
		wit(3,3)=1;;
		D3DXMatrixInverse(&wit,0,&wit);
		D3DXMatrixTranspose(&wit,&wit);
		FXManager.Effect[NormalMappingFX].FX->SetMatrix("worldInverseTransposeMatrix", &wit);

		tmp*=VP;
		FXManager.Effect[NormalMappingFX].FX->SetMatrix("worldViewProjectionMatrix", &tmp);

		if (SUCCEEDED(FXManager.Effect[NormalMappingFX].FX->Begin(&totalPasses, 0)))
		{
			for (UINT pass = 0; pass < totalPasses; ++pass)
			{
				if (SUCCEEDED(FXManager.Effect[NormalMappingFX].FX->BeginPass(pass)))
				{
					MeshObject[mesh].ppMesh->DrawSubset(Section[s].Objects[i].ObjIndex);
					FXManager.Effect[NormalMappingFX].FX->EndPass();
				}
			}

			FXManager.Effect[NormalMappingFX].FX->End();
		}

	}
	}

Here is the .fx:


//-----------------------------------------------------------------------------
// Copyright (c) 2008 dhpoware. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//
// Tangent space normal mapping with multiple point lights in a single pass
// using shader model 3.0. This effect file limits the number of point lights
// to 8.
//
//-----------------------------------------------------------------------------

#define MAX_POINT_LIGHTS 8

struct PointLight
{
	float3 pos;
	float4 ambient;
	float4 diffuse;
	float4 specular;
	float radius;
};

struct Material
{
	float4 ambient;
	float4 diffuse;
	float4 emissive;
	float4 specular;
	float shininess;
};

//-----------------------------------------------------------------------------
// Globals.
//-----------------------------------------------------------------------------

float4x4 worldMatrix;
float4x4 worldInverseTransposeMatrix;
float4x4 worldViewProjectionMatrix;

float3 cameraPos;
float4 globalAmbient;
int numLights;

PointLight lights[MAX_POINT_LIGHTS];
Material material;

//-----------------------------------------------------------------------------
// Textures.
//-----------------------------------------------------------------------------

texture colorMapTexture;
texture normalMapTexture;

sampler2D colorMap = sampler_state
{
	Texture = <colorMapTexture>;
    MagFilter = Linear;
    MinFilter = Anisotropic;
    MipFilter = Linear;
    MaxAnisotropy = 16;
};

sampler2D normalMap = sampler_state
{
    Texture = <normalMapTexture>;
    MagFilter = Linear;
    MinFilter = Anisotropic;
    MipFilter = Linear;
    MaxAnisotropy = 16;
};

//-----------------------------------------------------------------------------
// Vertex Shaders.
//-----------------------------------------------------------------------------

struct VS_INPUT
{
	float3 position : POSITION;
	float2 texCoord : TEXCOORD0;
	float3 normal : NORMAL;
    float4 tangent : TANGENT;
};

struct VS_OUTPUT
{
	float4 position : POSITION;
	float3 worldPos : TEXCOORD0;
	float2 texCoord : TEXCOORD1;
	float3 normal : TEXCOORD2;
	float3 tangent : TEXCOORD3;
	float3 bitangent : TEXCOORD4;
};

VS_OUTPUT VS_PointLighting(VS_INPUT IN)
{
	VS_OUTPUT OUT;

	OUT.position = mul(float4(IN.position, 1.0f), worldViewProjectionMatrix);
	OUT.worldPos = mul(float4(IN.position, 1.0f), worldMatrix).xyz;
	OUT.texCoord = IN.texCoord;
	
	OUT.normal = mul(IN.normal, (float3x3)worldInverseTransposeMatrix);
	OUT.tangent = mul(IN.tangent.xyz, (float3x3)worldInverseTransposeMatrix);
	OUT.bitangent = cross(OUT.normal, OUT.tangent) * IN.tangent.w;


	return OUT;
}

//-----------------------------------------------------------------------------
// Pixel Shaders.
//-----------------------------------------------------------------------------

float4 PS_PointLighting(VS_OUTPUT IN) : COLOR
{
    float3 t = normalize(IN.tangent);
    float3 b = normalize(IN.bitangent);
    float3 n = normalize(IN.normal);

    float3x3 tbnMatrix = float3x3(t.x, b.x, n.x,
	                              t.y, b.y, n.y,
	                              t.z, b.z, n.z);
	                                 
    float3 v = normalize(mul(cameraPos - IN.worldPos, tbnMatrix));
    float3 l = float3(0.0f, 0.0f, 0.0f);
    float3 h = float3(0.0f, 0.0f, 0.0f);
    
    float atten = 0.0f;
    float nDotL = 0.0f;
    float nDotH = 0.0f;
    float power = 0.0f;
    
    float4 color = float4(0.0f, 0.0f, 0.0f, 0.0f);
    
    n = normalize(tex2D(normalMap, IN.texCoord).rgb * 2.0f - 1.0f);
    
    for (int i = 0; i < numLights; ++i)
    {
        l = mul((lights[i].pos - IN.worldPos) / lights[i].radius, tbnMatrix);
        atten = saturate(1.0f - dot(l, l));
        
        l = normalize(l);
        h = normalize(l + v);
        
        nDotL = saturate(dot(n, l));
        nDotH = saturate(dot(n, h));
        power = (nDotL == 0.0f) ? 0.0f : pow(nDotH, material.shininess);
        
        color += (material.ambient * (globalAmbient + (atten * lights[i].ambient))) +
                 (material.diffuse * lights[i].diffuse * nDotL * atten) +
                 (material.specular * lights[i].specular * power * atten);
    }
                   
	return color * tex2D(colorMap, IN.texCoord);
}

//-----------------------------------------------------------------------------
// Techniques.
//-----------------------------------------------------------------------------

technique NormalMappingPointLighting
{
    pass
    {
        VertexShader = compile vs_3_0 VS_PointLighting();
        PixelShader = compile ps_3_0 PS_PointLighting();
    }
}


Here is a video illustrating the outcome:

[media]http:

[/media]

There is supposed to be 1 point light (at the player's head). The surfaces are not rendering properly....

This is how it SHOULD look:

[media]http:

[/media]

Debug a pixel in PIX, step through the pixel shader to see where you're going wrong.

Look at your two videos, it looks like you're missing the green channel for some reason (everything is magenta: red and blue).

The first video is pink because I wanted to have a contrast.

float diff[4]={1,0,1,0};

This line is the diffuse for the light-- I left out the green component. This is not the problem I was trying to illustrate.

I am convinced it is tied to the Tangent,Bitangent, and Normal calculations in the effect. It seems to be required as an input from the mesh. There is a conversion used in the author's code, but it was used to generate these from a customized vertex buffer. Here is the function:


void CalcTangentVector(float pos1[3], float pos2[3],
                       float pos3[3], const float texCoord1[2],
                       const float texCoord2[2], const float texCoord3[2],
					   float normal[3], D3DXVECTOR4 &tangent){			//********************	MAY HAVE TO USE THIS TO GET THE LIGHTS TO SHOW UP RIGHT
																				//		*********** WILL HAVE TO CLONEFVF EACH MESH AND CALCULATE THE TANGENTS ***************

    // Given the 3 vertices (position and texture coordinates) of a triangle
    // calculate and return the triangle's tangent vector. The handedness of
    // the local coordinate system is stored in tangent.w. The bitangent is
    // then: float3 bitangent = cross(normal, tangent.xyz) * tangent.w.

    // Create 2 vectors in object space.
    //
    // edge1 is the vector from vertex positions pos1 to pos2.
    // edge2 is the vector from vertex positions pos1 to pos3.
    D3DXVECTOR3 edge1(pos2[0] - pos1[0], pos2[1] - pos1[1], pos2[2] - pos1[2]);
    D3DXVECTOR3 edge2(pos3[0] - pos1[0], pos3[1] - pos1[1], pos3[2] - pos1[2]);

    D3DXVec3Normalize(&edge1, &edge1);
    D3DXVec3Normalize(&edge2, &edge2);

    // Create 2 vectors in tangent (texture) space that point in the same
    // direction as edge1 and edge2 (in object space).
    //
    // texEdge1 is the vector from texture coordinates texCoord1 to texCoord2.
    // texEdge2 is the vector from texture coordinates texCoord1 to texCoord3.
    D3DXVECTOR2 texEdge1(texCoord2[0] - texCoord1[0], texCoord2[1] - texCoord1[1]);
    D3DXVECTOR2 texEdge2(texCoord3[0] - texCoord1[0], texCoord3[1] - texCoord1[1]);

    D3DXVec2Normalize(&texEdge1, &texEdge1);
    D3DXVec2Normalize(&texEdge2, &texEdge2);

    // These 2 sets of vectors form the following system of equations:
    //
    //  edge1 = (texEdge1.x * tangent) + (texEdge1.y * bitangent)
    //  edge2 = (texEdge2.x * tangent) + (texEdge2.y * bitangent)
    //
    // Using matrix notation this system looks like:
    //
    //  [ edge1 ]     [ texEdge1.x  texEdge1.y ]  [ tangent   ]
    //  [       ]  =  [                        ]  [           ]
    //  [ edge2 ]     [ texEdge2.x  texEdge2.y ]  [ bitangent ]
    //
    // The solution is:
    //
    //  [ tangent   ]        1     [ texEdge2.y  -texEdge1.y ]  [ edge1 ]
    //  [           ]  =  -------  [                         ]  [       ]
    //  [ bitangent ]      det A   [-texEdge2.x   texEdge1.x ]  [ edge2 ]
    //
    //  where:
    //        [ texEdge1.x  texEdge1.y ]
    //    A = [                        ]
    //        [ texEdge2.x  texEdge2.y ]
    //
    //    det A = (texEdge1.x * texEdge2.y) - (texEdge1.y * texEdge2.x)
    //
    // From this solution the tangent space basis vectors are:
    //
    //    tangent = (1 / det A) * ( texEdge2.y * edge1 - texEdge1.y * edge2)
    //  bitangent = (1 / det A) * (-texEdge2.x * edge1 + texEdge1.x * edge2)
    //     normal = cross(tangent, bitangent)

    D3DXVECTOR3 bitangent;
    float det = (texEdge1.x * texEdge2.y) - (texEdge1.y * texEdge2.x);

    if (fabsf(det) < 1e-6f)    // almost equal to zero
    {
        tangent.x = 1.0f;
        tangent.y = 0.0f;
        tangent.z = 0.0f;

        bitangent.x = 0.0f;
        bitangent.y = 1.0f;
        bitangent.z = 0.0f;
    }
    else
    {
        det = 1.0f / det;

        tangent.x = (texEdge2.y * edge1.x - texEdge1.y * edge2.x) * det;
        tangent.y = (texEdge2.y * edge1.y - texEdge1.y * edge2.y) * det;
        tangent.z = (texEdge2.y * edge1.z - texEdge1.y * edge2.z) * det;
        tangent.w = 0.0f;

        bitangent.x = (-texEdge2.x * edge1.x + texEdge1.x * edge2.x) * det;
        bitangent.y = (-texEdge2.x * edge1.y + texEdge1.x * edge2.y) * det;
        bitangent.z = (-texEdge2.x * edge1.z + texEdge1.x * edge2.z) * det;

        D3DXVec4Normalize(&tangent, &tangent);
        D3DXVec3Normalize(&bitangent, &bitangent);
    }

    // Calculate the handedness of the local tangent space.
    // The bitangent vector is the cross product between the triangle face
    // normal vector and the calculated tangent vector. The resulting bitangent
    // vector should be the same as the bitangent vector calculated from the
    // set of linear equations above. If they point in different directions
    // then we need to invert the cross product calculated bitangent vector. We
    // store this scalar multiplier in the tangent vector's 'w' component so
    // that the correct bitangent vector can be generated in the normal mapping
    // shader's vertex shader.

    D3DXVECTOR3 n(normal[0], normal[1], normal[2]);
    D3DXVECTOR3 t(tangent.x, tangent.y, tangent.z);
    D3DXVECTOR3 b;

    D3DXVec3Cross(&b, &n, &t);
    tangent.w = (D3DXVec3Dot(&b, &bitangent) < 0.0f) ? -1.0f : 1.0f;

}

For each face of the mesh, the 3 vertex information is extracted and sent as parameters to this function. The result is the tangent.

Because the author used his own custom vertex buffer, I had to create a function to extract the information from the mesh so it could be sent to the function:


void ConvertMeshForLighting(int M,GAMEENGINE *GE){
	LPD3DXMESH tmpMesh;
	HRESULT hr=MeshObject[M].ppMesh->CloneMeshFVF(D3DPOOL_DEFAULT,NM_CUSTOM_VERTEX,GE->d3ddev,&tmpMesh);
	if (hr==D3D_OK){
		MeshObject[M].ppMesh->Release();
		MeshObject[M].ppMesh=tmpMesh;
		LPVOID IB;
		hr=MeshObject[M].ppMesh->LockIndexBuffer(D3DLOCK_READONLY,&IB);
		if (hr!=D3D_OK) tp2=2345;
		short *ib=(short*)IB;
		short numFaces=(short)MeshObject[M].ppMesh->GetNumFaces();

		LPVOID VB;
		hr=MeshObject[M].ppMesh->LockVertexBuffer(D3DLOCK_NO_DIRTY_UPDATE,&VB);
		if (hr!=D3D_OK) tp2=345;
		NMVertex *vb=(NMVertex*)VB;

		for (short f=0;f<numFaces*3;f+=3){
			CalcTangentVector(vb[ib[f]].pos,vb[ib[f+1]].pos,vb[ib[f+2]].pos,vb[ib[f]].texCoord,vb[ib[f+1]].texCoord,vb[ib[f+2]].texCoord
				,vb[ib[f]].normal,vb[ib[f]].tangent);
			vb[ib[f+1]].tangent=vb[ib[f+2]].tangent=vb[ib[f]].tangent;
		}

		MeshObject[M].ppMesh->UnlockVertexBuffer();
		MeshObject[M].ppMesh->UnlockIndexBuffer();
	}
	else{
		if (hr==D3DERR_INVALIDCALL) tp2=12345;
		if (hr==E_OUTOFMEMORY) tp2=54321;
	}

}

This function first converts the FVF to:


#define NM_CUSTOM_VERTEX (D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_NORMAL|D3DFVF_TEX2)
struct NMVertex
{
    float pos[3];
    float texCoord[2];
    float normal[3];
    D3DXVECTOR4 tangent;
};

It then destroys the original mesh and sets the pointer to the new mesh. Then it locks the IB and VB for the mesh and takes the vertex info from each index and sends it to the function............ I think you get the idea.

This function seems to do nothing for me as the results look exactly the same regardless of whether I use it or not.

Well the tangent and binormal are essential for normal-mapping, so if those aren't correct that would be a problem.

So... do the values for tangent look right? Step through the debugger and find out.

Also... I've never used the FVF's, so I could be wrong about this. But are you sure your custom vertex format matches what is expected in the shader? Your shader expects TANGENT (float4 tangent : TANGENT;), but you have no such thing.

This post suggests that you can't use FVFs if you require TANGENT or BINORMAL in your shader.

The D3DFVF_TEX2 is a 4 float value so that would handle D3DXVECTOR4. The inability to use FVFs with TANGENT or BINORMAL parameters sounds like a lack of forethought on the part of developers (people responsible for DX and video card interaction). How would I get around that? I am using meshes I construct in 3DSMax and export into .x files…


How would I get around that? I am using meshes I construct in 3DSMax and export into .x files…

http://msdn.microsoft.com/en-us/library/windows/desktop/bb206335(v=vs.85).aspx

So ID3DXBaseMesh::DrawSubset is obsolete?

What about indexed primitives? Is that used? What method would be the fastest/most efficient method?

This topic is closed to new replies.

Advertisement