DirectX11 With Windows SDK--09 纹理映射与采样器状态
原文:DirectX11 With Windows SDK--09 纹理映射与采样器状态

前言

在之前的DirectX SDK中,纹理的读取使用的是D3DX11CreateShaderResourceViewFromFile函数,现在在Windows SDK中已经没有这些函数,我们需要找到DDSTextureLoaderWICTextureLoader这两个库来读取DDS位图和WIC位图

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

纹理坐标系

纹理坐标系和屏幕、图片坐标系的有些相似,它们的U轴都是水平朝右,V轴竖直向下。但是纹理的X和Y的取值范围都为[0.0, 1.0],分别映射到[0, Width][0, Height]

对于一个3D的三角形,通过给这三个顶点额外的纹理坐标信息,那么三个纹理坐标就可以映射到纹理指定的某片三角形区域。

这样的话已知三个顶点的坐标p0,p1p2以及三个纹理坐标q0,q1q2,就可以求出顶点坐标映射与纹理坐标的对应关系:

\[(x, y, z) = \mathbf{p_0} + s(\mathbf{p_1} - \mathbf{p_0}) + t(\mathbf{p_2} - \mathbf{p_0})\]

\[(u, v) = \mathbf{q_0} + s(\mathbf{q_1} - \mathbf{q_0}) + t(\mathbf{q_2} - \mathbf{q_0})\]

并且还需要满足\(s >= 0, t >= 0, s + t <= 1\)

所以顶点结构体的内容会有所变化:

struct VertexPosNormalTex
{DirectX::XMFLOAT3 pos;DirectX::XMFLOAT3 normal;DirectX::XMFLOAT2 tex;static const D3D11_INPUT_ELEMENT_DESC inputLayout[3];
};

对应的每个输入元素的描述为:

const D3D11_INPUT_ELEMENT_DESC VertexPosNormalTex::inputLayout[3] = {{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

纹理读取

DDS位图和WIC位图

DDS是一种图片格式,是DirectDraw Surface的缩写,它是DirectX纹理压缩(DirectX Texture Compression,简称DXTC)的产物。由NVIDIA公司开发。大部分3D游戏引擎都可以使用DDS格式的图片用作贴图,也可以制作法线贴图。

WIC(Windows Imaging Component)是一个可以扩展的平台,为数字图像提供底层API,它可以支持bmp、dng、ico、jpeg、png、tiff等格式的位图。

DDSTextureLoader和WICTextureLoader库

要使用这两个库,有两种方案。

第一种:在DirectXTex中找到DDSTextureLoader文件夹和WICTextureLoader文件夹中分别找到对应的头文件和源文件(不带12的),并加入到你的项目中

第二种:将DirectXTK库添加到你的项目中,这里不再赘述

这之后就可以包含DDSTextureLoader.hWICTextureLoader.h进项目中了。

CreateDDSTextureFromFile函数--从文件读取DDS纹理

现在读取DDS纹理的操作变得更简单了:

HRESULT CreateDDSTextureFromFile(ID3D11Device* d3dDevice,                // [In]D3D设备const wchar_t* szFileName,              // [In]dds图片文件名ID3D11Resource** texture,               // [Out]输出一个指向资源接口类的指针,也可以填nullptrID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也可以填nullptrsize_t maxsize = 0,                     // [In]忽略DDS_ALPHA_MODE* alphaMode = nullptr);  // [In]忽略

下面是一个调用的例子:

// 初始化木箱纹理
HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, m_pWoodCrate.GetAddressOf()));

CreateWICTextureFromFile函数--从文件读取WIC纹理

函数原型如下:

HRESULT CreateWICTextureFromFile(ID3D11Device* d3dDevice,                // [In]D3D设备const wchar_t* szFileName,              // [In]wic所支持格式的图片文件名ID3D11Resource** texture,               // [Out]输出一个指向资源接口类的指针,也可以填nullptrID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也可以填nullptrsize_t maxsize = 0);                     // [In]忽略

下面是一个调用的例子:

// 初始化火焰纹理
WCHAR strFile[40];
m_pFireAnims.resize(120);
for (int i = 1; i <= 120; ++i)
{wsprintf(strFile, L"Texture\\FireAnim\\Fire%03d.bmp", i);HR(CreateWICTextureFromFile(m_pd3dDevice.Get(), strFile, nullptr, m_pFireAnims[i - 1].GetAddressOf()));
}

这里我们只需要创建着色器资源视图,而不是纹理资源。原因在后面会提到。

过滤器

图片的放大

图片在经过放大操作后,除了图片原有的像素被拉伸,还需要对其余空缺的像素位置选用合适的方式来进行填充。比如一个2x2位图被拉伸成8x8的,除了角上4个像素,还需要对其余60个像素进行填充。下面介绍几种方法

常量插值法

对于2x2位图,它的宽高表示范围都为[0,1],而8x8位图的都为[0,7],且只允许取整数。那么对于放大后的像素点(1, 4)就会映射到(1/7, 4/7)上。

常量插值法的做法十分简单粗暴,就是对X和Y值都进行四舍五入操作,然后取邻近像素点的颜色。比如对于映射后的值如果落在[20.5, 21.5)的范围,最终都会取21。根据上面的例子,最终会落入到像素点(0, 1)上,然后取该像素点的颜色。

线性插值法

现在只讨论一维情况,已知第20个像素点的颜色p0和第21个像素点的颜色p1,并且经过拉伸放大后,有一个像素点落在范围(20, 21)之间,我们就可以使用线性插值法求出最终的颜色(t取(0,1)):

\[\mathbf{p} = t\mathbf{p_1} + (1 - t)\mathbf{p_0}\]

对于二维情况,会有三种使用线性插值法的情况:

  1. X方向使用常量插值法,Y方向使用线性插值法
  2. X方向使用线性插值法,Y方向使用常量插值法
  3. X和Y方向均使用线性插值法

左图使用了常量插值法,右图使用了二维线性插值法

图片的缩小

图片在经过缩小操作后,需要抛弃掉一些像素。但显然每次绘制都按实际宽高来进行缩小会对性能有很大影响。 在d3d中可以使用mipmapping技术,以额外牺牲一些内存代价的方式来获得高效的拟合效果。

这里估计使用的是金字塔下采样的原理。一张256x256的纹理,通过不断的向下采样,可以获得256x256、128x128、64x64...一直到1x1的一系列位图,这些位图构建了一条mipmap链,并且不同的纹理标注有不同的mipmap等级

其中mipmap等级为0的纹理即为原来的纹理,等级为1的纹理所占内存为等级为0的1/4,等级为2的纹理所占内存为等级为1的1/4...以此类推我们可以知道包含完整mipmap的纹理占用的内存空间不超过原来纹理的

\[\lim_{n \to +\infty} \frac{1(1-(\frac{1}{4})^n)}{1-\frac{1}{4}} = \frac{1}{1-\frac{1}{4}} = \frac{4}{3}\]

接下来会有两种情况:

  1. 选取mipmap等级对应图片和缩小后的图片大小最接近的一张,然后进行线性插值法或者常量插值法,这种方式叫做点过滤(point filtering)
  2. 选取两张mipmap等级相邻的图片,使得缩小后的图片大小在那两张位图之间,然后对这两张位图进行常量插值法或者线性插值法分别取得颜色结果,最后对两个颜色结果进行线性插值,这种方式叫做线性过滤(linear filtering)。

各向异性过滤

Anisotropic Filtering可以帮助我们处理那些不与屏幕平行的平面,需要额外使用平面的法向量和摄像机的观察方向向量。虽然使用该种过滤器会有比较大的性能损耗,但是能诞生出比较理想的效果。

下面左图使用了线性过滤法,右边使用的是各向异性过滤,可以看到顶面纹理比左边的更加清晰

对纹理进行采样

所谓采样,就是根据纹理坐标取出纹理对应位置最为接近的像素,在HLSL的写法如下:

g_Tex.Sample(g_SamLinear, pIn.Tex);

但大多数时候绘制出的纹理会比所用的纹理大或小,这样就还涉及到了采样器使用什么方式(如常量插值法、线性插值法、各向异性过滤)来处理图片放大、缩小的情况。

HLSL代码的变动

Basic.hlsli代码如下:

#include "LightHelper.hlsli"Texture2D gTex : register(t0);
SamplerState gSamLinear : register(s0);cbuffer VSConstantBuffer : register(b0)
{matrix g_World; matrix g_View;  matrix g_Proj;  matrix g_WorldInvTranspose;
}cbuffer PSConstantBuffer : register(b1)
{DirectionalLight g_DirLight[10];PointLight g_PointLight[10];SpotLight g_SpotLight[10];Material g_Material;int g_NumDirLight;int g_NumPointLight;int g_NumSpotLight;float g_Pad1;float3 g_EyePosW;float g_Pad2;
}struct VertexPosNormalTex
{float3 PosL : POSITION;float3 NormalL : NORMAL;float2 Tex : TEXCOORD;
};struct VertexPosTex
{float3 PosL : POSITION;float2 Tex : TEXCOORD;
};struct VertexPosHWNormalTex
{float4 PosH : SV_POSITION;float3 PosW : POSITION;     // 在世界中的位置float3 NormalW : NORMAL;    // 法向量在世界中的方向float2 Tex : TEXCOORD;
};struct VertexPosHTex
{float4 PosH : SV_POSITION;float2 Tex : TEXCOORD;
};

Basic_VS_2D.hlsl的代码:

// Basic_VS_2D.hlsl
#include "Basic.hlsli"// 顶点着色器(2D)
VertexPosHTex VS_2D(VertexPosTex vIn)
{VertexPosHTex vOut;vOut.PosH = float4(vIn.PosL, 1.0f);vOut.Tex = vIn.Tex;return vOut;
}

Basic_PS_2D.hlsl的代码:

// Basic_PS_2D.hlsl
#include "Basic.hlsli"// 像素着色器(2D)
float4 PS_2D(VertexPosHTex pIn) : SV_Target
{return g_Tex.Sample(g_SamLinear, pIn.Tex);
}

Basic_VS_3D.hlsl的代码:

// Basic_VS_3D.hlsl
#include "Basic.hlsli"// 顶点着色器(3D)
VertexPosHWNormalTex VS_3D(VertexPosNormalTex vIn)
{VertexPosHWNormalTex vOut;matrix viewProj = mul(g_View, g_Proj);float4 posW = mul(float4(vIn.PosL, 1.0f), g_World);vOut.PosH = mul(posW, viewProj);vOut.PosW = posW.xyz;vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);vOut.Tex = vIn.Tex;return vOut;
}

Basic_PS_3D.hlsl的代码:

// Basic_PS_3D.hlsl
#include "Basic.hlsli"// 像素着色器(3D)
float4 PS_3D(VertexPosHWNormalTex pIn) : SV_Target
{// 标准化法向量pIn.NormalW = normalize(pIn.NormalW);// 顶点指向眼睛的向量float3 toEyeW = normalize(g_EyePosW - pIn.PosW);// 初始化为0 float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);int i;// 强制展开循环以减少指令数[unroll]for (i = 0; i < g_NumDirLight; ++i){ComputeDirectionalLight(g_Material, g_DirLight[i], pIn.NormalW, toEyeW, A, D, S);ambient += A;diffuse += D;spec += S;}[unroll]for (i = 0; i < g_NumPointLight; ++i){ComputePointLight(g_Material, g_PointLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S);ambient += A;diffuse += D;spec += S;}[unroll]for (i = 0; i < g_NumSpotLight; ++i){ComputeSpotLight(g_Material, g_SpotLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S);ambient += A;diffuse += D;spec += S;}float4 texColor = g_Tex.Sample(g_SamLinear, pIn.Tex);float4 litColor = texColor * (ambient + diffuse) + spec;litColor.a = texColor.a * g_Material.Diffuse.a;return litColor;
}

其中Texture2D类型保存了2D纹理的信息,在这是全局变量。而register(t0)对应起始槽索引0.

SamplerState类型确定采样器应如何进行采样,同样也是全局变量,register(s0)对应起始槽索引0.

上述两种变量都需要在C++应用层中初始化和绑定后才能使用。

Texture2D类型拥有Sample方法,需要提供采样器状态和2D纹理坐标方可使用,然后返回一个包含RGBA信息的float4向量。

[unroll]属性用于展开循环,避免不必要的跳转,但可能会产生大量的指令

除此之外,上面的HLSL代码允许每种灯光最多10盏,然后还提供了2D和3D版本的顶点/像素着色器供使用。

注意Basic.hlsliLightHelper.hlsli是不参与生成的。其余着色器文件需要按照第2章的方式去设置好。

ID3D11DeviceContext::*SSetShaderResources方法--设置着色器资源

需要注意的是,纹理并不能直接绑定到着色器中,需要为纹理创建对应的着色器资源视图才能够给着色器使用。上面打*意味着渲染管线的所有可编程着色器阶段都有该方法。

此外,着色器资源视图不仅可以绑定纹理资源,还可以绑定缓冲区资源。有关缓冲区资源绑定到着色器资源视图的应用,我们留到后面的章节再讲。

目前在DDSTextureLoaderWICTextureLoader中,我们只需要用到纹理的着色器资源。这里以ID3D11DeviceContext::PSSetShaderResources为例:

void ID3D11DeviceContext::PSSetShaderResources(UINT StartSlot, // [In]起始槽索引,对应HLSL的register(t*)UINT NumViews,  // [In]着色器资源视图数目ID3D11ShaderResourceView * const *ppShaderResourceViews // [In]着色器资源视图数组
);

然后调用方法如下:

m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf());

这样在HLSL里对应regisgter(t0)g_Tex存放的就是木箱表面的纹理了。

ID3D11Device::CreateSamplerState方法--创建采样器状态

在C++代码层中,我们只能通过D3D设备创建采样器状态,然后绑定到渲染管线中,使得在HLSL中可以根据过滤器、寻址模式等进行采样。

在创建采样器状态之前,需要先填充结构体D3D11_SAMPLER_DESC来描述采样器状态:

typedef struct D3D11_SAMPLER_DESC
{D3D11_FILTER Filter;                    // 所选过滤器D3D11_TEXTURE_ADDRESS_MODE AddressU;    // U方向寻址模式D3D11_TEXTURE_ADDRESS_MODE AddressV;    // V方向寻址模式D3D11_TEXTURE_ADDRESS_MODE AddressW;    // W方向寻址模式FLOAT MipLODBias;   // mipmap等级偏移值,最终算出的mipmap等级会加上该偏移值UINT MaxAnisotropy;                     // 最大各向异性等级(1-16)D3D11_COMPARISON_FUNC ComparisonFunc;   // 这节不讨论FLOAT BorderColor[ 4 ];     // 边界外的颜色,使用D3D11_TEXTURE_BORDER_COLOR时需要指定FLOAT MinLOD;   // 若mipmap等级低于MinLOD,则使用等级MinLOD。最小允许设为0FLOAT MaxLOD;   // 若mipmap等级高于MaxLOD,则使用等级MaxLOD。必须比MinLOD大
}   D3D11_SAMPLER_DESC;

D3D11_FILTER部分枚举含义如下:

枚举值 缩小 放大 mipmap
D3D11_FILTER_MIN_MAG_MIP_POINT 点采样 点采样 点采样
D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR 点采样 点采样 线性采样
D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT 点采样 线性采样 点采样
D3D11_FILTER_MIN_MAG_MIP_LINEAR 线性采样 线性采样 线性采样
D3D11_FILTER_ANISOTROPIC 各向异性 各向异性 各向异性

D3D11_TEXTURE_ADDRESS_MODE是单个方向的寻址模式,有时候纹理坐标会超过1.0或者小于0.0,这时候寻址模式可以解释边界外的情况,含义如下:

D3D11_TEXTURE_ADDRESS_WRAP使用重复的纹理去覆盖整个实数域,可以当做fmod(X, 1.0f)

D3D11_TEXTURE_ADDRESS_MIRROR用重复的纹理覆盖整个实数域,只不过相邻的两个纹理满足按交线对称

D3D11_TEXTURE_ADDRESS_CLAMP对U轴和V轴,小于0的值都取作0,大于1的值都取作1

D3D11_TEXTURE_BORDER_COLOR对于纹理范围外的区域都使用BorderColor进行填充

D3D11_TEXTURE_ADDRESS_MIRROR_ONCE相当于MIRROR和CLAMP的结合,仅[-1,1]的范围内镜像有效,其余范围都会取到-1或者1

最后就是ID3D11Device::CreateSamplerState方法:

HRESULT ID3D11Device::CreateSamplerState( const D3D11_SAMPLER_DESC *pSamplerDesc, // [In]采样器状态描述ID3D11SamplerState **ppSamplerState);   // [Out]输出的采样器

接下来演示了如何创建采样器状态;

// 初始化采样器状态描述
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;
HR(m_pd3dDevice->CreateSamplerState(&sampDesc, m_pSamplerState.GetAddressOf()));

ID3D11DeviceContext::PSSetSamplers方法--像素着色阶段设置采样器状态

void ID3D11DeviceContext::PSSetSamplers(UINT StartSlot,     // [In]起始槽索引UINT NumSamplers,   // [In]采样器状态数目ID3D11SamplerState * const * ppSamplers);   // [In]采样器数组  

根据前面的HLSL代码,samLinear使用了索引为0起始槽,所以需要这样调用:

// 像素着色阶段设置好采样器
m_pd3dImmediateContext->PSSetSamplers(0, 1, m_pSamplerState.GetAddressOf());

这样HLSL中对应的采样器状态就可以使用了。

GameApp类的变动

GameApp::InitEffect的变动

现在我们需要编译出4个着色器,2个顶点布局,以区分2D和3D的部分。

bool GameApp::InitEffect()
{ComPtr<ID3DBlob> blob;// 创建顶点着色器(2D)HR(CreateShaderFromFile(L"HLSL\\Basic_VS_2D.cso", L"HLSL\\Basic_VS_2D.hlsl", "VS_2D", "vs_5_0", blob.ReleaseAndGetAddressOf()));HR(m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader2D.GetAddressOf()));// 创建顶点布局(2D)HR(m_pd3dDevice->CreateInputLayout(VertexPosTex::inputLayout, ARRAYSIZE(VertexPosTex::inputLayout),blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout2D.GetAddressOf()));// 创建像素着色器(2D)HR(CreateShaderFromFile(L"HLSL\\Basic_PS_2D.cso", L"HLSL\\Basic_PS_2D.hlsl", "PS_2D", "ps_5_0", blob.ReleaseAndGetAddressOf()));HR(m_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pPixelShader2D.GetAddressOf()));// 创建顶点着色器(3D)HR(CreateShaderFromFile(L"HLSL\\Basic_VS_3D.cso", L"HLSL\\Basic_VS_3D.hlsl", "VS_3D", "vs_5_0", blob.ReleaseAndGetAddressOf()));HR(m_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pVertexShader3D.GetAddressOf()));// 创建顶点布局(3D)HR(m_pd3dDevice->CreateInputLayout(VertexPosNormalTex::inputLayout, ARRAYSIZE(VertexPosNormalTex::inputLayout),blob->GetBufferPointer(), blob->GetBufferSize(), m_pVertexLayout3D.GetAddressOf()));// 创建像素着色器(3D)HR(CreateShaderFromFile(L"HLSL\\Basic_PS_3D.cso", L"HLSL\\Basic_PS_3D.hlsl", "PS_3D", "ps_5_0", blob.ReleaseAndGetAddressOf()));HR(m_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, m_pPixelShader3D.GetAddressOf()));return true;
}

GameApp::InitResource的变化

虽然现在允许同时放入多盏灯光了,但在该项目我们只使用一盏点光灯,并且仅用于3D木盒的显示。对于2D火焰动画,实质上是由120张bmp位图构成,我们需要按顺序在每一帧切换下一张位图来达到火焰在动的效果。

bool GameApp::InitResource()
{// 初始化网格模型并设置到输入装配阶段auto meshData = Geometry::CreateBox();ResetMesh(meshData);// ******************// 设置常量缓冲区描述D3D11_BUFFER_DESC cbd;ZeroMemory(&cbd, sizeof(cbd));cbd.Usage = D3D11_USAGE_DYNAMIC;cbd.ByteWidth = sizeof(VSConstantBuffer);cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;// 新建用于VS和PS的常量缓冲区HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[0].GetAddressOf()));cbd.ByteWidth = sizeof(PSConstantBuffer);HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffers[1].GetAddressOf()));// ******************// 初始化纹理和采样器状态// 初始化木箱纹理HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, m_pWoodCrate.GetAddressOf()));// 初始化火焰纹理WCHAR strFile[40];m_pFireAnims.resize(120);for (int i = 1; i <= 120; ++i){wsprintf(strFile, L"Texture\\FireAnim\\Fire%03d.bmp", i);HR(CreateWICTextureFromFile(m_pd3dDevice.Get(), strFile, nullptr, m_pFireAnims[i - 1].GetAddressOf()));}// 初始化采样器状态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;HR(m_pd3dDevice->CreateSamplerState(&sampDesc, m_pSamplerState.GetAddressOf()));// ******************// 初始化常量缓冲区的值// 初始化用于VS的常量缓冲区的值m_VSConstantBuffer.world = XMMatrixIdentity();          m_VSConstantBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH(XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)));m_VSConstantBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f));m_VSConstantBuffer.worldInvTranspose = XMMatrixIdentity();// 初始化用于PS的常量缓冲区的值// 这里只使用一盏点光来演示m_PSConstantBuffer.pointLight[0].Position = XMFLOAT3(0.0f, 0.0f, -10.0f);m_PSConstantBuffer.pointLight[0].Ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);m_PSConstantBuffer.pointLight[0].Diffuse = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);m_PSConstantBuffer.pointLight[0].Specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);m_PSConstantBuffer.pointLight[0].Att = XMFLOAT3(0.0f, 0.1f, 0.0f);m_PSConstantBuffer.pointLight[0].Range = 25.0f;m_PSConstantBuffer.numDirLight = 0;m_PSConstantBuffer.numPointLight = 1;m_PSConstantBuffer.numSpotLight = 0;m_PSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f);  // 这里容易遗漏,已补上// 初始化材质m_PSConstantBuffer.material.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);m_PSConstantBuffer.material.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);m_PSConstantBuffer.material.Specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 5.0f);// 注意不要忘记设置此处的观察位置,否则高亮部分会有问题m_PSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f);// 更新PS常量缓冲区资源D3D11_MAPPED_SUBRESOURCE mappedData;HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[1].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));memcpy_s(mappedData.pData, sizeof(PSConstantBuffer), &m_PSConstantBuffer, sizeof(PSConstantBuffer));m_pd3dImmediateContext->Unmap(m_pConstantBuffers[1].Get(), 0);// ******************// 给渲染管线各个阶段绑定好所需资源// 设置图元类型,设定输入布局m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout3D.Get());// 默认绑定3D着色器m_pd3dImmediateContext->VSSetShader(m_pVertexShader3D.Get(), nullptr, 0);// VS常量缓冲区对应HLSL寄存于b0的常量缓冲区m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffers[0].GetAddressOf());// PS常量缓冲区对应HLSL寄存于b1的常量缓冲区m_pd3dImmediateContext->PSSetConstantBuffers(1, 1, m_pConstantBuffers[1].GetAddressOf());// 像素着色阶段设置好采样器m_pd3dImmediateContext->PSSetSamplers(0, 1, m_pSamplerState.GetAddressOf());m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf());m_pd3dImmediateContext->PSSetShader(m_pPixelShader3D.Get(), nullptr, 0);// 像素着色阶段默认设置木箱纹理m_CurrMode = ShowMode::WoodCrate;return true;
}

GameApp::UpdateScene的变化

该项目可以选择播放3D木箱或2D火焰动画,则需要有个当前正在播放内容的状态值,并随之进行更新。

其中ShowMode是一个枚举类,可以选择WoodCrate或者FireAnim

void GameApp::UpdateScene(float dt)
{Keyboard::State state = m_pKeyboard->GetState();m_KeyboardTracker.Update(state);    // 键盘切换模式if (m_KeyboardTracker.IsKeyPressed(Keyboard::D1)){// 播放木箱动画m_CurrMode = ShowMode::WoodCrate;m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout3D.Get());auto meshData = Geometry::CreateBox();ResetMesh(meshData);m_pd3dImmediateContext->VSSetShader(m_pVertexShader3D.Get(), nullptr, 0);m_pd3dImmediateContext->PSSetShader(m_pPixelShader3D.Get(), nullptr, 0);m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pWoodCrate.GetAddressOf());}else if (m_KeyboardTracker.IsKeyPressed(Keyboard::D2)){m_CurrMode = ShowMode::FireAnim;m_CurrFrame = 0;m_pd3dImmediateContext->IASetInputLayout(m_pVertexLayout2D.Get());auto meshData = Geometry::Create2DShow();ResetMesh(meshData);m_pd3dImmediateContext->VSSetShader(m_pVertexShader2D.Get(), nullptr, 0);m_pd3dImmediateContext->PSSetShader(m_pPixelShader2D.Get(), nullptr, 0);m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFireAnims[0].GetAddressOf());}if (m_CurrMode == ShowMode::WoodCrate){static float phi = 0.0f, theta = 0.0f;phi += 0.00003f, theta += 0.00005f;XMMATRIX W = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);m_VSConstantBuffer.world = XMMatrixTranspose(W);m_VSConstantBuffer.worldInvTranspose = XMMatrixInverse(nullptr, W); // 两次转置抵消// 更新常量缓冲区,让立方体转起来D3D11_MAPPED_SUBRESOURCE mappedData;HR(m_pd3dImmediateContext->Map(m_pConstantBuffers[0].Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));memcpy_s(mappedData.pData, sizeof(VSConstantBuffer), &m_VSConstantBuffer, sizeof(VSConstantBuffer));m_pd3dImmediateContext->Unmap(m_pConstantBuffers[0].Get(), 0);}else if (m_CurrMode == ShowMode::FireAnim){// 用于限制在1秒60帧static float totDeltaTime = 0;totDeltaTime += dt;if (totDeltaTime > 1.0f / 60){totDeltaTime -= 1.0f / 60;m_CurrFrame = (m_CurrFrame + 1) % 120;m_pd3dImmediateContext->PSSetShaderResources(0, 1, m_pFireAnims[m_CurrFrame].GetAddressOf());}       }
}

最终的显示效果如下:

练习题

粗体字为自定义题目

  1. 自己动手将过滤器修改为常量插值法、线性插值法、各向异性过滤,观察立方体盒的效果
  2. 尝试在使用Geometry::MeshData创建的立方体网格数据(不能对其修改)的基础上,让立方体的六个面使用不同的纹理来绘制,可以使用魔方项目里的纹理
  3. 使用教程项目第26章Texture文件夹中的flare.dds和flarealpha.dds,在着色器中通过分量乘法实现

    然后让纹理在立方体的表面旋转(考虑对纹理坐标的变换),纹理采样器的寻址模式使用BORDER_COLOR,边界色为黑色。效果如下:

    着色器需要提供两个纹理,并在一个顶点着色器、一个像素着色器完成任务。
  4. 如果你阅读过"深入理解与使用2D纹理资源"这篇,那么尝试用一个纹理数组存储所有的火焰纹理,在HLSL中使用Texture2DArray

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

posted on 2019-05-05 09:53 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/10811350.html

DirectX11 With Windows SDK--09 纹理映射与采样器状态相关推荐

  1. DirectX11 With Windows SDK--22 立方体映射:静态天空盒的读取与实现

    前言 这一章我们主要学习由6个纹理所构成的立方体映射,以及用它来实现一个静态天空盒. 但是在此之前先要消除两个误区: 认为这一章的天空盒就是简单的在一个超大立方体的六个面内部贴上天空盒纹理: 认为天空 ...

  2. DirectX11 With Windows SDK--01 DirectX11初始化

    DirectX11 With Windows SDK--01 DirectX11初始化 原文:DirectX11 With Windows SDK--01 DirectX11初始化 前言 由于个人觉得 ...

  3. DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果

    DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果 原文:DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果 前言 上一 ...

  4. DirectX11 With Windows SDK--12 深度/模板状态、平面镜反射绘制

    前言 深度/模板测试使用的是与后备缓冲区同等分辨率大小的缓冲区,每个元素的一部分连续位用于深度测试,其余的则用作模板测试.两个测试的目的都是为了能够根据深度/模板状态需求的设置来选择需要绘制的像素. ...

  5. DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

    DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 ...

  6. DirectX11 With Windows SDK--15 几何着色器初探

    DirectX11 With Windows SDK--15 几何着色器初探 原文:DirectX11 With Windows SDK--15 几何着色器初探 前言 从这一部分开始,感觉就像是踏入了 ...

  7. DirectX11 With Windows SDK--36 延迟渲染基础

    前言 随着图形硬件变得越来越通用和可编程化,采用实时3D图形渲染的应用程序已经开始探索传统渲染管线的替代方案,以避免其缺点.其中一项最流行的技术就是所谓的延迟渲染.这项技术主要是为了支持大量的动态灯光 ...

  8. DirectX11 With Windows SDK--32 SSAO(屏幕空间环境光遮蔽)

    前言 由于性能的限制,实时光照模型往往会忽略间接光因素(即场景中其他物体所反弹的光线).但在现实生活中,大部分光照其实是间接光.在第7章里面的光照方程里面引入了环境光项: C a = A L ⊗ m ...

  9. DirectX11 With Windows SDK--11 混合状态

    文章目录 前言 混合等式 混合等式 混合状态 混合运算符的设置 混合因子的设置 ID3D11Device::CreateBlendState方法--创建混合状态 ID3D11DeviceContext ...

最新文章

  1. R语言ggplot2可视化:ggplot2可视化水平堆叠条形图、并且在每个堆叠条形图的内部居中添加百分比文本标签信息
  2. asp.net中读取带有加号(+)的Cookie,会自动把加号替换为空格
  3. SQL盲注工具BBQSQL
  4. python 条件语句漫画解析_【Python】解析Python中的条件语句和循环语句
  5. B-Trees Concepts B-树介绍(都快忘了:))
  6. 【Java线程】线程协作实现多对多聊天
  7. 网络安全分析和公有云使用的安全性
  8. [html] 说说你对短链接的理解,它有什么应用场景呢?
  9. Chess Queen【数学】
  10. 大话Linux内核中锁机制之原子操作、自旋锁【转】
  11. 寻找linux最新版本,在各大Linux发行版中安装和使用inxi以查找Linux系统详细信息...
  12. 深入解读Docker底层技术Cgroup系列(5)——cgroup子系统cpuset
  13. linkinfo.dll 病毒,烦!
  14. Linux du命令详解
  15. python实训day8
  16. http://blog.sina.com.cn/s/blog_ad1c3bdf0102uz99.html
  17. CHROME扩展笔记之设置代理(项目完成再上完整案例)
  18. 支付FM——云闪付收款码配置教程
  19. 北航宇航学院计算机,2014年北航自动化考生倾情奉献的总结(也试用于宇航学院)...
  20. 软件工程复习13:软件发布

热门文章

  1. L2-021 点赞狂魔 (25分)
  2. 跨境电商亚马逊无货源模式基本问题总结汇总
  3. 关于 iptables 和 tc 的限速理解
  4. Alienware 17R5 Ubuntu 16.04显卡驱动
  5. 用r语言对优惠券推荐网站数据LDA文本挖掘
  6. 二进制数的补码及运算(2)
  7. 计算机一级光盘安装图,深度光盘安装系统图解教程
  8. 中国佛学66句震撼世界的禅语
  9. ubuntu无法修改分辨率的解决办法
  10. android SD卡检测和热插拔功能