DirectX/10.0/Direct3D/纹理
本教程将介绍如何在 DirectX 11 中使用纹理。纹理允许我们通过将照片和其他图像应用到多边形面上来为场景添加逼真效果。例如,在本教程中,我们将使用以下图像
然后将其应用于上一个教程中的多边形,以生成以下结果
我们将使用的纹理格式为 .dds 文件。这是 DirectX 使用的 Direct Draw 表面格式。用于生成 .dds 文件的工具包含在 DirectX SDK 中。它位于 DirectX 实用程序下,称为 DirectX 纹理工具。您可以创建任意大小和格式的新纹理,然后将图像或其他格式纹理剪切并粘贴到其中,并将其保存为 .dds 文件。它非常易于使用。
在我们深入代码之前,我们应该讨论纹理映射的工作原理。为了将 .dds 图像中的像素映射到多边形,我们使用称为纹理坐标系的系统。该系统将像素的整数值转换为介于 0.0f 和 1.0f 之间的浮点值。例如,如果纹理宽度为 256 像素,则第一个像素将映射到 0.0f,第 256 个像素将映射到 1.0f,而第 128 个中间像素将映射到 0.5f。
在纹理坐标系中,宽度值称为“U”,高度值称为“V”。宽度从左边的 0.0 延伸到右边的 1.0。高度从顶部的 0.0 延伸到底部的 1.0。例如,左上角将表示为 U 0.0,V 0.0,而右下角将表示为 U 1.0,V 1.0。我在下面制作了一张图以说明此系统
现在我们已经对如何将纹理映射到多边形有了基本了解,我们可以查看本教程的更新框架
自上一个教程以来,框架的变化是新的 TextureClass(位于 ModelClass 内部)和新的 TextureShaderClass(取代了 ColorShaderClass)。我们将从查看新的 HLSL 纹理着色器开始代码部分。
纹理顶点着色器类似于之前的颜色着色器,只是为了适应纹理,做了一些更改。
//////////////////////////////////////////////////////////////////////////////// // Filename: texture.vs //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
我们不再在顶点类型中使用颜色,而是改用纹理坐标。由于纹理坐标采用 U 和 V 浮点坐标,因此我们使用 float2 作为其类型。纹理坐标的语义对于顶点着色器和像素着色器来说是 TEXCOORD0。您可以将零更改为任何数字以指示您正在使用的坐标集,因为允许多个纹理坐标。
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType TextureVertexShader(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; return output; }
//////////////////////////////////////////////////////////////////////////////// // Filename: texture.ps ////////////////////////////////////////////////////////////////////////////////
纹理像素着色器有两个全局变量。第一个是 Texture2D shaderTexture,它是纹理资源。这将是我们用于在模型上渲染纹理的纹理资源。第二个新变量是 SamplerState SampleType。采样器状态允许我们修改在着色时像素写入多边形面的方式。例如,如果多边形距离很远,并且在屏幕上只占 8 个像素,那么我们使用采样器状态来确定从原始纹理中实际绘制哪些像素或哪些像素组合。原始纹理可能为 256 像素乘以 256 像素,因此决定绘制哪些像素对于确保纹理在非常小的多边形面上仍然看起来不错至关重要。我们将在 TextureShaderClass 中设置采样器状态,然后将其附加到资源指针,以便此像素着色器可以使用它来确定要绘制哪些像素样本。
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType;
纹理像素着色器的 PixelInputType 也使用纹理坐标而不是颜色值进行了修改。
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
像素着色器已修改,以便现在使用 HLSL 采样函数。采样函数使用我们上面定义的采样器状态和此像素的纹理坐标。它使用这两个变量来确定并返回多边形面上此 UV 位置的像素值。
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 TexturePixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; // Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex); return textureColor; }
TextureClass 封装了单个纹理资源的加载、卸载和访问。对于每个需要的纹理,必须实例化此类的对象。
//////////////////////////////////////////////////////////////////////////////// // Filename: textureclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTURECLASS_H_ #define _TEXTURECLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx11tex.h> //////////////////////////////////////////////////////////////////////////////// // Class name: TextureClass //////////////////////////////////////////////////////////////////////////////// class TextureClass { public: TextureClass(); TextureClass(const TextureClass&); ~TextureClass();
前两个函数将从给定文件名加载纹理,并在不再需要时卸载该纹理。
bool Initialize(ID3D11Device*, WCHAR*); void Shutdown();
GetTexture 函数返回指向纹理资源的指针,以便着色器可以使用它进行渲染。
ID3D11ShaderResourceView* GetTexture(); private:
这是私有的纹理资源。
ID3D11ShaderResourceView* m_texture; }; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: textureclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "textureclass.h"
类构造函数将纹理着色器资源指针初始化为 null。
TextureClass::TextureClass() { m_texture = 0; } TextureClass::TextureClass(const TextureClass& other) { } TextureClass::~TextureClass() { }
Initialize 接收 Direct3D 设备和纹理的文件名作为输入,然后将纹理文件加载到名为 m_texture 的着色器资源变量中。现在可以使用该纹理进行渲染。
bool TextureClass::Initialize(ID3D11Device* device, WCHAR* filename) { HRESULT result; // Load the texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename, NULL, NULL, &m_texture, NULL); if(FAILED(result)) { return false; } return true; }
Shutdown 函数在加载纹理资源后释放它,然后将指针设置为 null。
void TextureClass::Shutdown() { // Release the texture resource. if(m_texture) { m_texture->Release(); m_texture = 0; } return; }
GetTexture 是其他需要访问纹理着色器资源的对象调用的函数,以便它们可以使用该纹理进行渲染。
ID3D11ShaderResourceView* TextureClass::GetTexture() { return m_texture; }
自上一个教程以来,ModelClass 进行了更改,以便现在可以适应纹理。
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h>
TextureClass 头文件现在包含在 ModelClass 头文件中。
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "textureclass.h"
//////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private:
VertexType 已用纹理坐标组件替换了颜色组件。纹理坐标现在取代了上一个教程中使用的绿色。
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; };
public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D11Device*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount();
ModelClass 还具有一个 GetTexture 函数,因此它可以将其自己的纹理资源传递给将绘制此模型的着色器。
ID3D11ShaderResourceView* GetTexture();
private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*);
ModelClass 同时拥有 LoadTexture 和 ReleaseTexture,分别用于加载和释放用于渲染此模型的纹理。
bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture();
private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount;
m_Texture 变量用于加载、释放和访问此模型的纹理资源。
TextureClass* m_Texture;
}; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h" ModelClass::ModelClass() { m_vertexBuffer = 0; m_indexBuffer = 0;
类构造函数现在将新的纹理对象初始化为 null。
m_Texture = 0;
} ModelClass::ModelClass(const ModelClass& other) { } ModelClass::~ModelClass() { }
Initialize 现在接收模型将使用的 .dds 纹理的文件名作为输入。
bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename)
{ bool result; // Initialize the vertex and index buffer that hold the geometry for the triangle. result = InitializeBuffers(device); if(!result) { return false; }
Initialize 函数现在调用一个新的私有函数来加载纹理。
// Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; }
return true; } void ModelClass::Shutdown() {
Shutdown 函数现在调用一个新的私有函数来释放初始化期间加载的纹理对象。
// Release the model texture. ReleaseTexture();
// Release the vertex and index buffers. ShutdownBuffers(); return; } void ModelClass::Render(ID3D11DeviceContext* deviceContext) { // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return; } int ModelClass::GetIndexCount() { return m_indexCount; }
GetTexture 返回模型纹理资源。纹理着色器需要访问此纹理才能渲染模型。
ID3D11ShaderResourceView* ModelClass::GetTexture() { return m_Texture->GetTexture(); }
bool ModelClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; // Set the number of vertices in the vertex array. m_vertexCount = 3; // Set the number of indices in the index array. m_indexCount = 3; // 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; }
顶点数组现在具有纹理组件,而不是颜色组件。纹理向量始终是 U 优先,然后是 V。例如,第一个纹理坐标是三角形的左下角,对应于 U 0.0,V 1.0。使用页面顶部的图表来弄清楚您的坐标需要是什么。请注意,您可以更改坐标以将纹理的任何部分映射到多边形面的任何部分。在本教程中,我只为了简单起见进行了直接映射。
// Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left.
vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);
vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle.
vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f);
vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right.
vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);
// Load the index array with data. indices[0] = 0; // Bottom left. indices[1] = 1; // Top middle. indices[2] = 2; // Bottom right. // Set up the description of the 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; // 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; } void ModelClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; } void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { unsigned int stride; unsigned int offset; // Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
LoadTexture 是一个新的私有函数,它将创建纹理对象,然后使用提供的输入文件名对其进行初始化。此函数在初始化期间调用。
bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename) { bool result; // Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result) { return false; } return true; }
ReleaseTexture 函数将释放 LoadTexture 函数期间创建和加载的纹理对象。
void ModelClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
TextureShaderClass 只是之前教程中 ColorShaderClass 的更新版本。此类将用于使用顶点和像素着色器绘制 3D 模型。
//////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTURESHADERCLASS_H_ #define _TEXTURESHADERCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std; //////////////////////////////////////////////////////////////////////////////// // Class name: TextureShaderClass //////////////////////////////////////////////////////////////////////////////// class TextureShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: TextureShaderClass(); TextureShaderClass(const TextureShaderClass&); ~TextureShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11Buffer* m_matrixBuffer;
采样器状态指针有一个新的私有变量。此指针将用于与纹理着色器交互。
ID3D11SamplerState* m_sampleState; }; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "textureshaderclass.h" TextureShaderClass::TextureShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0;
在类构造函数中,新的采样器变量被设置为 null。
m_sampleState = 0; } TextureShaderClass::TextureShaderClass(const TextureShaderClass& other) { } TextureShaderClass::~TextureShaderClass() { } bool TextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result;
为该着色器加载了新的 texture.vs 和 texture.ps HLSL 文件。
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/texture.vs", L"../Engine/texture.ps"); if(!result) { return false; } return true; }
Shutdown 函数调用着色器变量的释放。
void TextureShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
Render 函数现在采用一个名为 texture 的新参数,它是指向纹理资源的指针。然后将其发送到 SetShaderParameters 函数,以便可以在着色器中设置纹理,然后用于渲染。
bool TextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { bool result; // Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; }
InitializeShader 设置纹理着色器。
bool TextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; unsigned int numElements; D3D11_BUFFER_DESC matrixBufferDesc;
我们有一个新的变量来保存纹理采样器描述,该描述将在该函数中设置。
D3D11_SAMPLER_DESC samplerDesc; // 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, "TextureVertexShader", "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, "TexturePixelShader", "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 pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; }
输入布局已更改,因为我们现在有一个纹理元素而不是颜色。第一个位置元素保持不变,但第二个元素的 SemanticName 和 Format 已更改为 TEXCOORD 和 DXGI_FORMAT_R32G32_FLOAT。这两个更改现在将使此布局与 ModelClass 定义和着色器文件中的类型定义中的新 VertexType 对齐。
// Create the vertex input layout description. // This setup needs to match the VertexType structure 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; // 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 dynamic matrix 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 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; }
采样器状态描述在这里设置,然后可以传递给像素着色器。纹理采样器描述中最重要的元素是 Filter。Filter 将确定它如何决定使用哪些像素或组合哪些像素来创建多边形面上纹理的最终外观。在下面的示例中,我使用 D3D11_FILTER_MIN_MAG_MIP_LINEAR,它在处理方面更昂贵,但提供最佳视觉效果。它告诉采样器对缩小、放大和 mip 等级采样使用线性插值。
AddressU 和 AddressV 被设置为 Wrap,这确保坐标保持在 0.0f 和 1.0f 之间。超出该范围的任何内容都会环绕并放置在 0.0f 和 1.0f 之间。采样器状态描述的所有其他设置都是默认值。
// 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; } return true; }
ShutdownShader 函数释放 TextureShaderClass 中使用的所有变量。
void TextureShaderClass::ShutdownShader() {
ShutdownShader 函数现在释放了在初始化期间创建的新采样器状态。
// 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 TextureShaderClass::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 TextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; MatrixBufferType* dataPtr; unsigned int bufferNumber; // Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the 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 constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
SetShaderParameters 函数已从之前的教程中修改,现在包括在像素着色器中设置纹理。
// Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); return true; }
RenderShader 调用着色器技术来渲染多边形。
void TextureShaderClass::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);
RenderShader 函数已更改,包括在渲染之前在像素着色器中设置样本状态。
// Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h"
GraphicsClass 现在包含新的 TextureShaderClass 头文件,并且 ColorShaderClass 头文件已被删除。
#include "textureshaderclass.h"
///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model;
添加了一个新的 TextureShaderClass 私有对象。
TextureShaderClass* m_TextureShader;
}; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
在构造函数中,m_TextureShader 变量被设置为 null。
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0;
m_TextureShader = 0;
} GraphicsClass::GraphicsClass(const GraphicsClass& other) { } GraphicsClass::~GraphicsClass() { } bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; // 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; } // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; }
ModelClass::Initialize 函数现在接受将用于渲染模型的纹理名称。
// Initialize the model object.
result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds");
if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; }
创建并初始化新的 TextureShaderClass 对象。
// Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; }
return true; } void GraphicsClass::Shutdown() {
TextureShaderClass 对象也在 Shutdown 函数中被释放。
// Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 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() { bool result; // Render the graphics scene. result = Render(); if(!result) { return false; } return true; } bool GraphicsClass::Render() { D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix; bool result; // 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 view, projection, and world matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetWorldMatrix(worldMatrix); // 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 texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; }
// Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
您现在应该了解加载纹理、将其映射到多边形面,然后使用着色器渲染它的基础知识。
1. 重新编译代码并确保屏幕上出现纹理映射三角形。完成後,按下 Escape 退出。
2. 创建自己的 dds 纹理并将其放在与 seafloor.dds 相同的目录中。在 GraphicsClass::Initialize 函数内部,将模型初始化更改为使用您的纹理名称,然后重新编译并运行程序。
3. 更改代码以创建两个形成正方形的三角形。将整个纹理映射到此正方形,以便整个纹理正确显示在屏幕上。
4. 将相机移动到不同的距离以查看 MIN_MAG_MIP_LINEAR 滤镜的效果。
5. 尝试其他一些过滤器并将相机移动到不同的距离以查看不同的结果。