确保顶点数据包含Tangent、Binormal、Normal这三个分量,其实就是顶点表面的x, y, z三个分量的矢量表示(注意在d3d中是左手系),若没有则需要利用d3dx函数计算这些分量,并重新创建mesh
// 声明支持normal、tangent、binormal的mesh const D3DVERTEXELEMENT9 vertexDecl[] = { { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, { 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, { 0, 20, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, { 0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0 }, { 0, 44, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0 }, D3DDECL_END() }; // 根据新的声明重建mesh FAILED_THROW_D3DEXCEPTION(tempMesh->CloneMesh(tempMesh->GetOptions(), vertexDecl, pd3dDevice, &m_mesh)); D3DVERTEXELEMENT9 vertexOldDecl[MAX_FVF_DECL_SIZE]; FAILED_THROW_D3DEXCEPTION(tempMesh->GetDeclaration(vertexOldDecl)); bool bHadNormal = false; bool bHadTangent = false; bool bHadBinormal = false; for(UINT i = 0; i < D3DXGetDeclLength(vertexOldDecl); i++) { switch(vertexOldDecl[i].Usage) { case D3DDECLUSAGE_NORMAL: bHadNormal = true; break; case D3DDECLUSAGE_TANGENT: bHadTangent = true; break; case D3DDECLUSAGE_BINORMAL: bHadBinormal = true; break; } } // 生成normals, 并优化inplace HRESULT hr; if(!bHadNormal) { V(D3DXComputeNormals(m_mesh, NULL)); } std::vector<DWORD> rgdwAdjacency(m_mesh->GetNumFaces() * 3); V(m_mesh->GenerateAdjacency(1e-6f, &rgdwAdjacency[0])); V(m_mesh->OptimizeInplace(D3DXMESHOPT_VERTEXCACHE, &rgdwAdjacency[0], NULL, NULL, NULL)); // 生成tangent, binormal if(!bHadTangent || !bHadBinormal) { CComPtr<ID3DXMesh> retMesh; FAILED_THROW_D3DEXCEPTION(D3DXComputeTangentFrameEx( m_mesh, D3DDECLUSAGE_TEXCOORD, 0, D3DDECLUSAGE_TANGENT, 0, D3DDECLUSAGE_BINORMAL, 0, D3DDECLUSAGE_NORMAL, 0, 0, &rgdwAdjacency[0], -1.01f, -0.01f, -1.01f, &retMesh, NULL)); m_mesh = retMesh; }
在顶点管线中,将世界坐标中的光源转换到模型表面的Tangent坐标中
void RenderBumpMapVS( float4 inPositionOS : POSITION, float2 inTexCoord : TEXCOORD0, float3 inNormalOS : NORMAL, float3 inTangentOS : TANGENT, float3 inBinormalOS : BINORMAL, out float4 outPosition : POSITION, out float2 outTexCoord : TEXCOORD0, out float3 outLightTS : TEXCOORD1 ) { // 将顶点变换到相机透视空间 outPosition = mul( inPositionOS, g_mWorldViewProjection ); // 复制贴图坐标 outTexCoord = inTexCoord; // 将模型空间中的法线变换到世界坐标,注意,仅仅是旋转变换 float3 vNormalWS = mul(inNormalOS, (float3x3) g_mWorld); float3 vTangentWS = mul(inTangentOS, (float3x3) g_mWorld); float3 vBinormalWS = mul(inBinormalOS, (float3x3) g_mWorld); vNormalWS = normalize(vNormalWS); vTangentWS = normalize(vTangentWS); vBinormalWS = normalize(vBinormalWS); // 将光源方向转换成贴图表面的Tangent坐标,注意,右手坐标系转换 // 注意:在vs中进行normalize是没有意义的,因为ps中的值是插值结果 float3x3 mWorldToTangent = float3x3(vTangentWS, vBinormalWS, vNormalWS); outLightTS = mul(g_LightDir, mWorldToTangent); }
在像素管线中读取Normal Texture的值并转换为Tangent坐标中的法线,再求以光照
void RenderBumpMapPS( float2 texCoord : TEXCOORD0, float3 lightTS : TEXCOORD1, out float4 color : COLOR ) { // 需要对插值结果重新normalize float3 vLightTS = normalize(lightTS); // 获得法线贴图的法线,转换到[-1, 1]空间之中 float3 vNormalTS = normalize(tex2D(samplerNormalTexture, texCoord) * 2 - 1); // 获取原始贴图的颜色,这里要求原始贴图和发现贴图使用公用的uv float4 cBaseColor = tex2D(MeshTextureSampler, texCoord); // 输出颜色 color = cBaseColor * (saturate(dot(vNormalTS, vLightTS)) * g_MaterialDiffuseColor + g_MaterialAmbientColor); }