在以前的DirectX SDK中,纹理的读取使用的是D3DX11CreateShaderResourceViewFromFile
函数,如今在Windows SDK中已经没有这些函数,咱们须要找到DDSTextureLoader
和WICTextureLoader
这两个库来读取DDS位图和WIC位图html
DirectX11 With Windows SDK完整目录git
Github项目源码github
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。编程
纹理坐标系和屏幕、图片坐标系的有些类似,它们的U轴都是水平朝右,V轴竖直向下。可是纹理的X和Y的取值范围都为[0.0, 1.0]
,分别映射到[0, Width]
和[0, Height]
数组
对于一个3D的三角形,经过给这三个顶点额外的纹理坐标信息,那么三个纹理坐标就能够映射到纹理指定的某片三角形区域。app
这样的话已知三个顶点的坐标p0
,p1
和p2
以及三个纹理坐标q0
,q1
和q2
,就能够求出顶点坐标映射与纹理坐标的对应关系:函数
\[(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是一种图片格式,是DirectDraw Surface的缩写,它是DirectX纹理压缩(DirectX Texture Compression,简称DXTC)的产物。由NVIDIA公司开发。大部分3D游戏引擎均可以使用DDS格式的图片用做贴图,也能够制做法线贴图。
WIC(Windows Imaging Component)是一个能够扩展的平台,为数字图像提供底层API,它能够支持bmp、dng、ico、jpeg、png、tiff等格式的位图。
要使用这两个库,有两种方案。
第一种:在DirectXTex中找到DDSTextureLoader
文件夹和WICTextureLoader
文件夹中分别找到对应的头文件和源文件(不带12的),并加入到你的项目中
第二种:将DirectXTK库添加到你的项目中,这里再也不赘述
这以后就能够包含DDSTextureLoader.h
和WICTextureLoader.h
进项目中了。
如今读取DDS纹理的操做变得更简单了:
HRESULT CreateDDSTextureFromFile( ID3D11Device* d3dDevice, // [In]D3D设备 const wchar_t* szFileName, // [In]dds图片文件名 ID3D11Resource** texture, // [Out]输出一个指向资源接口类的指针,也能够填nullptr ID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也能够填nullptr size_t maxsize = 0, // [In]忽略 DDS_ALPHA_MODE* alphaMode = nullptr); // [In]忽略
下面是一个调用的例子:
// 初始化木箱纹理 HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, m_pWoodCrate.GetAddressOf()));
函数原型以下:
HRESULT CreateWICTextureFromFile( ID3D11Device* d3dDevice, // [In]D3D设备 const wchar_t* szFileName, // [In]wic所支持格式的图片文件名 ID3D11Resource** texture, // [Out]输出一个指向资源接口类的指针,也能够填nullptr ID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也能够填nullptr size_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}\]
对于二维状况,会有三种使用线性插值法的状况:
下图展现了双线性插值法的过程,已知4个相邻像素点,当前采样的纹理坐标在这四个点内,则首先根据x方向的纹理坐标进行线性插值,而后根据y方向的纹理坐标再进行一遍线性插值:
而下图则演示了两种插值法的效果,其中左边使用了常量插值法,右边使用了二维线性插值法
图片在通过缩小操做后,须要抛弃掉一些像素。但显然每次绘制都按实际宽高来进行缩小会对性能有很大影响。 在d3d中可使用mipmapping技术,以额外牺牲一些内存代价的方式来得到高效的拟合效果。
这里估计使用的是金字塔下采样的原理。一张256x256的纹理,经过不断的向下采样,能够得到256x25六、128x12八、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}\]
接下来会有两种状况:
Anisotropic Filtering能够帮助咱们处理那些不与屏幕平行的平面,须要额外使用平面的法向量和摄像机的观察方向向量。虽然使用该种过滤器会有比较大的性能损耗,可是能诞生出比较理想的效果。
下面左图使用了线性过滤法,右边使用的是各向异性过滤,能够看到顶面纹理比左边的更加清晰
所谓采样,就是根据纹理坐标取出纹理对应位置最为接近的像素,在HLSL的写法以下:
g_Tex.Sample(g_SamLinear, pIn.Tex);
但大多数时候绘制出的纹理会比所用的纹理大或小,这样就还涉及到了采样器使用什么方式(如常量插值法、线性插值法、各向异性过滤)来处理图片放大、缩小的状况。
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.hlsli
和LightHelper.hlsli
是不参与生成的。其他着色器文件须要按照第2章的方式去设置好。
须要注意的是,纹理并不能直接绑定到着色器中,须要为纹理建立对应的着色器资源视图才可以给着色器使用。上面打*意味着渲染管线的全部可编程着色器阶段都有该方法。
此外,着色器资源视图不只能够绑定纹理资源,还能够绑定缓冲区资源。有关缓冲区资源绑定到着色器资源视图的应用,咱们留到后面的章节再讲。
目前在DDSTextureLoader
和WICTextureLoader
中,咱们只须要用到纹理的着色器资源。这里以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
存放的就是木箱表面的纹理了。
在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。最小容许设为0 FLOAT 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()));
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中对应的采样器状态就可使用了。
如今咱们须要编译出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; }
虽然如今容许同时放入多盏灯光了,但在该项目咱们只使用一盏点光灯,而且仅用于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; }
该项目能够选择播放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()); } } }
最终的显示效果以下:
粗体字为自定义题目
Geometry::MeshData
建立的立方体网格数据(不能对其修改)的基础上,让立方体的六个面使用不一样的纹理来绘制,可使用魔方项目里的纹理Texture2DArray
DirectX11 With Windows SDK完整目录
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。