首页 > 其他分享 >DX12 实现 模板——物体轮廓

DX12 实现 模板——物体轮廓

时间:2023-05-05 23:47:42浏览次数:51  
标签:STENCIL outlineDesc DX12 DEPTH D3D12 轮廓 模板 OP

前言

本篇将展示如何运用深度模板缓冲区来实现游戏中的物体轮廓效果

源代码model_outline

基础知识

模板测试过程

// 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)

D3D12_STENCIL_OP (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);

输出

image-20230505230357005

现在实现游戏中外挂的透视效果也是轻轻松松

reference

[LearnOpenGL ](https://learnopengl-cn.github.io/04 Advanced OpenGL/02 Stencil testing/)

Direct3D 12 programming guide

Directx12 3D 游戏开发实战

标签:STENCIL,outlineDesc,DX12,DEPTH,D3D12,轮廓,模板,OP
From: https://www.cnblogs.com/chenglixue/p/17375686.html

相关文章

  • C++类模板(模板类)
    参考资料:C++类模板(模板类)详解(biancheng.net) 人们需要编写多个形式和功能都相似的函数,因此有了函数模板来减少重复劳动;人们也需要编写多个形式和功能都相似的类,于是 C++ 引人了类模板的概念,编译器从类模板可以自动生成多个类,避免了程序员的重复劳动。例如,在《C++运算符重......
  • 【模板】堆 题解
    题目传送门一道小根堆模板题。在做这道题之前,我们先介绍一下小根堆是什么。我们定义小根堆是一种对于任何一个父结点的权值总是小于或等于子节点权值的完全二叉树。因此,不难看出,一个小根堆的堆顶(这棵树的根节点)应该是这个堆(树)中权值最小的结点。简单介绍完了小根堆,我们再介绍......
  • 求最大值(函数模板)
    一、问题描述:两个类如下设计:类Time有三个数据成员,hh,mm,ss,分别代表时,分和秒,并有若干构造函数和一个重载-(减号)的成员函数。类Date有三个数据成员,year,month,day分别代表年月日,并有若干构造函数和一个重载>(<)(大于号或者小于号)的成员函数。要求设计一个函数模板template<classT>Tma......
  • eclipse注释模板及格式化模板导入方法
    格式化模板导入步骤  1.点击Window->Preference->Java->CodeStyle->Formatter2.点击右侧Import选择*.xml模板文件导入即可3.如果需要对模板进行修改,可点击Edit编辑即可4.模板示例:1.<?xmlversion="1.0"encoding="UTF-8"standalone="no"?>2.<profilesvers......
  • 洛谷 P3369 【模板】普通平衡树
    有旋Treap模板#include<bits/stdc++.h>usingnamespacestd;structNode{ Node*ch[2]; intval,rank; intrep_cnt; intsiz; Node(intval):val(val),rep_cnt(1),siz(1){ ch[0]=ch[1]=nullptr; rank=rand(); } voidupd_siz(){ siz=......
  • DX12 实现 天空盒
    前言本篇将展示如何使用DX12实现天空盒源代码cubemap创建dds立方体贴图微软官方提供了一个命令行工具Texassemble,该工具可以将输入的几个图片转换为GPU可识别的DDS贴图,如立方体贴图语法texassemble<command>[-r][-flist<filename>][-wwidth][-hheight][-f......
  • 模板方法中的线程安全问题
    1、线程安全?是否存在临界区,共享的变量,会被不同线程写入那么模板方法里面基类的成员变量或者方法就会存在线程安全问题2、excel  AbstractExcelSheet业务数据和excel逻辑解耦让data可以在service层之间set进来这样excel的相关类不用添加到spring容器中 pub......
  • 6-2 数组排序输出(函数模板)
    对于输入的每一批数,按从小到大排序后输出。一行输入为一批数,第一个输入为数据类型(1表示整数,2表示字符型数,3表示有一位小数的浮点数,4表示字符串,0表示输入结束),第二个输入为该批数的数量size(0<size<=10),接下来为size个指定类型的数据。输出将从小到大顺序输出数据。函数接口定义:sor......
  • 类模板。。。对象数组
    #include<bits/stdc++.h>usingnamespacestd;template<classT>classAAA{      Ta,b;   public:      AAA(T_a,T_b):a(_a),b(_b){};      Tsum(){         returna+b;      }      Tcha();};template<......
  • 极简爬虫通用模板
    网络爬虫的一般步骤如下:1、确定爬取目标:确定需要爬取的数据类型和来源网站。2、制定爬取策略:确定爬取哪些网页、如何爬取和频率等。3、构建爬虫程序:使用编程语言(如Python)实现爬虫程序,通过HTTP请求获取网页内容,并进行解析和处理。4、数据存储:将爬取到的数据存储到数据库或文件......