前言
本篇将展示如何运用深度模板缓冲区来实现游戏中的物体轮廓效果
基础知识
模板测试过程
// compare_func:定义的比较函数。对两个参数进行比较
// StencilRef:模板参考值
// StencilReadMask:位于D3D12_DEPTH_STENCIL_DESC
// Value:正在接受模板测试的值
if(compare_func(StencilRef & StencilReadMask, Value & StencilReadMask))
accept pixel
else
reject pixel
不懂这个式子的含义也没关系,接下来的内容能理解就行
深度模板描述符
该描述符作用于PSO,因此填写完后赋予PSO即可
D3D12_DEPTH_STENCIL_DESC (d3d12.h)
D3D12_DEPTH_WRITE_MASK (d3d12.h)
D3D12_COMPARISON_FUNC (d3d12.h)
D3D12_DEPTH_STENCILOP_DESC (d3d12.h)
typedef struct D3D12_DEPTH_STENCIL_DESC {
BOOL DepthEnable; // 启用/禁用深度测试
D3D12_DEPTH_WRITE_MASK DepthWriteMask; // 启用/禁用深度写入
D3D12_COMPARISON_FUNC DepthFunc; // 深度测试的比较函数
BOOL StencilEnable; // 启用/禁用模板测试
UINT8 StencilReadMask; // 读取模板值时,禁止对应位的读取.
// 默认不屏蔽任何一位:D3D12_DEFAULT_STENCIL_READ_MASK
UINT8 StencilWriteMask; // 写入模板值时,禁止对应位的写入(深度写入).
// 默认不屏蔽任何一位:D3D12_DEFAULT_STENCIL_WRITE_MASK
D3D12_DEPTH_STENCILOP_DESC FrontFace; // 根据模板测试和深度测试的结果,对正面朝向的三角形面进行对应模板运算
D3D12_DEPTH_STENCILOP_DESC BackFace; // 根据模板测试和深度测试的结果,对背面朝向方向的三角形面进行对应模板运算
} D3D12_DEPTH_STENCIL_DESC;
typedef enum D3D12_DEPTH_WRITE_MASK {
D3D12_DEPTH_WRITE_MASK_ZERO = 0, // Turn off writes to the depth-stencil buffer.
D3D12_DEPTH_WRITE_MASK_ALL = 1 // Turn on writes to the depth-stencil buffer.
} ;
typedef enum D3D12_COMPARISON_FUNC {
D3D12_COMPARISON_FUNC_NEVER = 1, // 不通过测试
D3D12_COMPARISON_FUNC_LESS = 2, // 若源数据值<目标数据值,通过测试
D3D12_COMPARISON_FUNC_EQUAL = 3, // 若源数据值=目标数据值,通过测试
D3D12_COMPARISON_FUNC_LESS_EQUAL = 4, // 若源数据值≤目标数据值,通过测试
D3D12_COMPARISON_FUNC_GREATER = 5, // 若源数据值>目标数据值,通过测试
D3D12_COMPARISON_FUNC_NOT_EQUAL = 6, // 若源数据值!=目标数据值,通过测试
D3D12_COMPARISON_FUNC_GREATER_EQUAL = 7, // 若源数据值≥目标数据值,通过测试
D3D12_COMPARISON_FUNC_ALWAYS = 8 // 通过测试
} ;
#define D3D12_DEFAULT_STENCIL_WRITE_MASK ( 0xff )
#define D3D12_DEFAULT_STENCIL_READ_MASK ( 0xff )
// 根据模板测试的结果,进行对应的模板运算更新模板缓冲区
typedef struct D3D12_DEPTH_STENCILOP_DESC {
D3D12_STENCIL_OP StencilFailOp; // 模板测试失败时
D3D12_STENCIL_OP StencilDepthFailOp; // 模板测试通过但深度测试失败
D3D12_STENCIL_OP StencilPassOp; // 模板测试和深度测试都通过
D3D12_COMPARISON_FUNC StencilFunc; // 模板测试的比较函数
} D3D12_DEPTH_STENCILOP_DESC;
// 模板运算
typedef enum D3D12_STENCIL_OP {
D3D12_STENCIL_OP_KEEP = 1, // 不修改模板缓冲区的数据
D3D12_STENCIL_OP_ZERO = 2, // 将模板缓冲区的数据置为0
D3D12_STENCIL_OP_REPLACE = 3, // 将模板缓冲区的数据设为模板参考值(StencilRef)
D3D12_STENCIL_OP_INCR_SAT = 4, // 模板缓冲区的数据的值+1,并将该该值限制(clamp)在一定范围内
D3D12_STENCIL_OP_DECR_SAT = 5, // 模板缓冲区的数据的值-1,并将该该值限制(clamp)在一定范围内
D3D12_STENCIL_OP_INVERT = 6, // 模板缓冲区的数据的值按二进制位进行反转
D3D12_STENCIL_OP_INCR = 7, // 模板缓冲区的数据的值+1,如果该值超过最大值,则进行回环(warp)
D3D12_STENCIL_OP_DECR = 8 // 模板缓冲区的数据的值-1,如果该值小于最小值,则进行回环(warp)
// warp:对于8位的模板缓冲区来说,其最大值为255,若+1等于255则使其置于0
} ;
几个重点:
- 启用/禁用深度测试:DepthEnable
- 启用/禁用深度写入:DepthWriteMask
- 启用/禁用模板测试:StencilEnable
- 启用/禁用模板写入:StencilWriteMask
设置模板参考值
ID3D12GraphicsCommandList::OMSetStencilRef (d3d12.h)
void OMSetStencilRef(
[in] UINT StencilRef
);
实现物体轮廓
思路
实现物体轮廓需要分两次渲染
- 第一次渲染。启用深度测试、深度写入、模板测试、模板写入,将模板比较函数设为D3D12_COMPARISON_FUNC_ALWAYS(总是通过测试),当物体的片段进行渲染时,在模板缓冲区中将对应的片段更新为1(StencilPassOp = D3D12_STENCIL_OP_REPLACE,OMSetStencilRef(1))
- 第二次渲染。禁用深度测试(防止被其他物体覆盖)、模板写入(需要和StencilRef进行比较),把物体稍微放大一丢丢,将模板函数设为D3D12_COMPARISON_FUNC_NOT_EQUAL(绘制模板值不等于1的片元,也就是放大后多出的轮廓),使用一个单独的PS绘制边框颜色。记得再启用深度测试、模板写入
shader
m_shaders["Outline_VS"] = CompileShader(GetAssetFullPath(L"Outline_VS.hlsl").c_str(), nullptr, "main", "vs_5_1");
m_shaders["Outline_PS"] = CompileShader(GetAssetFullPath(L"Outline_PS.hlsl").c_str(), nullptr, "main", "ps_5_1");
cbuffer ObjectCB : register(b0)
{
float4x4 world;
}
cbuffer PassCB : register(b1)
{
float4x4 view;
float4x4 inverseView;
float4x4 projection;
float4x4 inverseProjection;
float4x4 viewProjection;
float4x4 inverseViewProjection;
float3 eyeWorldPosition;
Light lights[MAX_LIGHT_COUNT];
}
static float4x4 scale =
{
1.2f, 0.f, 0.f, 0.f,
0.f, 1.2f, 0.f, 0.f,
0.f, 0.f, 1.2f, 0.f,
0.f, 0.f, 0.f, 1.f
};
struct VSInput
{
float3 position : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD;
};
struct PSInput
{
float3 positionW : POSITION;
float4 positionH : SV_POSITION;
float3 normalW : NORMAL;
float2 uv : TEXCOORD;
};
// VS
PSInput main(VSInput input)
{
PSInput output;
float4 positionW = mul(mul(float4(input.position, 1.f), world), scale);
output.positionW = positionW.xyz;
output.normalW = mul(mul(input.normal, (float3x3) world), (float3x3)scale);
output.positionH = mul(positionW, viewProjection);
output.uv = input.uv;
return output;
}
// PS
float4 main() : SV_TARGET
{
return float4(0.04f, 0.28f, 0.26f, 1.0f);
}
PSO & 深度模板描述符
//
// 第一次渲染
//
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePSODesc = {};
{
// set depth & stencil test
D3D12_DEPTH_STENCIL_DESC outlineDesc;
outlineDesc.DepthEnable = TRUE; // enable depth test
outlineDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; // Turn on writes to the depth-stencil buffer
outlineDesc.DepthFunc = D3D12_COMPARISON_FUNC_LESS; // if less pass the depth test
outlineDesc.StencilEnable = TRUE;
outlineDesc.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; // no mask
outlineDesc.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK; // no mask
outlineDesc.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; // pass stencil test
outlineDesc.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; // no change
outlineDesc.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; // no change
outlineDesc.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE; // use stencil reference to replace data of stencil buffer
// default
outlineDesc.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
outlineDesc.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
outlineDesc.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
outlineDesc.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
opaquePSODesc.DepthStencilState = outlineDesc;
}
ThrowIfFailed(m_device->CreateGraphicsPipelineState(&opaquePSODesc, IID_PPV_ARGS(&m_PSOs["opaque"])));
//
// 第二次渲染
//
D3D12_GRAPHICS_PIPELINE_STATE_DESC outlinePSODesc = opaquePSODesc;
D3D12_DEPTH_STENCIL_DESC outlineDesc;
outlineDesc.DepthEnable = FALSE; // forbie depth test
outlineDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; // Turn on writes to the depth-stencil buffer
outlineDesc.DepthFunc = D3D12_COMPARISON_FUNC_LESS; // if less pass the depth test
outlineDesc.StencilEnable = TRUE;
outlineDesc.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; // no mask
outlineDesc.StencilWriteMask = 0x00; // forbie stencil write
outlineDesc.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_NOT_EQUAL; // pass stencil test
outlineDesc.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; // no change
outlineDesc.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; // no change
outlineDesc.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; // no change
// default
outlineDesc.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
outlineDesc.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
outlineDesc.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
outlineDesc.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
outlinePSODesc.VS =
{
reinterpret_cast<BYTE*>(m_shaders["Outline_VS"]->GetBufferPointer()),
m_shaders["Outline_VS"]->GetBufferSize()
};
outlinePSODesc.PS =
{
reinterpret_cast<BYTE*>(m_shaders["Outline_PS"]->GetBufferPointer()),
m_shaders["Outline_PS"]->GetBufferSize()
};
ThrowIfFailed(m_device->CreateGraphicsPipelineState(&outlinePSODesc, IID_PPV_ARGS(&m_PSOs["Outline"])));
渲染
// 绘制物体
m_commandList->OMSetStencilRef(1); // 将模板参考值设为1
m_pCurrentFrameResource->PopulateCommandList(m_commandList.Get(), m_renderLayers[(int)Core::RenderLayer::Opaque], m_cbvSrvHeap.Get(), m_passCBVOffset, m_frameIndex, m_cbvSrvDescriptorSize);
// 绘制轮廓
m_commandList->SetPipelineState(m_PSOs["Outline"].Get());
m_pCurrentFrameResource->PopulateCommandList(m_commandList.Get(), m_renderLayers[(int)Core::RenderLayer::Opaque], m_cbvSrvHeap.Get(), m_passCBVOffset, m_frameIndex, m_cbvSrvDescriptorSize);
输出
现在实现游戏中外挂的透视效果也是轻轻松松
reference
[LearnOpenGL ](https://learnopengl-cn.github.io/04 Advanced OpenGL/02 Stencil testing/)
Directx12 3D 游戏开发实战
标签:STENCIL,outlineDesc,DX12,DEPTH,D3D12,轮廓,模板,OP From: https://www.cnblogs.com/chenglixue/p/17375686.html