DirectX11 Without DirectX SDK--14 深度测试

前言

当使用加法/减法/乘法颜色混合,或者使用透明混合的时候,在经过深度测试时可能会引发一些问题。例如现在我们需要使用加法混合来绘制一系列对象,而这些对象彼此之间不会相互阻挡。若我们仍使用原来的深度测试,就必须保证某一像素下的所有片元需要按照从远到近的顺序来进行绘制,但这很难做到,尤其在绘制一些几何体的时候可能会出现在前面的像素片元挡住了后面的像素片元的情况。现在,我们有能力通过调整深度测试这一行为,来改变最终的显示结果。

DirectX11 With Windows SDK完整目录

Github项目源码

交换律

加法/减法/乘法的混合运算是满足交换律的(对C0到Cn-1来说),这说明我们绘制像素的先后顺序应该是不会对结果产生影响的:
\(\mathbf{B'}=\mathbf{B}+\mathbf{C_0}+\mathbf{C_1}+...+\mathbf{C_{n-1}}\)
\(\mathbf{B'}=\mathbf{B}-\mathbf{C_0}-\mathbf{C_1}-...-\mathbf{C_{n-1}}\)
\(\mathbf{B'}=\mathbf{B} \otimes \mathbf{C_0} \otimes \mathbf{C_1} \otimes ... \otimes \mathbf{C_{n-1}}\)

但是混合的先后顺序会对结果有影响,所以不满足交换律(无论alpha值如何):
\(\mathbf{B'}= 0.5(0.5\mathbf{B} + 0.5\mathbf{C_0}) + 0.5\mathbf{C_1} = 0.25\mathbf{B} + 0.25\mathbf{C_0} + 0.5\mathbf{C_1}\)
\(\mathbf{B'}= 0.5(0.5\mathbf{B} + 0.5\mathbf{C_1}) + 0.5\mathbf{C_0} = 0.25\mathbf{B} + 0.25\mathbf{C_1} + 0.5\mathbf{C_0}\)

深度测试

关闭深度测试

在某一个阶段关闭深度测试后,若某一像素位置有新的像素片元出现,那么该像素片元就会直接不经过深度测试来到后面的混合阶段。此时混合没开的话,该像素片元就会直接取代后备缓冲区的对应像素,此时需要按从后到前的顺序来绘制物体才能保证正确的显示效果。但关闭深度测试的一个主要用途是绘制场景内的一系列透明物体。当然,前提是这一堆透明物体中间没有不透明物体在阻挡,否则不透明物体后面的透明物也会被绘制出来。

开启深度测试但关闭深度写入

相比上面的方式,这是一种更为合理的做法。我们只需要在渲染的时候先绘制不透明物体,然后就可以按任意的顺序来绘制透明物体。这是因为当绘制透明物体的时候,若它前面有不透明物体阻挡,则不会通过深度测试。所以十分适合用于处理透明物体和不透明物体相互交叉的情况。

利用深度测试和模板测试来绘制闪电特效与其镜面

一个闪电动画实际上是由60张按顺序播放的位图构成:

然后在绘制的时候,每秒绘制60帧闪电动画,即1秒一个循环。切换下一帧的时候,只需要更换下一张纹理即可。

// 更新闪电动画
mBoltAnim.SetTexture(mBoltSRVs[currBoltFrame].Get());
if (frameTime > 1.0f / 60)
{
    currBoltFrame = (currBoltFrame + 1) % 60;
    frameTime -= 1.0f / 60;
}
frameTime += dt;

RenderStates类的变化

现在RenderStates类有如下可用的状态:

class RenderStates
{
public:
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;

    static void InitAll(const ComPtr<ID3D11Device>& device);
    // 使用ComPtr无需手工释放

public:
    static ComPtr<ID3D11RasterizerState> RSWireframe;       // 光栅化器状态:线框模式
    static ComPtr<ID3D11RasterizerState> RSNoCull;          // 光栅化器状态:无背面裁剪模式
    static ComPtr<ID3D11RasterizerState> RSCullClockWise;   // 光栅化器状态:顺时针裁剪模式

    static ComPtr<ID3D11SamplerState> SSLinearWrap;         // 采样器状态:线性过滤
    static ComPtr<ID3D11SamplerState> SSAnistropicWrap;     // 采样器状态:各项异性过滤

    static ComPtr<ID3D11BlendState> BSNoColorWrite;     // 混合状态:不写入颜色
    static ComPtr<ID3D11BlendState> BSTransparent;      // 混合状态:透明混合
    static ComPtr<ID3D11BlendState> BSAlphaToCoverage;  // 混合状态:Alpha-To-Coverage
    static ComPtr<ID3D11BlendState> BSAdditive;         // 混合状态:加法混合

    static ComPtr<ID3D11DepthStencilState> DSSWriteStencil;     // 深度/模板状态:写入模板值
    static ComPtr<ID3D11DepthStencilState> DSSDrawWithStencil;  // 深度/模板状态:对指定模板值的区域进行绘制
    static ComPtr<ID3D11DepthStencilState> DSSNoDoubleBlend;    // 深度/模板状态:无二次混合区域
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthTest;      // 深度/模板状态:关闭深度测试
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthWrite;     // 深度/模板状态:仅深度测试,不写入深度值
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthTestWithStencil;   // 深度/模板状态:关闭深度测试,对指定模板值的区域进行绘制
    static ComPtr<ID3D11DepthStencilState> DSSNoDepthWriteWithStencil;  // 深度/模板状态:仅深度测试,不写入深度值,对指定模板值的区域进行绘制
};

加法混合

加法混合模式的创建如下:

D3D11_BLEND_DESC blendDesc;
ZeroMemory(&blendDesc, sizeof(blendDesc));
auto& rtDesc = blendDesc.RenderTarget[0];
blendDesc.AlphaToCoverageEnable = false;
blendDesc.IndependentBlendEnable = false;
rtDesc.BlendEnable = true;

// 加法混合模式
// Color = SrcColor + DestColor
// Alpha = SrcAlpha
rtDesc.SrcBlend = D3D11_BLEND_ONE;
rtDesc.DestBlend = D3D11_BLEND_ONE;
rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
rtDesc.SrcBlendAlpha = D3D11_BLEND_ONE;
rtDesc.DestBlendAlpha = D3D11_BLEND_ZERO;
rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;

HR(device->CreateBlendState(&blendDesc, BSAdditive.ReleaseAndGetAddressOf()));

关闭深度测试

需要准备好默认情况下的绘制和指定模板值绘制两种情况:

D3D11_DEPTH_STENCIL_DESC dsDesc;

// 关闭深度测试的深度/模板状态
// 若绘制非透明物体,务必严格按照绘制顺序
// 绘制透明物体则不需要担心绘制顺序
// 而默认情况下模板测试就是关闭的
dsDesc.DepthEnable = false;
dsDesc.StencilEnable = false;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthTest.ReleaseAndGetAddressOf()));

// 关闭深度测试
// 若绘制非透明物体,务必严格按照绘制顺序
// 绘制透明物体则不需要担心绘制顺序
// 对满足模板值条件的区域才进行绘制
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_KEEP;
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_KEEP;
dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthTestWithStencil.ReleaseAndGetAddressOf()));

允许深度测试,但不写入深度值的状态

同理也需要准备好默认绘制和指定模板值绘制两种情况:

// 进行深度测试,但不写入深度值的状态
// 若绘制非透明物体时,应使用默认状态
// 绘制透明物体时,使用该状态可以有效确保混合状态的进行
// 并且确保较前的非透明物体可以阻挡较后的一切物体
dsDesc.DepthEnable = true;
dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
dsDesc.DepthFunc = D3D11_COMPARISON_LESS;
dsDesc.StencilEnable = false;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthWrite.ReleaseAndGetAddressOf()));

// 进行深度测试,但不写入深度值的状态
// 若绘制非透明物体时,应使用默认状态
// 绘制透明物体时,使用该状态可以有效确保混合状态的进行
// 并且确保较前的非透明物体可以阻挡较后的一切物体
// 对满足模板值条件的区域才进行绘制
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_KEEP;
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_KEEP;
dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;

HR(device->CreateDepthStencilState(&dsDesc, DSSNoDepthWriteWithStencil.ReleaseAndGetAddressOf()));

BasicFX类的变化

本来打算利用ID3D11ShaderReflection来尝试获得更多信息的,但是发现要实现一个功能接近于Effects11的类还是特别复杂,并且没办法写原来FX特有的HLSL代码,在这里只好先搁置。

下面四个方法都是专门用于绘制闪电动画的,使用了加法混合。每一个都可以看做是一个Effects11中的Technique11

BasicFX::SetDrawBoltAnimNoDepthTest方法

该方法关闭了深度测试,用于绘制闪电动画(但默认并不是使用这个,你需要自行修改代码替换调用来查看区别)

void BasicFX::SetDrawBoltAnimNoDepthTest()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSNoDepthTest.Get(), 0);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

BasicFX::SetDrawBoltAnimNoDepthWrite方法

该方法允许深度测试,但关闭深度值写入,用于绘制闪电动画(在程序中默认使用这种模式)

void BasicFX::SetDrawBoltAnimNoDepthWrite()
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSNoDepthWrite.Get(), 0);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

BasicFX::SetDrawBoltAnimNoDepthTestWithStencil方法

该方法关闭了深度测试,用于绘制镜面区域的闪电动画(默认不使用这种模式)

void BasicFX::SetDrawBoltAnimNoDepthTestWithStencil(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSNoDepthTestWithStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

BasicFX::SetDrawBoltAnimNoDepthWriteWithStencil方法

该方法开启深度测试,但不允许写入深度值,用于绘制镜面区域的闪电动画(默认使用这种模式)

void BasicFX::SetDrawBoltAnimNoDepthWriteWithStencil(UINT stencilRef)
{
    md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
    md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
    md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
    md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    md3dImmediateContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    md3dImmediateContext->OMSetDepthStencilState(RenderStates::DSSNoDepthWriteWithStencil.Get(), stencilRef);
    md3dImmediateContext->OMSetBlendState(RenderStates::BSAdditive.Get(), nullptr, 0xFFFFFFFF);
}

场景绘制

现在的场景绘制可以说算是比较复杂的了,需要同时处理透明物体、非透明物体的绘制,以及绘制镜面和阴影的效果。因此严格的按照正确顺序去绘制在这里就变得十分重要。

第1步: 镜面区域写入模板缓冲区

和之前一样,先标记好镜面区域:

// *********************
// 1. 给镜面反射区域写入值1到模板缓冲区
// 

mBasicFX.SetWriteStencilOnly(1);
mMirror.Draw(md3dImmediateContext);

第2步:绘制不透明的反射物体

// ***********************
// 2. 绘制不透明的反射物体
//

// 开启反射绘制
mDrawingState.isReflection = 1;     // 反射开启
mBasicFX.UpdateConstantBuffer(mDrawingState);
mBasicFX.SetRenderDefaultWithStencil(1);

mWalls[2].Draw(md3dImmediateContext);
mWalls[3].Draw(md3dImmediateContext);
mWalls[4].Draw(md3dImmediateContext);
mFloor.Draw(md3dImmediateContext);

mWoodCrate.Draw(md3dImmediateContext);

第3步:绘制不透明反射物体的阴影

// ***********************
// 3. 绘制不透明反射物体的阴影
//

mWoodCrate.SetMaterial(mShadowMat);
mDrawingState.isShadow = 1;         // 反射开启,阴影开启
mBasicFX.UpdateConstantBuffer(mDrawingState);
mBasicFX.SetRenderNoDoubleBlend(1);

mWoodCrate.Draw(md3dImmediateContext);

// 恢复到原来的状态
mDrawingState.isShadow = 0;
mBasicFX.UpdateConstantBuffer(mDrawingState);
mWoodCrate.SetMaterial(mWoodCrateMat);

第4步:绘制需要混合的反射闪电动画和透明物体

// ***********************
// 4. 绘制需要混合的反射闪电动画和透明物体
//

mBasicFX.SetDrawBoltAnimNoDepthWriteWithStencil(1);
mBoltAnim.Draw(md3dImmediateContext);

mBasicFX.SetRenderAlphaBlendWithStencil(1);
mMirror.Draw(md3dImmediateContext);

第5步:绘制不透明的正常物体

// ************************
// 5. 绘制不透明的正常物体
//
mBasicFX.SetRenderDefault();
mDrawingState.isReflection = 0;     // 反射关闭
mBasicFX.UpdateConstantBuffer(mDrawingState);

for (auto& wall : mWalls)
    wall.Draw(md3dImmediateContext);
mFloor.Draw(md3dImmediateContext);
mWoodCrate.Draw(md3dImmediateContext);

第6步:绘制不透明正常物体的阴影

// ************************
// 6. 绘制不透明正常物体的阴影
//
mWoodCrate.SetMaterial(mShadowMat);
mDrawingState.isShadow = 1;         // 反射关闭,阴影开启
mBasicFX.UpdateConstantBuffer(mDrawingState);
mBasicFX.SetRenderNoDoubleBlend(0);

mWoodCrate.Draw(md3dImmediateContext);

mDrawingState.isShadow = 0;         // 阴影关闭
mBasicFX.UpdateConstantBuffer(mDrawingState);
mWoodCrate.SetMaterial(mWoodCrateMat);

第7步:绘制需要混合的闪电动画

// ************************
// 7. 绘制需要混合的闪电动画
mBasicFX.SetDrawBoltAnimNoDepthWrite();
mBoltAnim.Draw(md3dImmediateContext);

最终动画效果如下:

DirectX11 With Windows SDK完整目录

Github项目源码

原文地址:https://www.cnblogs.com/X-Jun/p/9425416.html

时间: 2024-10-31 04:30:44

DirectX11 Without DirectX SDK--14 深度测试的相关文章

DirectX11 With Windows SDK 24--Render-To-Texture(RTT)技术的应用、使用ScreenGrab保存纹理到文件

前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用.此外,这里还会讲到如何使用DirectXTex的ScreenGrab来保存纹理,可以说是干货满满了. 如果想要看Render-To-Texture在动态天空盒的应用,可以点此回顾: 章节 23 立方体映射:动态天空盒的实现 DirectX11 With Windows SDK完整目录 Github项目

【DirectX SDK Extra】提示缺少Qedit.h问题 (转)

原文转自 http://blog.csdn.net/joeblackzqq/article/details/10944005 DirectX 9.0 SDK 开发包以及扩展包下载(February 2005) http://dev.csdn.net/article/62/62941.shtm.直接到微软官网下的话,需要validation确认. http://download.microsoft.com/download/7/b/9/7b92308a-ec8d-4016-8d45-2f91dd1

[转载]DirectX SDK (June 2010)安装错误S1023,解决方法

导致这个错误的原因是在安装DirectX SDK (June 2010)之前.我就安装了VS2010 . 所以也就安装了: Microsoft Visual C++ 2010 x86 Redistributable Microsoft Visual C++ 2010 x64 Redistributable 而DirectX SDK (June 2010)会在安装的过程中再次安装.囧 所以解决的办法就是在安装DirectX SDK (June 2010)之前先将以上两个程序卸载. 在运行中执行以下

DirectX SDK版本与Visual Studio版本

对于刚刚接触 DirectShow 的人来说,安装配置是一个令人头疼的问题,经常出现的情况是最基本的 baseclass 就无法编译.一开始我也为此费了很大的功夫,比如说修改代码.修改编译选项使其编译通过.因为大家 Visual Studio 的版本与 DirectShow 的版本各不相同,导致问题多种多样.网上的参考也不可尽信,往往花费了大量的时间和精力,程序仍没有编译通过,而丧失学习 DirectShow 的兴趣和信心.我在 Visual Stdio 6.0,2003,2005,2008 都

学习笔记一:关于directx sdk的安装于一些概念

关于directx sdk开发环境的安装: 在百度搜索了directx sdk,进入了微软的官网,下载了DXSDK_Jun10.exe 百度网盘:http://pan.baidu.com/s/1o6r3MDO 下载安装就可以了 VC++开发用的是VS2010 新建VS2010工程:win32项目,空项目 创建了空的win32项目后,首先要进行directx开发环境的设置 首先右键-工程-属性,在弹出的工程属性中选择VC++目录 这里要设置的是 包含目录 和 库目录 包含目录:D:\Program

粒子系统与雨的效果 (DirectX11 with Windows SDK)

前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用Windows SDK来实现. 顺手安利一波:我最近在学DX11 with WindowSDK 教程 博客地址: https://www.cnblogs.com/X-Jun/p/9028764.html 所以下面的都是基于这个教程和上面提到的那本书来写的,推荐看到教程17章和书里第15章之后再来看这篇博客,有助

DirectX SDK

http://blog.csdn.net/c4501srsy/article/details/17403927 http://blog.csdn.net/yy649487394/article/details/45075167 http://jingyan.baidu.com/article/b7001fe199dbf00e7382dd75.html?qq-pf-to=pcqq.c2c http://blog.csdn.net/kangxidashen/article/details/23472

Directx11学习笔记【十四】 使用最新的Effect框架和SDK

由于之前一直在看directx11龙书学习,因此sdk一直用的Microsoft DirectX SDK (June 2010) 版本,最近在stackoverflow上问dx11相关问题时,一直被大神吐槽为何还用已经废弃的directx sdk,由于directx sdk现在已经和windows sdk合并到一起了,只能去下windows sdk了.为了方便索性直接换了vs 2015社区版,里面自带了(windows sdk),既然sdk换了最新的,effect框架也要换最新的啊(Effect

DirectX11 SDK 例程报错解决方法

下载好DirectX11例程后,VS2015运行不起来,好几个报错 在这里记录一下,虽然挺简单的,但是我想对于像我这样的新手小伙伴们来说还是挺有用的 第一个错误: FXC : error X3501: 'main': entrypoint not found 解决方法: 原因是.fx文件VS会默认使用HLSL编译器对其进行编译,而.fx文件中并未定义main函数,所以会导致编译出错 右键.fx文件,“属性->配置属性->常规->项类型”,将“HLSL编译器”改为“不参与生成” 第二个错误