DirectX12 3D 游戏开发与实战第十章内容(上)
仅供个人学习使用,请勿转载。谢谢!
10、混合
本章将研究混合技术,混合技术可以让我们将当前需要光栅化的像素(也称为源像素)和之前已经光栅化到后台缓冲区的像素(也称为目标像素)进行融合。因此,该技术可以用来渲染如水和玻璃之类的半透物体。
学习目标
- 理解混合技术的工作原理,并且在Direct3D中运用该技术
- 学习Direct3D所支持的不同混合模式
- 探究如何使用alpha分量来调节图元的透明度
- 学会仅通过调用HLSL中的clip函数来阻止向后台缓冲区中绘制像素
10.1、混合方程
设C为像素着色器输出的当前正在光栅化的第i行、第j列像素(源像素)的颜色值,D为目前在后台缓冲区中与之对应的第i行、第j列像素(目标像素)的颜色值。如果不使用混合技术,C将会直接覆盖D,从而使后台缓冲区中第i行、第j列的像素更新为新的颜色值C,如果使用混合技术,C将会和D混合成新的颜色值再去覆盖D。
Direct3D使用下列混合方程来使源像素和目标像素融合:
其中F1和F2分别为源混合因子和目标混合因子,我们将会在10.3节介绍,运算符 · 表示针对颜色向量而定义的分量式乘法,* 表示将会在10.2节介绍的二元运算符。
上述混合方程仅用于RGB分量,而alpha分量由另一条混合方程处理。这里不介绍了。两条混合方程的本质是一样的,之所以分开处理RGB分量和alpha分量主要是希望可以由此产生更多不同的混合效果
10.2、混合运算
下列枚举项将会用作混合方程中的二元运算符 * :
typedef enum D3D12_BLEND_OP
{
D3D12_BLEND_OP_ADD = 1, //加
D3D12_BLEND_OP_SUBTRACT = 2, //源像素·源混合因子-目标像素·目标混合因子
D3D12_BLEND_OP_REV_SUBTRACT = 3, //减数和被减数调换位置
D3D12_BLEND_OP_MIN = 4, //取最小值
D3D12_BLEND_OP_MAX = 5 //取最大值
}D3D12_BLEND_OP;
上述二元运算符也可以用于alpha分量的混合运算。
Direct3D最近几个版本开始加入了一项新的特性,即通过逻辑运算符对源颜色和目标颜色进行混合,用以取代传统的混合方程。这些逻辑运算符如下:
typedef enum D3D12_LOGIC_OP
{
D3D12_LOGIC_OP_CLEAR = 0,
D3D12_LOGIC_OP_SET = ( D3D12_LOGIC_OP_CLEAR + 1 ) ,
D3D12_LOGIC_OP_COPY = ( D3D12_LOGIC_OP_SET + 1 ) ,
D3D12_LOGIC_OP_COPY_INVERTED = ( D3D12_LOGIC_OP_COPY + 1 ) ,
D3D12_LOGIC_OP_NOOP = ( D3D12_LOGIC_OP_COPY_INVERTED + 1 ) ,
D3D12_LOGIC_OP_INVERT = ( D3D12_LOGIC_OP_NOOP + 1 ) ,
D3D12_LOGIC_OP_AND = ( D3D12_LOGIC_OP_INVERT + 1 ) ,
D3D12_LOGIC_OP_NAND = ( D3D12_LOGIC_OP_AND + 1 ) ,
D3D12_LOGIC_OP_OR = ( D3D12_LOGIC_OP_NAND + 1 ) ,
D3D12_LOGIC_OP_NOR = ( D3D12_LOGIC_OP_OR + 1 ) ,
D3D12_LOGIC_OP_XOR = ( D3D12_LOGIC_OP_NOR + 1 ) ,
D3D12_LOGIC_OP_EQUIV = ( D3D12_LOGIC_OP_XOR + 1 ) ,
D3D12_LOGIC_OP_AND_REVERSE = ( D3D12_LOGIC_OP_EQUIV + 1 ) ,
D3D12_LOGIC_OP_AND_INVERTED = ( D3D12_LOGIC_OP_AND_REVERSE + 1 ) ,
D3D12_LOGIC_OP_OR_REVERSE = ( D3D12_LOGIC_OP_AND_INVERTED + 1 ) ,
D3D12_LOGIC_OP_OR_INVERTED = ( D3D12_LOGIC_OP_OR_REVERSE + 1 )
}D3D12_LOGIC_OP;
注意点:我们不同同时使用传统混合方程和逻辑运算符这两种混合手段,我们只能二选一。如果我们要使用逻辑运算符混合技术,就一定要选择它所支持的渲染目标格式——UNIT(无符号整数),否则会报错
10.3、混合因子
通过为源混合因子和目标混合因子设置不同的混合运算符,我们就可以实现各种各样的混合效果。这里就不列举混合因子了,如果有兴趣的可以查看SDK文档中的D3D12_BLEND枚举类型
10.4、混合状态
前面已经介绍过混合运算符和混合因子了,那么我们要如何使用Direct3D来设置这些数值?
答:混合状态也是PSO(流水线状态对象)的一部分,只不过我们之前没有去设置它,即没有开启混合技术。
opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
为了配置非默认混合状态,我们必须填写D3D12_BLEND_DESC结构体,该结构体的定义如下:
typedef struct D3D12_BLEND_DESC{
BOOL AlphaToCoverageEnable; //默认为false
BOOL IndependentBlendEnable; //默认为false
D3D12_RENDER_TARGET_BLEND_DESC RenderTarget[ 8 ];
}
参数介绍:
- AlphaToCoverageEnable:如果指定为true,则启用alpha-to-coverage功能,这是一种在渲染叶片或者门等纹理时极其有用的一种多重采样技术。(开启此项技术需要开启多重采样)
- IndependentBlendEnable:Direct3D最多同时支持8个渲染目标,如果该标志被设置为ture,则表明可以向每一个渲染目标执行不同的混合操作,如果该标志为false,则每个渲染目标将会采用D3D12_BLEND_DESC::RenderTarget数组中第一个元素所描述的方式进行混合操作。(多渲染目标技术常用于高级算法,我们现在每次仅向一个渲染目标进行绘制)
- RenderTarget:具有8个D3D12_RENDER_TARGET_BLEND_DESC元素的数组,其中第i个元素描述了如何对第i个渲染目标进行混合处理。
结构体D3D12_RENDER_TARGET_BLEND_DESC结构体的定义如下:
typedef struct D3D12_RENDER_TARGET_BLEND_DESC
{
BOOL BlendEnable; //是否开启常规混合运算
BOOL LogicOpEnable; //是否开启逻辑混合运算(常规混合运算和逻辑混合运算只能二选一)
D3D12_BLEND SrcBlend; //指定RGB混合运算中的源混合因子
D3D12_BLEND DestBlend; //指定RGB混合运算中的目标混合因子
D3D12_BLEND_OP BlendOp; //指定RGB混合运算中的混合运算符
D3D12_BLEND SrcBlendAlpha; //指定alpha混合运算中的源混合因子
D3D12_BLEND DestBlendAlpha; //指定alpha混合运算中的目标混合因子
D3D12_BLEND_OP BlendOpAlpha;//指定alpha混合运算中的混合运算符
D3D12_LOGIC_OP LogicOp; //指定源颜色和目标颜色所使用的逻辑运算符
UINT8 RenderTargetWriteMask;//控制混合后的数据可以被写入后台缓冲区中的那些颜色通道
}D3D12_RENDER_TARGET_BLEND_DESC;
下面的代码将会展示如何创建和设置混合状态:
//创建开启混合功能的PSO
D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc;
D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
transparencyBlendDesc.BlendEnable = true;
transparencyBlendDesc.LogicOpEnable = false;
transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
transparencyBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE;
transparencyBlendDesc.DestBlendAlpha = D3D12_BLEND_ZERO;
transparencyBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
transparencyBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
transparencyBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&transparentPsoDesc, IID_PPV_ARGS(&mPSOs["transparent"])));
和其他的PSO一一昂,我们应该在应用程序的初始化期间创建他们,然后根据需求通过ID3D12GraphicsCommandList::SetPipelineState方法在不同的状态之间来回切换。
10.5、混合示例
在下面的各个小结中,我们将讨论一些用于获取特效的混合因子组合,在这些示例中,我们暂时只关注RGB分量,不考虑alpha分量。(RGB分量和alpha分量的处理方式类似)
10.5.1、禁止颜色的写操作
如果希望原始的目标像素保持不变,即不对目标像素进行覆盖,也不和目标像素进行混合。为了实现这个功能,我们只需要把源混合因子设置为D3D12_BLEND_ZERO,将目标混合因子设置为D3D12_BLEND_ONE,在将混合运算符设置为D3D12_BLEND_OP_DAA即可,此时混合方程为:
然而实际上我们有一个更简便的方法实现上述功能,即将成员D3D12_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask设置为0,便可以禁止向任何颜色通道执行写操作。
10.5.2、加法混合和减法混合
如果希望源像素和目标像素实现加法运算,我们可以将源混合因子和目标混合因子都设置为D3D12_BLEND_ONE,然后把混合运算符设置为D3D12_BLEND_OP_ADD即可,此时混合方程为
如果继续使用上述混合因子,但是将混合运算符替换为D3D12_BLEND_OP_SUBTRACT,我们便可以实现减法混合。
10.5.3、乘法混合
如果希望源像素和目标像素实现乘法运算,我们可以将源混合因子设置为D3D12_BLEND_ZERO,目标混合因子设置为D3D12_BLEND_SRC_COLOR,在将混合运算符设置为D3D12_BLEND_OP_ADD,此时混合方程为
10.5.4、透明混合
源alpha分量a是一种可以控制源像素不透明度的百分比的分量,假设我们希望基于源像素的不透明度,将源像素和目标像素进行混合,为了实现此效果,我们可以将源混合因子设置为D3D12_BLEND_SRC_ALPHA,目标混合因子设置为D3D12_BLEND_INV_SRC_ALPHA,并且将混合运算符设置为D3D12_BLEND_OP_ADD,此时混合方程为:
例如,a = 0.25,则源像素的不透明度为25%,也就是说,源像素和目标像素进行混合的时候,最终颜色将会由25%的源像素和75%的目标像素组成(源像素会位于目标像素的后侧)。
如果使用上述透明混合方法,我们需要考虑物体的绘制顺序,首先要绘制无序混合处理的物体,然后根据混合物体到摄像机的距离对他们进行排序,最后按由远到近的顺序通过混合的方式依次绘制这些物体。因为每一个透明的物体都应该要可以看到它后面的物体,而进行混合运算时,每一个物体都会和其后所有的物体进行混合运算,因此我们需要将透明物体后面的物体的像素都事先写入后台缓冲区中,然后将透明物体的源像素与其后面的目标像素进行混合。
105.5、混合与深度缓冲区
在使用加法/减法/乘法运算进行混合时,会涉及到深度测试这一问题,这里将会通过加法混合进行讲解。
如果要使用加法混合来渲染一个物体集合S,且不希望S中的物体相互遮挡,这时我们不希望在S中开启深度测试,如果在S中开启深度测试,且没有从后到前进行绘制,那么S中的俩个物体如果存在遮挡,靠后的像素片段将会被丢弃,这意味该物体的像素颜色将不会被累加到混合求和的结果中。
为了解决上面的问题,我们可以在渲染S中的物体的时候,通过禁止向深度缓冲区的写操作来禁用S中的物体之间的加法测试,因为向深度缓冲区的写操作被禁止之后,物体的深度信息将不会写入深度缓冲区中,从而避免了深度测试。