DirectX/10.0/Direct3D/凹凸贴图
本教程将介绍如何在 DirectX 11 中使用 HLSL 和 C++ 执行凹凸贴图。本教程中的代码基于之前教程中的代码。
我们使用的凹凸贴图技术的正确术语称为法线贴图。原因是使用了一种称为法线贴图的特殊纹理,它本质上是表面法线的查找表。此法线贴图中的每个像素都指示纹理颜色贴图上对应像素的光线方向。
例如,以下为颜色贴图
上面纹理的法线贴图如下所示
使用法线贴图和每个像素的当前光线方向,将生成以下凹凸贴图纹理
如您所见,这种效果非常逼真,并且使用凹凸贴图生成该效果的成本远低于渲染高多边形表面以获得相同的效果。
要创建法线贴图,您通常需要有人生成表面的 3D 模型,然后使用工具将该 3D 模型转换为法线贴图。还有一些工具可以处理 2D 纹理来生成相当不错的法线贴图,但显然它不像 3D 模型版本那样准确。
创建法线贴图的工具会获取 x、y、z 坐标并将其转换为红、绿、蓝像素,每个颜色的强度表示它们所代表的法线的角度。我们多边形表面的法线仍然与之前相同的方式计算。但是,我们还需要计算的另外两个法线需要该多边形表面的顶点和纹理坐标。这两个法线称为切线和副切线。下图显示了每个法线的方向
法线仍然直接指向观察者。但是,切线和副切线沿着多边形的表面运行,切线沿着 x 轴,副切线沿着 y 轴。这两个法线然后直接转换为法线贴图的 tu 和 tv 纹理坐标,纹理 U 坐标映射到切线,纹理 V 坐标映射到副切线。
我们需要进行一些预先计算,才能使用法线和纹理坐标来确定副切线和切线向量。还要注意,您永远不要在着色器中执行此操作,因为所有涉及的浮点数学运算都相当昂贵。相反,我使用 C++ 代码中的一个函数来在模型加载期间执行此操作,您将在下面看到。此外,如果您希望在大量高多边形模型上使用这种效果,最好预先计算这些不同的法线并将它们存储在您的模型格式中。一旦我们预先计算了切线和副切线,就可以使用以下等式来使用法线贴图确定任何像素的凹凸法线
bumpNormal = normal + bumpMap.x * tangent + bumpMap.y * binormal;
一旦我们有了该像素的法线,就可以针对光线方向进行计算,并乘以来自颜色纹理的像素的颜色值,以获得最终结果。
本教程的框架如下所示。唯一的新类是 BumpMapShaderClass。
我们将从查看凹凸贴图 HLSL 着色器代码开始本教程
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmap.vs //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
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; }
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmap.ps //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // /////////////
凹凸贴图着色器需要两种纹理。数组中的第一种纹理是颜色纹理。第二种纹理是法线贴图。
Texture2D shaderTextures[2]; SamplerState SampleType;
与大多数灯光着色器一样,灯光的方向和颜色是照明计算所必需的。
cbuffer LightBuffer { float4 diffuseColor; float3 lightDirection; }; ////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; };
像素着色器按我们上面描述的方式工作,代码中还有几行代码。首先,我们从颜色纹理和法线贴图采样像素。然后,我们将法线贴图值乘以 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; }
BumpMapShaderClass 只是之前教程中着色器类的一个修改版本。
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmapshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _BUMPMAPSHADERCLASS_H_ #define _BUMPMAPSHADERCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std; //////////////////////////////////////////////////////////////////////////////// // Class name: BumpMapShaderClass //////////////////////////////////////////////////////////////////////////////// class BumpMapShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; struct LightBufferType { D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float padding; }; public: BumpMapShaderClass(); BumpMapShaderClass(const BumpMapShaderClass&); ~BumpMapShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**, D3DXVECTOR3, D3DXVECTOR4); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**, D3DXVECTOR3, D3DXVECTOR4); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer; ID3D11SamplerState* m_sampleState;
凹凸贴图着色器将需要一个常量缓冲区来与灯光方向和灯光颜色交互。
ID3D11Buffer* m_lightBuffer; }; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: bumpmapshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "bumpmapshaderclass.h"
类构造函数将指针初始化为 null。
BumpMapShaderClass::BumpMapShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0; m_sampleState = 0; m_lightBuffer = 0; } BumpMapShaderClass::BumpMapShaderClass(const BumpMapShaderClass& other) { } BumpMapShaderClass::~BumpMapShaderClass() { }
Initialize 函数将调用着色器来加载凹凸贴图 HLSL 文件。
bool BumpMapShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/bumpmap.vs", L"../Engine/bumpmap.ps"); if(!result) { return false; } return true; }
Shutdown 释放着色器效果。
void BumpMapShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
Render 函数首先设置着色器参数,然后使用凹凸贴图着色器渲染模型。
bool BumpMapShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { bool result; // Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray, lightDirection, diffuseColor); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
InitializeShader 设置凹凸贴图着色器。
bool BumpMapShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer;
多边形布局现在设置为五个元素,以适应切线和副切线。
D3D11_INPUT_ELEMENT_DESC polygonLayout[5]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC lightBufferDesc; // Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0;
凹凸贴图顶点着色器在此加载。
// Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "BumpMapVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &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, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; }
凹凸贴图像素着色器在此加载。
// Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "BumpMapPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &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, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // 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 = D3D11_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 = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_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 = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0;
布局现在包含一个切线和副切线元素,它们与法线元素的设置方式相同,但语义名称除外。
polygonLayout[3].SemanticName = "TANGENT"; polygonLayout[3].SemanticIndex = 0; polygonLayout[3].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[3].InputSlot = 0; polygonLayout[3].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[3].InputSlotClass = D3D11_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 = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[4].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[4].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Setup the description of the matrix dynamic constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } // Create a texture sampler state description. samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.MipLODBias = 0.0f; samplerDesc.MaxAnisotropy = 1; samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; samplerDesc.BorderColor[0] = 0; samplerDesc.BorderColor[1] = 0; samplerDesc.BorderColor[2] = 0; samplerDesc.BorderColor[3] = 0; samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; // Create the texture sampler state. result = device->CreateSamplerState(&samplerDesc, &m_sampleState); if(FAILED(result)) { return false; }
灯光常量缓冲区在此设置。
// Setup the description of the light dynamic constant buffer that is in the pixel shader. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; } return true; }
ShutdownShader 函数释放 InitializeShader 函数中设置的所有指针。
void BumpMapShaderClass::ShutdownShader() { // Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 0; } return; }
OutputShaderErrorMessage 如果 HLSL 着色器文件无法正确编译,则将错误写入文本文件。
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 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 函数在渲染发生之前设置着色器参数。
bool BumpMapShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; LightBufferType* dataPtr2; // Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the matrix constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the matrix constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the matrix constant buffer in the vertex shader. bufferNumber = 0; // Now set the matrix constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
纹理数组在此设置,它包含两种纹理。第一种纹理是颜色纹理,第二种纹理是法线贴图。
// Set shader texture array resource in the pixel shader. deviceContext->PSSetShaderResources(0, 2, textureArray);
像素着色器中的灯光缓冲区随后使用漫射灯光颜色和灯光方向进行设置。
// Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData; // Copy the lighting variables into the constant buffer. dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; // Unlock the constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer); return true; }
RenderShader 使用凹凸贴图着色器绘制模型。
void BumpMapShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangles. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #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(ID3D11Device*, char*, WCHAR*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView** GetTextureArray(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*); void ReleaseTextures(); bool LoadModel(char*); void ReleaseModel();
我们有三个新函数用于计算模型的切线和副切线向量。
void CalculateModelVectors(); void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&); void CalculateNormal(VectorType, VectorType, VectorType&); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; ModelType* m_model; TextureArrayClass* m_TextureArray; }; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h"
Initialize 函数现在接受两个纹理文件名。第一个纹理文件名用于颜色纹理。第二个纹理文件名用于将用于创建凹凸效果的法线贴图。
bool ModelClass::Initialize(ID3D11Device* 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 buffers. result = InitializeBuffers(device); if(!result) { return false; }
模型的两种纹理在此加载。第一个是颜色纹理,第二个是法线贴图。
// Load the textures for this model. result = LoadTextures(device, textureFilename1, textureFilename2); if(!result) { return false; } return true; } bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_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 static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // 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(ID3D11Device* 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 函数用于生成模型的切线、副切线和重新计算的法线向量。首先,它计算模型中包含的面数(三角形)。然后,它遍历每个三角形,获取其三个顶点并计算切线、副切线和法线。最后,它将这三个法线向量保存回模型结构中。
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 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
[edit | edit source]//////////////////////////////////////////////////////////////////////////////// // 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"
这里在 GraphicsClass 头文件中包含了新的 BumpMapShaderClass 头文件。
#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
[edit | edit source]//////////////////////////////////////////////////////////////////////////////// // 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; } 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 对象使用 cube 模型、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 D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } 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->GetDeviceContext());
使用凹凸贴图着色器渲染模型。
// Render the model using the bump map shader. m_BumpMapShader->Render(m_D3D->GetDeviceContext(), 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; }
Summary
[edit | edit source]使用凹凸贴图着色器,您可以使用两个 2D 纹理创建看起来非常逼真的 3D 场景。
To Do Exercises
[edit | edit source]1. 重新编译并运行程序。您应该看到一个旋转的凹凸贴图立方体。按 Esc 键退出。
2. 在着色器中将凹凸贴图效果从 2.5 更改为更小的值(例如 1.0)和更大的值(例如 5.0)以查看凹凸深度的变化。
3. 在像素着色器中注释掉 color = color * textureColor; 行以查看仅凹凸照明效果。
4. 移动相机和灯光位置以从不同角度查看效果。