DirectX 10 教程20:凹凸映射
原文地址:Tutorial 20: Bump Mapping(http://www.rastertek.com/dx10tut20.html)。
源代码下载:dx10tut20.zip。
本教程介绍如何使用HLSL和C++实现凹凸映射,本教程的代码基于上一个教程。
我们使用的凹凸映射技术的正确叫法应是法线映射,原因是我们使用了一张叫做法线贴图的特殊纹理,通过法线贴图可以查询表面法线。法线贴图上的每个像素对应颜色纹理的每个像素的法线方向。我们使用的颜色纹理如下图所示:
用于上一张纹理的法线贴图如下图所示:
使用法线映射之后,生成的效果如下图所示:
如你所见,视觉效果变得更为真实。比起用更多的多边形,使用法线贴图能用小得多的开销达到同样的效果。
要创建法线贴图,通常需要别人生成一个3D模型,然后使用工具将这个3D模型表面转换为法线贴图。还有些工具可以将2D纹理处理成不错的法线贴图,但是没有从3D模型转换而来的那么精确。
创建法线贴图的工具会将x,y,z坐标转换为red,green,blue像素,分别代表三个矢量的方向,多边形的法线计算方法仍和以前相同,而切线和副法线的计算需要顶点和纹理坐标的信息。下图表示了三个矢量的方向:
法线指向观察者,而切线和副法线位于多边形表面,切线沿x轴,副法线沿y轴,然后两者直接转换到法线贴图的tu和tv纹理坐标,其中纹理的U坐标映射切线,纹理V坐标映射副法线。
我们需要使用法线和纹理坐标事先计算切线和副法线矢量。注意不要在shader中进行这个操作,因为浮点计算是非常耗时的,我在加载模型的时候用C++代码编写了一个函数进行以上的工作。如果你需要在一个多边形数量很大的模型上施加凹凸映射,那么做好事先计算这三个矢量并将它们存储到你的模型格式中去。有了事先计算的切线和副法线,你就可以使用下面的方程确定任意像素的法线了。
bumpNormal = normal + bumpMap.x * tangent + bumpMap.y * binormal;
有了每个像素的法线,我们就可以进一步根据光照方向、颜色纹理确定最终的输出颜色。
框架
本教程使用的框架如下图所示,新的类为BumpMapShaderClass。
首先看一下bump map HLSL shader代码:
Bumpmap.fx
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmap.fx //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix;
bump map shader需要两张纹理,数组中的第一张纹理为颜色纹理,第二张为法线贴图。
Texture2D shaderTextures[2];
需要光照方向和颜色用于光照计算。
float4 diffuseColor; float3 lightDirection; /////////////////// // SAMPLE STATES // /////////////////// SamplerState SampleType { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; };
VertexInputType和PixelInputType都有了切线和副法线矢量用于法线映射的计算。
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; }; //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType BumpMapVertexShader(VertexInputType input) { PixelInputType output; // Change the position vector to be 4 units for proper matrix calculations. input.position.w = 1.0f; // Calculate the position of the vertex against the world, view, and projection matrices. output.position = mul(input.position, worldMatrix); output.position = mul(output.position, viewMatrix); output.position = mul(output.position, projectionMatrix); // Store the texture coordinates for the pixel shader. output.tex = input.tex; // Calculate the normal vector against the world matrix only and then normalize the final value. output.normal = mul(input.normal, (float3x3)worldMatrix); output.normal = normalize(output.normal);
输入的切线和副法线都会乘以世界矩阵并进行归一化。
// Calculate the tangent vector against the world matrix only and then normalize the final value. output.tangent = mul(input.tangent, (float3x3)worldMatrix); output.tangent = normalize(output.tangent); // Calculate the binormal vector against the world matrix only and then normalize the final value. output.binormal = mul(input.binormal, (float3x3)worldMatrix); output.binormal = normalize(output.binormal); return output; }
在像素着色器中,我们首先采样颜色纹理和法线贴图的像素,然后将法线贴图的值乘以2再减1使这个值的范围变为-1.0到+1.0,做这一步的原因是纹理坐标的范围是0.0到+1.0,只是法线值的一半,所以需要进行映射。之后,我们就使用前面提到的方程计算映射法线,经过归一化后的法线与光照方向进行点乘后就得出了光照强度,最后使用这个光照强度、光照颜色和纹理颜色获取最终的像素颜色。
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 BumpMapPixelShader(PixelInputType input) : SV_Target { float4 textureColor; float4 bumpMap; float3 bumpNormal; float3 lightDir; float lightIntensity; float4 color; // Sample the texture pixel at this location. textureColor = shaderTextures[0].Sample(SampleType, input.tex); // Sample the pixel in the bump map. bumpMap = shaderTextures[1].Sample(SampleType, input.tex); // Expand the range of the normal value from (0, +1) to (-1, +1). bumpMap = (bumpMap * 2.0f) - 1.0f; // Calculate the normal from the data in the bump map. bumpNormal = input.normal + bumpMap.x * input.tangent + bumpMap.y * input.binormal; // Normalize the resulting bump normal. bumpNormal = normalize(bumpNormal); // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel based on the bump map normal value. lightIntensity = saturate(dot(bumpNormal, lightDir)); // Determine the final diffuse color based on the diffuse color and the amount of light intensity. color = saturate(diffuseColor * lightIntensity); // Combine the final bump light color with the texture color. //color = color * textureColor; return color; } //////////////////////////////////////////////////////////////////////////////// // Technique //////////////////////////////////////////////////////////////////////////////// technique10 BumpMapTechnique { pass pass0 { SetVertexShader(CompileShader(vs_4_0, BumpMapVertexShader())); SetPixelShader(CompileShader(ps_4_0, BumpMapPixelShader())); SetGeometryShader(NULL); } }
Bumpmapshaderclass.h
BumpMapShaderClass只是上一个教程shader类的修改版本。
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmapshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _BUMPMAPSHADERCLASS_H_ #define _BUMPMAPSHADERCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d10.h> #include <d3dx10.h> #include <fstream> using namespace std; //////////////////////////////////////////////////////////////////////////////// // Class name: BumpMapShaderClass //////////////////////////////////////////////////////////////////////////////// class BumpMapShaderClass { public: BumpMapShaderClass(); BumpMapShaderClass(const BumpMapShaderClass&); ~BumpMapShaderClass(); bool Initialize(ID3D10Device*, HWND); void Shutdown(); void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView**, D3DXVECTOR3, D3DXVECTOR4); private: bool InitializeShader(ID3D10Device*, HWND, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D10ShaderResourceView**, D3DXVECTOR3, D3DXVECTOR4); void RenderShader(ID3D10Device*, int); private: ID3D10Effect* m_effect; ID3D10EffectTechnique* m_technique; ID3D10InputLayout* m_layout; ID3D10EffectMatrixVariable* m_worldMatrixPtr; ID3D10EffectMatrixVariable* m_viewMatrixPtr; ID3D10EffectMatrixVariable* m_projectionMatrixPtr;
bump map shader需要指向纹理数组,光照方向和颜色的指针。
ID3D10EffectShaderResourceVariable* m_textureArrayPtr; ID3D10EffectVectorVariable* lightDirectionPtr; ID3D10EffectVectorVariable* diffuseColorPtr; }; #endif
Bumpmapshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmapshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "bumpmapshaderclass.h" BumpMapShaderClass::BumpMapShaderClass() { m_effect = 0; m_technique = 0; m_layout = 0; m_worldMatrixPtr = 0; m_viewMatrixPtr = 0; m_projectionMatrixPtr = 0;
在构造函数中将纹理数组、光照方向和颜色初始化为null。
m_textureArrayPtr = 0; lightDirectionPtr = 0; diffuseColorPtr = 0; } BumpMapShaderClass::BumpMapShaderClass(const BumpMapShaderClass& other) { } BumpMapShaderClass::~BumpMapShaderClass() { }
Initialize方法加载bump map HLSL文件。
bool BumpMapShaderClass::Initialize(ID3D10Device* device, HWND hwnd) { bool result; // Initialize the shader that will be used to draw the triangles. result = InitializeShader(device, hwnd, L"../Engine/bumpmap.fx"); if(!result) { return false; } return true; }
Shutdown释放shader effect。
void BumpMapShaderClass::Shutdown() { // Shutdown the shader effect. ShutdownShader(); return; }
Render中首先设置shader参数,然后使用bump map shader绘制模型。
void BumpMapShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView** textureArray, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { // Set the shader parameters that it will use for rendering. SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, textureArray, lightDirection, diffuseColor); // Now render the prepared buffers with the shader. RenderShader(device, indexCount); return; }
InitializeShader创建bump map shader。
bool BumpMapShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename) { HRESULT result; ID3D10Blob* errorMessage;
polygon layout现在包含五个元素,包含了切线和副法线。
D3D10_INPUT_ELEMENT_DESC polygonLayout[5]; unsigned int numElements; D3D10_PASS_DESC passDesc; // Initialize the error message. errorMessage = 0; // Load the shader in from the file. result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, device, NULL, NULL, &m_effect, &errorMessage, NULL); if(FAILED(result)) { // If the shader failed to compile it should have writen something to the error message. if(errorMessage) { OutputShaderErrorMessage(errorMessage, hwnd, filename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, filename, L"Missing Shader File", MB_OK); } return false; }
Technique名称修改为BumpMapTechnique。
// Get a pointer to the technique inside the shader. m_technique = m_effect->GetTechniqueByName("BumpMapTechnique"); if(!m_technique) { return false; } // Now setup the layout of the data that goes into the shader. // This setup needs to match the VertexType stucture in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; polygonLayout[2].SemanticName = "NORMAL"; polygonLayout[2].SemanticIndex = 0; polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[2].InputSlot = 0; polygonLayout[2].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0;
layout现在包含了切线和副法线元素,除了语义名称,它们的定义方式与法线相同。
polygonLayout[3].SemanticName = "TANGENT"; polygonLayout[3].SemanticIndex = 0; polygonLayout[3].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[3].InputSlot = 0; polygonLayout[3].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT; polygonLayout[3].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; polygonLayout[3].InstanceDataStepRate = 0; polygonLayout[4].SemanticName = "BINORMAL"; polygonLayout[4].SemanticIndex = 0; polygonLayout[4].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[4].InputSlot = 0; polygonLayout[4].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT; polygonLayout[4].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA; polygonLayout[4].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Get the description of the first pass described in the shader technique. m_technique->GetPassByIndex(0)->GetDesc(&passDesc); // Create the input layout. result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &m_layout); if(FAILED(result)) { return false; } // Get pointers to the three matrices inside the shader so we can update them from this class. m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix(); m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix(); m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();
下面的代码获取纹理数组,光线方向,光线颜色的指针。
// Get pointer to the texture array resource inside the shader. m_textureArrayPtr = m_effect->GetVariableByName("shaderTextures")->AsShaderResource(); // Get a pointer to the light direction and color variables inside the shader. lightDirectionPtr = m_effect->GetVariableByName("lightDirection")->AsVector(); diffuseColorPtr = m_effect->GetVariableByName("diffuseColor")->AsVector(); return true; }
ShutdownShader方法释放所有在InitializeShader方法中创建的对象。
void BumpMapShaderClass::ShutdownShader() { // Release the light pointers. lightDirectionPtr = 0; diffuseColorPtr = 0; // Release the pointer to the texture array in the shader file. m_textureArrayPtr = 0; // Release the pointers to the matrices inside the shader. m_worldMatrixPtr = 0; m_viewMatrixPtr = 0; m_projectionMatrixPtr = 0; // Release the pointer to the shader layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pointer to the shader technique. m_technique = 0; // Release the pointer to the shader. if(m_effect) { m_effect->Release(); m_effect = 0; } return; } void BumpMapShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename) { char* compileErrors; unsigned long bufferSize, i; ofstream fout; // Get a pointer to the error message text buffer. compileErrors = (char*)(errorMessage->GetBufferPointer()); // Get the length of the message. bufferSize = errorMessage->GetBufferSize(); // Open a file to write the error message to. fout.open("shader-error.txt"); // Write out the error message. for(i=0; i<bufferSize; i++) { fout << compileErrors[i]; } // Close the file. fout.close(); // Release the error message. errorMessage->Release(); errorMessage = 0; // Pop a message up on the screen to notify the user to check the text file for compile errors. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK); return; }
SetShaderParameters方法设置shader参数。
void BumpMapShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView** textureArray, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { // Set the world matrix variable inside the shader. m_worldMatrixPtr->SetMatrix((float*)&worldMatrix); // Set the view matrix variable inside the shader. m_viewMatrixPtr->SetMatrix((float*)&viewMatrix); // Set the projection matrix variable inside the shader. m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);
下面的代码设置纹理数组,此数组包含两张纹理,第一张为颜色纹理,第二张为法线贴图。
// Bind the texture array. m_textureArrayPtr->SetResourceArray(textureArray, 0, 2);
下面的代码设置光照方向和颜色。
// Set the direction of the light. lightDirectionPtr->SetFloatVector((float*)&lightDirection); // Set the diffuse color of the light. diffuseColorPtr->SetFloatVector((float*)&diffuseColor); return; }
RenderShader方法使用bump map shader绘制模型。
void BumpMapShaderClass::RenderShader(ID3D10Device* device, int indexCount) { D3D10_TECHNIQUE_DESC techniqueDesc; unsigned int i; // Set the input layout. device->IASetInputLayout(m_layout); // Get the description structure of the technique from inside the shader so it can be used for rendering. m_technique->GetDesc(&techniqueDesc); // Go through each pass in the technique (should be just one currently) and render the triangles. for(i=0; i<techniqueDesc.Passes; ++i) { m_technique->GetPassByIndex(i)->Apply(0); device->DrawIndexed(indexCount, 0, 0); } return; }
Modelclass.h
//////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_ ////////////// // INCLUDES // ////////////// #include <fstream> using namespace std; /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "texturearrayclass.h" //////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private:
VertexType结构体新添了切线和副法线矢量。
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; D3DXVECTOR3 normal; D3DXVECTOR3 tangent; D3DXVECTOR3 binormal; };
ModelType结构体也新添了切线和副法线矢量。
struct ModelType { float x, y, z; float tu, tv; float nx, ny, nz; float tx, ty, tz; float bx, by, bz; };
下面两个结构体用于计算切线和副法线。
struct TempVertexType { float x, y, z; float tu, tv; float nx, ny, nz; }; struct VectorType { float x, y, z; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D10Device*, char*, WCHAR*, WCHAR*); void Shutdown(); void Render(ID3D10Device*); int GetIndexCount(); ID3D10ShaderResourceView** GetTextureArray(); private: bool InitializeBuffers(ID3D10Device*); void ShutdownBuffers(); void RenderBuffers(ID3D10Device*); bool LoadTextures(ID3D10Device*, WCHAR*, WCHAR*); void ReleaseTextures(); bool LoadModel(char*); void ReleaseModel();
新添了三个法线用于计算切线和副法线。
void CalculateModelVectors(); void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&); void CalculateNormal(VectorType, VectorType, VectorType&); private: ID3D10Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; ModelType* m_model; TextureArrayClass* m_TextureArray; }; #endif
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
Initialize方法的参数包含两张纹理的文件名称。
bool ModelClass::Initialize(ID3D10Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2) { bool result; // Load in the model data. result = LoadModel(modelFilename); if(!result) { return false; }
调用新的CalculateModelVectors方法计算切线和副法线,还要重新计算法线矢量。
// Calculate the normal, tangent, and binormal vectors for the model. CalculateModelVectors(); // Initialize the vertex and index buffer that hold the geometry for the triangle. result = InitializeBuffers(device); if(!result) { return false; }
加载两张纹理。
// Load the texture array for this model. result = LoadTextures(device, textureFilename1, textureFilename2); if(!result) { return false; } return true; } bool ModelClass::InitializeBuffers(ID3D10Device* device) { VertexType* vertices; unsigned long* indices; D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D10_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; }
InitializeBuffers有所改变,顶点数组加载的数据来自于ModelType数组,而ModelType数组现在包含了切线和副法线数据,它们需要复制到顶点数组。
// Load the vertex array and index array with data. for(i=0; i<m_vertexCount; i++) { vertices[i].position = D3DXVECTOR3(m_model[i].x, m_model[i].y, m_model[i].z); vertices[i].texture = D3DXVECTOR2(m_model[i].tu, m_model[i].tv); vertices[i].normal = D3DXVECTOR3(m_model[i].nx, m_model[i].ny, m_model[i].nz); vertices[i].tangent = D3DXVECTOR3(m_model[i].tx, m_model[i].ty, m_model[i].tz); vertices[i].binormal = D3DXVECTOR3(m_model[i].bx, m_model[i].by, m_model[i].bz); indices[i] = i; } // Set up the description of the vertex buffer. vertexBufferDesc.Usage = D3D10_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; // Now finally create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the index buffer. indexBufferDesc.Usage = D3D10_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
LoadTextures加载颜色纹理和法线贴图。
bool ModelClass::LoadTextures(ID3D10Device* device, WCHAR* filename1, WCHAR* filename2) { bool result; // Create the texture array object. m_TextureArray = new TextureArrayClass; if(!m_TextureArray) { return false; } // Initialize the texture array object. result = m_TextureArray->Initialize(device, filename1, filename2); if(!result) { return false; } return true; }
CalculateModelVectors生成模型的切线和副法线,并且重新计算法线矢量。它首先计算模型有多少个面(三角形),然后根据每个三角形的三个顶点计算切线、副法线和法线。计算了这三个矢量之后将它们保存到ModelType结构数组中。
void ModelClass::CalculateModelVectors() { int faceCount, i, index; TempVertexType vertex1, vertex2, vertex3; VectorType tangent, binormal, normal; // Calculate the number of faces in the model. faceCount = m_vertexCount / 3; // Initialize the index to the model data. index = 0; // Go through all the faces and calculate the the tangent, binormal, and normal vectors. for(i=0; i<faceCount; i++) { // Get the three vertices for this face from the model. vertex1.x = m_model[index].x; vertex1.y = m_model[index].y; vertex1.z = m_model[index].z; vertex1.tu = m_model[index].tu; vertex1.tv = m_model[index].tv; vertex1.nx = m_model[index].nx; vertex1.ny = m_model[index].ny; vertex1.nz = m_model[index].nz; index++; vertex2.x = m_model[index].x; vertex2.y = m_model[index].y; vertex2.z = m_model[index].z; vertex2.tu = m_model[index].tu; vertex2.tv = m_model[index].tv; vertex2.nx = m_model[index].nx; vertex2.ny = m_model[index].ny; vertex2.nz = m_model[index].nz; index++; vertex3.x = m_model[index].x; vertex3.y = m_model[index].y; vertex3.z = m_model[index].z; vertex3.tu = m_model[index].tu; vertex3.tv = m_model[index].tv; vertex3.nx = m_model[index].nx; vertex3.ny = m_model[index].ny; vertex3.nz = m_model[index].nz; index++; // Calculate the tangent and binormal of that face. CalculateTangentBinormal(vertex1, vertex2, vertex3, tangent, binormal); // Calculate the new normal using the tangent and binormal. CalculateNormal(tangent, binormal, normal); // Store the normal, tangent, and binormal for this face back in the model structure. m_model[index-1].nx = normal.x; m_model[index-1].ny = normal.y; m_model[index-1].nz = normal.z; m_model[index-1].tx = tangent.x; m_model[index-1].ty = tangent.y; m_model[index-1].tz = tangent.z; m_model[index-1].bx = binormal.x; m_model[index-1].by = binormal.y; m_model[index-1].bz = binormal.z; m_model[index-2].nx = normal.x; m_model[index-2].ny = normal.y; m_model[index-2].nz = normal.z; m_model[index-2].tx = tangent.x; m_model[index-2].ty = tangent.y; m_model[index-2].tz = tangent.z; m_model[index-2].bx = binormal.x; m_model[index-2].by = binormal.y; m_model[index-2].bz = binormal.z; m_model[index-3].nx = normal.x; m_model[index-3].ny = normal.y; m_model[index-3].nz = normal.z; m_model[index-3].tx = tangent.x; m_model[index-3].ty = tangent.y; m_model[index-3].tz = tangent.z; m_model[index-3].bx = binormal.x; m_model[index-3].by = binormal.y; m_model[index-3].bz = binormal.z; } return; }
CalculateTangentBinormal方法的参数为三角形的三个顶点,经过计算后返回这三个顶点的切线和副法线。
void ModelClass::CalculateTangentBinormal(TempVertexType vertex1, TempVertexType vertex2, TempVertexType vertex3, VectorType& tangent, VectorType& binormal) { float vector1[3], vector2[3]; float tuVector[2], tvVector[2]; float den; float length; // Calculate the two vectors for this face. vector1[0] = vertex2.x - vertex1.x; vector1[1] = vertex2.y - vertex1.y; vector1[2] = vertex2.z - vertex1.z; vector2[0] = vertex3.x - vertex1.x; vector2[1] = vertex3.y - vertex1.y; vector2[2] = vertex3.z - vertex1.z; // Calculate the tu and tv texture space vectors. tuVector[0] = vertex2.tu - vertex1.tu; tvVector[0] = vertex2.tv - vertex1.tv; tuVector[1] = vertex3.tu - vertex1.tu; tvVector[1] = vertex3.tv - vertex1.tv; // Calculate the denominator of the tangent/binormal equation. den = 1.0f / (tuVector[0] * tvVector[1] - tuVector[1] * tvVector[0]); // Calculate the cross products and multiply by the coefficient to get the tangent and binormal. tangent.x = (tvVector[1] * vector1[0] - tvVector[0] * vector2[0]) * den; tangent.y = (tvVector[1] * vector1[1] - tvVector[0] * vector2[1]) * den; tangent.z = (tvVector[1] * vector1[2] - tvVector[0] * vector2[2]) * den; binormal.x = (tuVector[0] * vector2[0] - tuVector[1] * vector1[0]) * den; binormal.y = (tuVector[0] * vector2[1] - tuVector[1] * vector1[1]) * den; binormal.z = (tuVector[0] * vector2[2] - tuVector[1] * vector1[2]) * den; // Calculate the length of this normal. length = sqrt((tangent.x * tangent.x) + (tangent.y * tangent.y) + (tangent.z * tangent.z)); // Normalize the normal and then store it tangent.x = tangent.x / length; tangent.y = tangent.y / length; tangent.z = tangent.z / length; // Calculate the length of this normal. length = sqrt((binormal.x * binormal.x) + (binormal.y * binormal.y) + (binormal.z * binormal.z)); // Normalize the normal and then store it binormal.x = binormal.x / length; binormal.y = binormal.y / length; binormal.z = binormal.z / length; return; }
CalculateNormal方法的参数为切线和副法线,经过叉乘计算获取法线矢量。
void ModelClass::CalculateNormal(VectorType tangent, VectorType binormal, VectorType& normal) { float length; // Calculate the cross product of the tangent and binormal which will give the normal vector. normal.x = (tangent.y * binormal.z) - (tangent.z * binormal.y); normal.y = (tangent.z * binormal.x) - (tangent.x * binormal.z); normal.z = (tangent.x * binormal.y) - (tangent.y * binormal.x); // Calculate the length of the normal. length = sqrt((normal.x * normal.x) + (normal.y * normal.y) + (normal.z * normal.z)); // Normalize the normal. normal.x = normal.x / length; normal.y = normal.y / length; normal.z = normal.z / length; return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h"
新的BumpMapShaderClass头文件包含在GraphicsClass头文件中。
#include "bumpmapshaderclass.h" #include "lightclass.h" //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model;
新添了BumpMapShaderClass对象。
BumpMapShaderClass* m_BumpMapShader; LightClass* m_Light; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0;
在构造函数中将BumpMapShaderClass对象初始化为null。
m_BumpMapShader = 0; m_Light = 0; } GraphicsClass::GraphicsClass(const GraphicsClass& other) { } GraphicsClass::~GraphicsClass() { } bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; D3DXMATRIX baseViewMatrix; // Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Initialize a base view matrix with the camera for 2D user interface rendering. m_Camera->SetPosition(0.0f, 0.0f, -1.0f); m_Camera->Render(); m_Camera->GetViewMatrix(baseViewMatrix); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; }
ModelClass对象初始化为一个立方体,颜色纹理为stone01.dds,法线贴图为bump01.dds。
// Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/stone01.dds", L"../Engine/data/bump01.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; }
创建并初始化BumpMapShaderClass对象。
// Create the bump map shader object. m_BumpMapShader = new BumpMapShaderClass; if(!m_BumpMapShader) { return false; } // Initialize the bump map shader object. result = m_BumpMapShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the bump map shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; }
光照颜色设置为白色,光照方向设置为正Z轴方向。
// Initialize the light object. m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f); return true; } void GraphicsClass::Shutdown() { // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; }
在Shutdown方法中释放BumpMapShaderClass对象。
// Release the bump map shader object. if(m_BumpMapShader) { m_BumpMapShader->Shutdown(); delete m_BumpMapShader; m_BumpMapShader = 0; } // Release the model object. if(m_Model) { m_Model->Shutdown(); delete m_Model; m_Model = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the Direct3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } bool GraphicsClass::Frame() { // Set the position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -5.0f); return true; } bool GraphicsClass::Render() { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix; static float rotation = 0.0f; // Clear the buffers to begin the scene. m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and d3d objects. m_D3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetOrthoMatrix(orthoMatrix);
旋转立方体观察效果。
// Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.0025f; if(rotation > 360.0f) { rotation -= 360.0f; } // Rotate the world matrix by the rotation value. D3DXMatrixRotationY(&worldMatrix, rotation); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDevice()); // Render the model using the bump map shader. m_BumpMapShader->Render(m_D3D->GetDevice(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTextureArray(), m_Light->GetDirection(), m_Light->GetDiffuseColor()); // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
总结
使用bump map shader,你可以只用2D纹理就达到细节清晰的场景。
练习
1.编译并运行程序,你会看到一个旋转的立方体,按escape键退出。
2.在像素着色器中注释color = color * textureColor;,看看只有凹凸光照的效果。
3.移动相机和光源位置,从不同角度观察效果。
文件下载(已下载 1113 次)发布时间:2012/8/8 上午12:45:41 阅读次数:7279