DirectX/10.0/Direct3D/漫反射光照
在本教程中,我将介绍如何使用漫反射光照和 DirectX 11 来照亮 3D 对象。我们将从上一个教程的代码开始,并对其进行修改。
我们将要实现的漫反射光照类型称为方向光照。方向光照类似于太阳照亮地球的方式。它是一个距离很远的灯光源,根据它发出光的方向,可以确定任何物体上的光照量。但是,与环境光照(我们很快将介绍的另一种光照模型)不同,它不会照亮它没有直接接触到的表面。
我选择方向光照作为起点,因为它非常容易在视觉上进行调试。此外,由于它只需要一个方向,因此公式比其他类型的漫反射光照(如聚光灯和点光源)更简单。
在 DirectX 11 中,漫反射光照的实现是通过顶点着色器和像素着色器完成的。漫反射光照只需要任何我们要照亮的多边形的朝向和法线向量。朝向是一个您定义的单个向量,您可以使用构成多边形的三个顶点来计算任何多边形的法线。在本教程中,我们还将在光照方程中实现漫反射光的颜色。
在本教程中,我们将创建一个名为 LightClass 的新类,它将代表场景中的光源。LightClass 除了保存光的朝向和颜色之外,实际上不会做任何其他事情。我们还将删除 TextureShaderClass,并将其替换为 LightshaderClass,它处理模型上的光照着色。随着新类的添加,框架现在看起来如下所示
我们将从代码部分开始,看看 HLSL 光照着色器。你会注意到,光照着色器只是上一个教程中纹理着色器的更新版本。
//////////////////////////////////////////////////////////////////////////////// // Filename: light.vs //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
这两个结构现在都有一个 3 个浮点数的法线向量。法线向量用于通过使用法线方向和光照方向之间的角度来计算光照量。
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType LightVertexShader(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. output.normal = mul(input.normal, (float3x3)worldMatrix); // Normalize the normal vector. output.normal = normalize(output.normal); return output; }
//////////////////////////////////////////////////////////////////////////////// // Filename: light.ps //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType;
我们在 LightBuffer 中有两个新的全局变量,它们保存着漫反射光照的颜色和光的朝向。这两个变量将从新的 LightClass 对象中的值设置。
cbuffer LightBuffer { float4 diffuseColor; float3 lightDirection; float padding; }; ////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 LightPixelShader(PixelInputType input) : SV_TARGET { float4 textureColor; float3 lightDir; float lightIntensity; float4 color; // Sample the pixel color from the texture using the sampler at this texture coordinate location. textureColor = shaderTexture.Sample(SampleType, input.tex);
这是前面讨论的光照方程现在被实现的地方。光照强度值被计算为三角形法线向量和光照方向向量之间的点积。
// Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel. lightIntensity = saturate(dot(input.normal, lightDir));
最后,光的漫反射值与纹理像素值结合起来,产生颜色结果。
// Determine the final amount of diffuse color based on the diffuse color combined with the light intensity. color = saturate(diffuseColor * lightIntensity); // Multiply the texture pixel and the final diffuse color to get the final pixel color result. color = color * textureColor; return color; }
新的 LightShaderClass 只是上一个教程中 TextureShaderClass 的略微重写版本,以合并光照。
//////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTSHADERCLASS_H_ #define _LIGHTSHADERCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std; //////////////////////////////////////////////////////////////////////////////// // Class name: LightShaderClass //////////////////////////////////////////////////////////////////////////////// class LightShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; };
新的 LightBufferType 结构将用于保存光照信息。此 typedef 与像素着色器中的新 typedef 相同。请注意,我添加了一个额外的浮点数进行大小填充,以确保结构是 16 的倍数。由于没有额外浮点数的结构只有 28 个字节,如果我们使用 sizeof(LightBufferType),CreateBuffer 会失败,因为它需要大小是 16 的倍数才能成功。
struct LightBufferType { D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float padding; // Added extra padding so structure is a multiple of 16 for CreateBuffer function requirements. }; public: LightShaderClass(); LightShaderClass(const LightShaderClass&); ~LightShaderClass(); 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; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer;
有一个用于光照信息(颜色和朝向)的新私有常量缓冲区。光照缓冲区将被此类用于设置 HLSL 像素着色器内部的全局光照变量。
ID3D11Buffer* m_lightBuffer; }; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: lightshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightshaderclass.h" LightShaderClass::LightShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_sampleState = 0; m_matrixBuffer = 0;
在类构造函数中将新的光照常量缓冲区设置为 null。
m_lightBuffer = 0; } LightShaderClass::LightShaderClass(const LightShaderClass& other) { } LightShaderClass::~LightShaderClass() { } bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result;
新的 light.vs 和 light.ps HLSL 着色器文件被用作初始化光照着色器的输入。
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps"); if(!result) { return false; } return true; } void LightShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
Render 函数现在将光照方向和漫反射光照颜色作为输入。然后,这些变量被发送到 SetShaderParameters 函数,最后在着色器本身内部设置。
bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { bool result; // Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, diffuseColor); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; } bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer;
polygonLayout 变量已被更改为具有三个元素,而不是两个。这是为了它可以容纳布局中的法线向量。
D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC matrixBufferDesc;
我们还添加了一个新的描述变量,用于光照常量缓冲区。
D3D11_BUFFER_DESC lightBufferDesc; // Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0;
加载新的 light 顶点着色器。
// Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "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; }
加载新的 light 像素着色器。
// Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "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; } // 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;
着色器初始化的一个主要变化是这里在 polygonLayout 中。我们添加了用于光照计算的第三个元素,用于法线向量。语义名称是 NORMAL,格式是常规的 DXGI_FORMAT_R32G32B32_FLOAT,它处理法线向量的 x、y 和 z 的 3 个浮点数。该布局现在将与 HLSL 顶点着色器的预期输入匹配。
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; // 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; // 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 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; }
在这里,我们设置光照常量缓冲区描述,它将处理漫反射光照颜色和光照方向。注意常量缓冲区的大小,如果它们不是 16 的倍数,则需要在它们末尾添加额外的空间,否则 CreateBuffer 函数将失败。在本例中,常量缓冲区为 28 字节,填充 4 字节,使其变为 32 字节。
// Setup the description of the light dynamic constant buffer that is in the pixel shader. // Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail. 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; } void LightShaderClass::ShutdownShader() {
新的光照常量缓冲区在 ShutdownShader 函数中被释放。
// Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 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; } void LightShaderClass::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 函数现在将 lightDirection 和 diffuseColor 作为输入。
bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; unsigned int bufferNumber; MatrixBufferType* dataPtr; LightBufferType* dataPtr2; // 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); // Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture);
光照常量缓冲区的设置方式与矩阵常量缓冲区相同。我们首先锁定缓冲区并获取指向它的指针。之后,我们使用该指针设置漫反射光照颜色和光照方向。数据设置完成后,我们解锁缓冲区,然后将其设置在像素着色器中。注意,我们使用 PSSetConstantBuffers 函数而不是 VSSetConstantBuffers,因为这是我们要设置的像素着色器缓冲区。
// 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; dataPtr2->padding = 0.0f; // 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; } void LightShaderClass::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 triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
ModelClass 已被稍微修改以处理光照组件。
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "textureclass.h" //////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private:
VertexType 结构现在有一个法线向量,以适应光照。
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture;
D3DXVECTOR3 normal;
}; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D11Device*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView* GetTexture(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; TextureClass* m_Texture; }; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h" ModelClass::ModelClass() { m_vertexBuffer = 0; m_indexBuffer = 0; m_Texture = 0; } ModelClass::ModelClass(const ModelClass& other) { } ModelClass::~ModelClass() { } bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename) { bool result; // Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } // Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; } void ModelClass::Shutdown() { // Release the model texture. ReleaseTexture(); // Shutdown 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; } 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; }
InitializeBuffers 函数的唯一变化是这里在顶点设置中。每个顶点现在都有与之相关的法线,用于光照计算。法线是一条垂直于多边形面的线,以便可以计算出该面指向的确切方向。为了简单起见,我通过将每个 Z 组件设置为 -1.0f 来沿着 Z 轴设置每个顶点的法线,这使得法线指向观察者。
// 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[0].normal = D3DXVECTOR3(0.0f, 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[1].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f);
vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);
vertices[2].normal = D3DXVECTOR3(0.0f, 0.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 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; } 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; } 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; } void ModelClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
现在我们将看看新的光照类,它非常简单。它的目的仅仅是维护光的朝向和颜色。
//////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _LIGHTCLASS_H_ #define _LIGHTCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3dx10math.h> //////////////////////////////////////////////////////////////////////////////// // Class name: LightClass //////////////////////////////////////////////////////////////////////////////// class LightClass { public: LightClass(); LightClass(const LightClass&); ~LightClass(); void SetDiffuseColor(float, float, float, float); void SetDirection(float, float, float); D3DXVECTOR4 GetDiffuseColor(); D3DXVECTOR3 GetDirection(); private: D3DXVECTOR4 m_diffuseColor; D3DXVECTOR3 m_direction; }; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: lightclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "lightclass.h" LightClass::LightClass() { } LightClass::LightClass(const LightClass& other) { } LightClass::~LightClass() { } void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha) { m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha); return; } void LightClass::SetDirection(float x, float y, float z) { m_direction = D3DXVECTOR3(x, y, z); return; } D3DXVECTOR4 LightClass::GetDiffuseColor() { return m_diffuseColor; } D3DXVECTOR3 LightClass::GetDirection() { return m_direction; }
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h"
GraphicsClass 现在有两个新的包含,用于 LightShaderClass 和 LightClass。
#include "lightshaderclass.h" #include "lightclass.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:
现在 Render 函数接受浮点型数值作为输入。
bool Render(float);
private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model;
光照着色器和光照对象新增了两个私有变量。
LightShaderClass* m_LightShader; LightClass* m_Light;
}; #endif
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0;
光照着色器和光照对象在类构造函数中被设置为 null。
m_LightShader = 0; m_Light = 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; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; } // 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; }
此处创建并初始化新的光照着色器对象。
// Create the light shader object. m_LightShader = new LightShaderClass; if(!m_LightShader) { return false; } // Initialize the light shader object. result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the light 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, 0.0f, 1.0f, 1.0f); m_Light->SetDirection(0.0f, 0.0f, 1.0f);
return true; } void GraphicsClass::Shutdown() {
Shutdown 函数释放新的光照和光照着色器对象。
// Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the light shader object. if(m_LightShader) { m_LightShader->Shutdown(); delete m_LightShader; m_LightShader = 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::Frame() { bool result;
我们添加了一个新的静态变量,用于在每一帧中保存更新的旋转值,该值将传递到 Render 函数。
static float rotation = 0.0f; // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.01f; if(rotation > 360.0f) { rotation -= 360.0f; }
// Render the graphics scene.
result = Render(rotation);
if(!result) { return false; } return true; } bool GraphicsClass::Render(float rotation) { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix; 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 world, view, and projection matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix);
这里我们根据旋转值旋转世界矩阵,这样当我们使用更新后的世界矩阵渲染三角形时,它就会以旋转量旋转三角形。
// Rotate the world matrix by the rotation value so that the triangle will spin. 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 函数,以便着色器可以访问这些值。
// Render the model using the light shader. result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor()); if(!result) { return false; }
// Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
通过对代码进行一些修改,我们能够实现一些基本的定向光照。确保你理解法线向量是如何工作的,以及它们为什么对于计算多边形表面的光照很重要。请注意,旋转三角形的背面不会亮起来,因为我们在 D3DClass 中启用了背面剔除。
1. 重新编译项目,确保你得到一个被紫色光照亮的旋转纹理三角形。按 Esc 键退出。
2. 在像素着色器中注释掉 "color = color * textureColor;",这样 shaderTexture 就不会再被使用,你应该会看到没有纹理的光照效果。
3. 在 GraphicsClass 中的 m_Light->SetDiffuseColor 代码行将光照颜色更改为绿色。
4. 将光照方向更改为指向正 X 轴和负 X 轴方向。你可能还想更改旋转速度。