11.纹理
本文实现从文件加载纹理并将其映射到几何图形!
源码下载地址:
- https://www.braynzarsoft.net/file/11
- DX11_Lesson_11_Textures_zip.zip
介绍
在这里,我们将学习如何将纹理映射到我们的对象。
在 Direct3D 中,我们使用 2D (u,v) 坐标系将纹理映射到对象上。
u 轴水平延伸到图像,v 轴垂直延伸,其中 u 为 0-1(0 表示图像长度的起点,1 表示图像长度的终点)。因此,即使实际图像长度为 256 像素,图像水平长度的一半也是 0.5。
现在,如果我们将 u 和 v 的值改为大于 1,会发生什么?比如说 2?你猜对了!它将重复纹理,如下所示。
变量声明
第一个新接口是一个对象,它将保存我们从文件加载的纹理。第二个接口将保存我们的采样器状态信息。
ID3D11ShaderResourceView* CubesTexture;
ID3D11SamplerState* CubesTexSamplerState;
顶点结构/输入布局
我们看一下我们的顶点结构。我们删除了颜色成员,并用纹理坐标成员替换它。2D 纹理的纹理坐标只需要 u 和 v 值,如果对于使用 3D 纹理作为天空图,它使用额外的“w”值。
我们还修改了输入布局,为纹理坐标包含两个浮点元素,它替换了我们的颜色元素。
struct Vertex //Overloaded Vertex Structure
{
Vertex(){}
Vertex(float x, float y, float z,
float u, float v)
: pos(x,y,z), texCoord(u, v){}
XMFLOAT3 pos;
XMFLOAT2 texCoord;
};
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
顶点结构/缓冲区、索引列表
如果我们不为每个三角形顶点单独设置纹理坐标,则只有顶部和底部可能被正确映射,这就是为什么我们为立方体中的每个三角形添加顶点,以便我们可以设置每个顶点的纹理坐标。您可以尝试仅使用我们在上一课中使用的 8 个顶点来设置纹理坐标,但就像我说的那样,纹理将无法在所有六个侧面正确映射。
由于我们添加了更多顶点,我们还需要更新顶点缓冲区以保存 24 个顶点,而不是之前的 8 个。
Vertex v[] =
{
// Front Face
Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f),
Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f),
Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 0.0f),
Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 1.0f),
// Back Face
Vertex(-1.0f, -1.0f, 1.0f, 1.0f, 1.0f),
Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 1.0f),
Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 0.0f),
Vertex(-1.0f, 1.0f, 1.0f, 1.0f, 0.0f),
// Top Face
Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f),
Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 0.0f),
Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f),
Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 1.0f),
// Bottom Face
Vertex(-1.0f, -1.0f, -1.0f, 1.0f, 1.0f),
Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 1.0f),
Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 0.0f),
Vertex(-1.0f, -1.0f, 1.0f, 1.0f, 0.0f),
// Left Face
Vertex(-1.0f, -1.0f, 1.0f, 0.0f, 1.0f),
Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 0.0f),
Vertex(-1.0f, 1.0f, -1.0f, 1.0f, 0.0f),
Vertex(-1.0f, -1.0f, -1.0f, 1.0f, 1.0f),
// Right Face
Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 1.0f),
Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 0.0f),
Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f),
Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 1.0f),
};
DWORD indices[] = {
// Front Face
0, 1, 2,
0, 2, 3,
// Back Face
4, 5, 6,
4, 6, 7,
// Top Face
8, 9, 10,
8, 10, 11,
// Bottom Face
12, 13, 14,
12, 14, 15,
// Left Face
16, 17, 18,
16, 18, 19,
// Right Face
20, 21, 22,
20, 22, 23
};
D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(DWORD) * 12 * 3;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &squareIndexBuffer);
d3d11DevCon->IASetIndexBuffer( squareIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 24;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
从文件加载纹理
在我们的初始化场景函数的底部,我们使用函数 D3DX11CreateShaderResourceViewFromFile() 从文件中加载纹理,该函数定义如下:
HRESULT D3DX11CreateShaderResourceViewFromFile(
__in ID3D11Device *pDevice,
__in LPCTSTR pSrcFile,
__in D3DX11_IMAGE_LOAD_INFO *pLoadInfo,
__in ID3DX11ThreadPump *pPump,
__out ID3D11ShaderResourceView **ppShaderResourceView,
__out HRESULT *pHResult
);
其中每个参数的描述如下:
- pDevice—— 指向我们的 D3D 设备的指针。
- pSrcFile—— 我们的文件的名称(如果它与 exe 不在同一文件夹中,则为位置)。
- pLoadInfo - 指向 D3DX11_IMAGE_LOAD_INFO 结构的指针,该结构定义应如何加载纹理。我们可以将其设置为 NULL。
- pPump - 指向 ID3DX11ThreadPump 接口的指针,仅在我们想要多线程时使用,并且即使在加载此文件时也让程序继续运行。在此处输入 NULL 会使此函数仅在完成时返回。
- ppShaderResourceView - 这是一个指向着色器资源视图 (ID3D11ShaderResourceView) 的指针,它将保存来自此纹理的数据。
- pHResult - 这是返回的指针,用于存储此函数的结果。我们之前讨论过 HRESULT。
hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, L"braynzar.jpg",
NULL, NULL, &CubesTexture, NULL );
描述采样状态
这里我们可以描述采样器状态,或者着色器如何渲染纹理。我们创建一个 D3D11_SAMPLER_DESC 对象:
typedef struct D3D11_SAMPLER_DESC {
D3D11_FILTER Filter;
D3D11_TEXTURE_ADDRESS_MODE AddressU;
D3D11_TEXTURE_ADDRESS_MODE AddressV;
D3D11_TEXTURE_ADDRESS_MODE AddressW;
FLOAT MipLODBias;
UINT MaxAnisotropy;
D3D11_COMPARISON_FUNC ComparisonFunc;
FLOAT BorderColor[4];
FLOAT MinLOD;
FLOAT MaxLOD;
} D3D11_SAMPLER_DESC;
其中每个成员的描述如下:
- Filter - 一个 D3D11_FILTER 枚举类型,描述要使用的过滤方法。
- AddressU - 一个 D3D11_TEXTURE_ADDRESS_MODE 枚举类型,描述如果 u 值大于 1 或小于 0 时该怎么做。
- AddressV - 一个 D3D11_TEXTURE_ADDRESS_MODE 枚举类型,描述如果 v 值大于 1 或小于 0 时该怎么做。
- AddressW - 一个 D3D11_TEXTURE_ADDRESS_MODE 枚举类型,描述如果 w 值大于 1 或小于 0 时该怎么做。
MipLODBias - 与计算出的 mipmap 级别的偏移。例如,如果 Direct3D 计算出纹理应在 mipmap 级别 3 处采样,而 MipLODBias 为 2,则该纹理将在 mipmap 级别 5 处采样。
MaxAnisotropy - 如果在 Filter 中指定了 D3D11_FILTER_ANISOTROPIC 或 D3D11_FILTER_COMPARISON_ANISOTROPIC,则使用钳制值。有效值介于 1 到 16 之间。
ComparisonFunc - 枚举类型 D3D11_COMPARISON_FUNC 。这将比较此纹理的采样 mipmap 数据与另一个 mipmaps 采样数据。
- BorderColor[4] - 如果为 AddressU、V 或 W 中的任意一个指定了 D3D11_TEXTURE_ADDRESS_BORDER,那么如果 u、v 或 w 大于 1 或小于 0,则这是纹理和三角形边缘之间的空间的颜色。
- MinLOD - 这是要使用的最低 mipmap 级别,其中 0 表示最详细且最大的级别。
- MaxLOD - 这是要使用的最大 mipmap 级别,其中 0 表示最详细且最大。要使用所有 mipmap,您需要指定一个非常大的数字,例如 FLT_MAX。
如果任何成员未填写,则将使用默认值:
Filter MIN_MAG_MIP_LINEAR
AddressU Clamp
AddressV Clamp
AddressW Clamp
MinLOD -3.402823466e+38F (-FLT_MAX)
MaxLOD 3.402823466e+38F (FLT_MAX)
MipMapLODBias 0.0f
MaxAnisotropy 16
ComparisonFunc Never
BorderColor float4(0.0f,0.0f,0.0f,0.0f)
D3D11_SAMPLER_DESC sampDesc;
ZeroMemory( &sampDesc, sizeof(sampDesc) );
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampDesc.MinLOD = 0;
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
顶点结构/输入布局
描述完采样器后,我们需要创建它。我们可以通过使用 CreateSamplerState() 方法来实现这一点,其中第一个参数是我们描述的采样器状态,第二个参数是将采样器状态放入的接口。
hr = d3d11Device->CreateSamplerState( &sampDesc, &CubesTexSamplerState );
将采样器状态和纹理发送到着色器
如果我们愿意,我们只需要对使用相同采样器状态和纹理的每组对象将采样器状态和纹理发送到像素着色器一次,但很多时候对象会有多个纹理,因此您无法对每个对象仅设置一次。无论如何,由于 PS 将使用此信息,我们将把它发送到那里。我们可以通过调用方法 ID3D11DeviceContext::PSSetShaderResources() 和 ID3D11DeviceContext::PSSetSamplers() 来执行此操作。
PSSetShaderResources() 方法的第一个参数是我们将着色器发送到的槽号。第二个参数是纹理数组中的元素数。我们将其设置为 1,因为我们只发送一个纹理,但实际上我们可以将一个纹理数组发送到着色器。第三个参数是 ID3D11ShaderResourceView 数组。
PSSetSamplers 类似,只不过我们发送的不是 ID3D11ShaderResourceView 数组,而是 ID3D11SamplerState 数组。不过我们只有一个采样器状态,因此只需发送一个即可。
void DrawScene()
{
//Clear our backbuffer
float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);
//Refresh the Depth/Stencil view
d3d11DevCon->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);
//Set the WVP matrix and send it to the constant buffer in effect file
WVP = cube1World * camView * camProjection;
cbPerObj.WVP = XMMatrixTranspose(WVP);
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
///////////////**************new**************////////////////////
d3d11DevCon->PSSetShaderResources( 0, 1, &CubesTexture );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );
///////////////**************new**************////////////////////
//Draw the first cube
d3d11DevCon->DrawIndexed( 36, 0, 0 );
WVP = cube2World * camView * camProjection;
cbPerObj.WVP = XMMatrixTranspose(WVP);
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
///////////////**************new**************////////////////////
d3d11DevCon->PSSetShaderResources( 0, 1, &CubesTexture );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );
///////////////**************new**************////////////////////
//Draw the second cube
d3d11DevCon->DrawIndexed( 36, 0, 0 );
//Present the backbuffer to the screen
SwapChain->Present(0, 0);
}