本来说这节打算自己搞一个D3D框架的,但是跟了外网的教程走了半个月后发现Chili的框架对于我这种初学者而言太过于事无巨细,做了很多framework,如果只想学习d3d api以及用其要达到什么效果的话,有些“重量级”,所以我打算两手抓了,相比沉浸在c++新特性和指针、模版里,我相信龙书的教程更有指向性。拿龙书与Chili的教程相互比较学习,还是不错的,有了Chili的基础,再看龙书的代码感觉就快多了。
读了下教程里整理的简易框架
在常量缓冲区和其集合里各设置了一个BOOL值,当Set数值修改后就修改BOOL,update时判断BOOL再进行Map,用来减少操作。
void GameObject::Draw(ID3D11DeviceContext * deviceContext, BasicEffect& effect)
{
// 设置顶点/索引缓冲区
UINT strides = m_VertexStride;
UINT offsets = 0;
deviceContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &strides, &offsets);
deviceContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
// 更新数据并应用
effect.SetWorldMatrix(XMLoadFloat4x4(&m_WorldMatrix));
effect.SetTexture(m_pTexture.Get());
effect.SetMaterial(m_Material);
effect.Apply(deviceContext);
deviceContext->DrawIndexed(m_IndexCount, 0, 0);
}
//...
void BasicEffect::Apply(ID3D11DeviceContext * deviceContext)
{
auto& pCBuffers = pImpl->m_pCBuffers;
// 将缓冲区绑定到渲染管线上
pCBuffers[0]->BindVS(deviceContext);
pCBuffers[1]->BindVS(deviceContext);
pCBuffers[2]->BindVS(deviceContext);
pCBuffers[3]->BindVS(deviceContext);
pCBuffers[4]->BindVS(deviceContext);
pCBuffers[0]->BindPS(deviceContext);
pCBuffers[1]->BindPS(deviceContext);
pCBuffers[2]->BindPS(deviceContext);
pCBuffers[4]->BindPS(deviceContext);
// 设置纹理
deviceContext->PSSetShaderResources(0, 1, pImpl->m_pTexture.GetAddressOf());
if (pImpl->m_IsDirty)
{
pImpl->m_IsDirty = false;
for (auto& pCBuffer : pCBuffers)
{
pCBuffer->UpdateBuffer(deviceContext);
}
}
}
跟Chili的框架目的一样,翻来覆去无非就是尽量减少常量缓冲区的Map操作,同时尽可能的让其他可以通用的渲染状态独立出来,而每次渲染的游戏物体都可能变化的顶点索引与GameObject绑定,只是优化的程度和方式不同,框架就会发生相应的变化。
Chili是将每个可以Bind的API封装成Bindable类,一群Bindable的集合体就是Drawable,同时将可能变化的部分拆成静态和非静态,这样同样的GameObject对象就可以共享一些资源,反正框架方法不一,思想类似,但是看看还挺有意思。
假设绘制的是box的阴影
任务有两个:
一. 将盒子的顶点由光照投影到某一个平面
inline XMMATRIX XM_CALLCONV XMMatrixShadow
(
FXMVECTOR ShadowPlane, //投影到的平面
FXMVECTOR LightPosition //光源位置,平行光写负方向且w为0,点光源写坐标且w为1+
)
定义矩阵
const XMMATRIX reflectMatrix = XMMatrixReflect(XMVectorSet(0.0f, 0.0f, -1.0f, 10.0f));
const XMVECTOR lightPos = XMVectorSet(0.0f, 10.0f, -10.0f, 1.0f);
const XMVECTOR floorPlane = XMVectorSet(0.0f, 1.0f, 0.0f, 0.99f);//地板是y+0.99=0的平面,不到1是为了防止阴影平面与地板平面重叠
const XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, -1.0f, 10.0f);
const XMVECTOR reflectLightPos = XMVector4Transform(lightPos, XMMatrixReflect(mirrorPlane));
m_BasicEffect.SetReflectionMatrix(reflectMatrix);
m_BasicEffect.SetShadowMatrix(XMMatrixShadow(floorPlane, lightPos));
m_BasicEffect.SetRefShadowMatrix(XMMatrixShadow(floorPlane, reflectLightPos));
顶点着色器
#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);
float3 normalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);
// 若当前在绘制反射物体,先进行反射操作
[flatten]
if (g_IsReflection)
{
posW = mul(posW, g_Reflection);
normalW = mul(normalW, (float3x3) g_Reflection);
}
// 若当前在绘制阴影,先进行投影操作
[flatten]
if (g_IsShadow)
{
//将顶点投影到投影平面上
posW = (g_IsReflection ? mul(posW, g_RefShadow) : mul(posW, g_Shadow));
}
vOut.PosH = mul(posW, viewProj);
vOut.PosW = posW.xyz;
vOut.NormalW = normalW;
vOut.Tex = vIn.Tex;
return vOut;
}
这样可以将箱子顶点在投影平面摊开,纹理仍是箱子的(二向箔降维打击!),这时候只要再把箱子材质的反射系数rgb改为全0,a不为0,就可以将纹理变成透明的黑色,办法有很多。
物体绘制代码
//m_ShadowMat 初始化
m_ShadowMat.ambient = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.5f);
m_ShadowMat.diffuse = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.5f);
m_ShadowMat.specular = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.0f);
//.......
//draw
m_WoodCrate.SetMaterial(m_ShadowMat);
m_BasicEffect.SetShadowState(true); // 反射关闭,阴影开启
m_BasicEffect.SetRenderNoDoubleBlend(m_pd3dImmediateContext.Get(), 0);
m_WoodCrate.Draw(m_pd3dImmediateContext.Get(), m_BasicEffect);
m_BasicEffect.SetShadowState(false); // 阴影关闭
m_WoodCrate.SetMaterial(m_WoodCrateMat);
二. 禁止颜色堆叠
这样会存在一个颜色堆叠的问题,因为背面消除后顶点围成的三角形在平面上还是可能互相交叉堆叠,阴影也就叠加了起来,我们要防止这种问题。
办法是模版缓冲区
在一帧开始前清除模版缓冲区,并设初始值0。
渲染阴影时开启模版缓冲区,传入参考模版值也为0,只有模版缓冲区内值与参考值相等时才通过,通过后区内值做修改成其他的,若不通过则keep,这样就可以保证一个像素只会叠加一次。
dsDesc.DepthEnable = true;
dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
dsDesc.DepthFunc = D3D11_COMPARISON_LESS;
dsDesc.StencilEnable = true;
dsDesc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
dsDesc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;
dsDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_INCR;
dsDesc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
// 对于背面的几何体我们是不进行渲染的,所以这里的设置无关紧要
dsDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_INCR;
dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;
本想看看投影的推导公式,但是书都在学校因为疫情也回不去。。。不说了,继续跟着Chili写框架了