DirectX11学习笔记六 Blending混合

我们从地形和板条箱开始渲染场景,先绘制地形,再绘制板条箱,使地形和板条箱的像素依次存入后台缓冲区。然后使用混合(blending),将水体绘制到后台缓冲区,使水体像素和后台缓冲区中的地形、板条箱像素融为一体。通过一方式,我们可以透过水体看到地形和板条箱。本章我们主要讲解混合技术,它可以将当前的光栅化像素(也称为源像素)与后台缓冲区中的像素(也称为目标像素)混合(融合)在一起。该技术通常用于渲染半透明物体,比如水和玻璃。
在这里插入图片描述
在输出合并(output merger,简称OM)阶段中,某些像素片段会被丢弃(例如,未能通过深度测试或模板测试)。未丢弃的像素片段会被写入后台缓冲区。混合(blending)工作是在该阶段中完成的,一个像素可以与后台缓冲区中的当前像素进行混合,并以混合后的值作为该像素的最终颜色。某些特殊效果,比如透明度,就是通过混合来实现的。
透明效果在DirectX中时需要PS和api同时设置才能实现的,如果在c++的api中不开启混合,那么即使在PS中设置了颜色的a值,最终渲染时a也会被丢弃。
在PS里的a值计算很好理解,就是类似

	float4 litColor = texColor * (ambient + diffuse) + spec;
    litColor.a = texColor.a * g_Material.Diffuse.a;
    return litColor;

将最后计算出来的纹理透明度和材质透明度做乘法。
但是实际上,某个像素的颜色算出后会存到后备缓冲区中,通过z深度与其他像素排序,并计算透明度叠加效果,比如可以透过透明的玻璃看到小明,但小明身后的墙壁是看不到的(墙壁没通过深度测试,直接被丢弃),例如,我们设Csrc = 玻璃,Cdst = 小明,C = 最终颜色。设Csrc为当前正在进行光栅化处理的第ij个像素(源像素)的颜色,Cdst为后台缓冲区中的第ij个像素(目标像素)的颜色。当不使用混合时,Csrc会覆盖Cdst的值(假设该值已通过深度/模板测试)并成为后台缓冲区中的第ij个像素的新颜色。但是当使用混合时,Csrc和Cdst会被组合为一个新颜色C并覆盖Cdst的值(即,混合颜色C会成为后台缓冲区中的第ij个像素的新颜色)
还需要在API中设置前后两个像素叠加的方式。所以渲染次序也需要修改,我们先渲染不透明的物体,而对于透明的物体,则按照从远到近的次序渲染。对于本次实例,我们先渲染的篱笆盒,其次是水,因为篱笆盒有一部分在水下,如果反过来,则先渲染水再渲染篱笆盒,因为篱笆盒在水下的部分深度比水面大,无法通过深度测试而被丢弃。所以在绘制透明物体前,要么关闭深度测试,要么对物体到摄像机的先后顺序进行排序,并按从后到前的顺序进行绘制。
下面就要引入混合方程的概念,这些都是API帮我们处理的,不需要修改hlsl。

混合方程

首先Csrc和Cdst都是float3,即rgb
基本方程为
C = Csrc * Fsrc op1 Cdst * Fdst
A = Asrc * Fsrc op2 Adst * Fdst
其中,*表示分量乘法rgb都要算;op则表示一个未知运算符,这个运算符具体是什么,通过api来设定;Fsrc和Fdst,混合因子,跟op一样,也要去api设定。
注意:我们很少混合alpha分量,多数情况下,我们只是对RGB分量进行混合。尤其是在本书中,我们从不混合源alpha值和目标alpha值,虽然alpha值会参与我们的RGB混合操作。这主要是因为我们暂时用不到后台缓冲区中的alpha值。只有当某些算法需要使用目标alpha值时,后台缓冲区中的alpha值才有用。以小明为例,窗户为透明,需要将玻璃的颜色混合到玻璃后的小明身上,使小明出现一种被玻璃阻挡的感觉,但透明度不需要混合,因为小明不是透明的,也不需要透明,其A值永远是1。

渲染状态

RenderStates实际上就是将光栅化阶段描述、混合阶段描述和采样器等等需要额外创建的资源的创建代码与GameApp分隔开,整理下流程。

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

	static bool IsInit();

	static void InitAll(ID3D11Device * device);
	// 使用ComPtr无需手工释放

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

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

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

这里要引入两个新的类

ID3D11RasterizerState

参考官方文档,用来描述光栅化阶段的操作,创建的套路跟缓冲区,采样器什么的差不多,也是需要一个D3D11_RASTERIZER_DESC的描述对象,不如直接看代码。

D3D11_RASTERIZER_DESC rasterizerDesc;
	ZeroMemory(&rasterizerDesc, sizeof(rasterizerDesc));

	// 线框模式
	rasterizerDesc.FillMode = D3D11_FILL_WIREFRAME;
	rasterizerDesc.CullMode = D3D11_CULL_NONE;
	rasterizerDesc.FrontCounterClockwise = false; 
	rasterizerDesc.DepthClipEnable = true;
	HR(device->CreateRasterizerState(&rasterizerDesc, RSWireframe.GetAddressOf()));

	// 无背面剔除模式
	rasterizerDesc.FillMode = D3D11_FILL_SOLID;
	rasterizerDesc.CullMode = D3D11_CULL_NONE;
	rasterizerDesc.FrontCounterClockwise = false;
	rasterizerDesc.DepthClipEnable = true;
	HR(device->CreateRasterizerState(&rasterizerDesc, RSNoCull.GetAddressOf()));

D3D11_RASTERIZER_DESC的定义

typedef struct D3D11_RASTERIZER_DESC
    {
    D3D11_FILL_MODE FillMode;  // 填充模式
    D3D11_CULL_MODE CullMode;  // 裁剪模式
    BOOL FrontCounterClockwise;  //false表示顶点序列按顺时针排列的为正面,反之则反之
    INT DepthBias;  // https://docs.microsoft.com/zh-cn/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
    FLOAT DepthBiasClamp;  // 网址同上
    FLOAT SlopeScaledDepthBias;  // 网址同上
    BOOL DepthClipEnable;  // 是否开启深度测试
    BOOL ScissorEnable;  // 是否开启矩形裁剪
    BOOL MultisampleEnable;  // 设置是否开启四重采样
    BOOL AntialiasedLineEnable; // 设置是否开启反走样,仅multisampleEnable为false有效
    } 	D3D11_RASTERIZER_DESC;
FillMode描述
D3D11_FILL_WIREFRAME线框模式
D3D11_FILL_SOLID填充模式
CullMode描述
D3D11_CULL_NONE总是渲染三角形
D3D11_CULL_FRONT不渲染正面对着摄像机的三角形
D3D11_CULL_BACK不渲染背面对着摄像机的三角形(默认)

创建并绑定完对应的ID3D11RasterizerState,最后去draw前绑定

		m_pd3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());

绑定到渲染管线就可以了。

ID3D11BlendState

参考官方文档,创建混合状态需要用到D3D11_BLEND_DESC

// 调用 ******************
	// 初始化混合状态
	//
	D3D11_BLEND_DESC blendDesc;
	ZeroMemory(&blendDesc, sizeof(blendDesc));
	auto& rtDesc = blendDesc.RenderTarget[0];
	// Alpha-To-Coverage模式
	blendDesc.AlphaToCoverageEnable = true;
	blendDesc.IndependentBlendEnable = false;
	rtDesc.BlendEnable = false;
	rtDesc.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
	HR(device->CreateBlendState(&blendDesc, BSAlphaToCoverage.GetAddressOf()));

	// 透明混合模式
	// Color = SrcAlpha * SrcColor + (1 - SrcAlpha) * DestColor 
	// Alpha = SrcAlpha
	blendDesc.AlphaToCoverageEnable = false;
	blendDesc.IndependentBlendEnable = false;
	rtDesc.BlendEnable = true;
	rtDesc.SrcBlend = D3D11_BLEND_SRC_ALPHA;
	rtDesc.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	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, BSTransparent.GetAddressOf()));
	
	// 无颜色写入混合模式
	// Color = DestColor
	// Alpha = DestAlpha
	rtDesc.SrcBlend = D3D11_BLEND_ZERO;
	rtDesc.DestBlend = D3D11_BLEND_ONE;
	rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
	rtDesc.SrcBlendAlpha = D3D11_BLEND_ZERO;
	rtDesc.DestBlendAlpha = D3D11_BLEND_ONE;
	rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;
	HR(device->CreateBlendState(&blendDesc, BSNoColorWrite.GetAddressOf()));

D3D11_BLEND_DESC的定义

typedef struct D3D11_BLEND_DESC
{
    BOOL AlphaToCoverageEnable;  // 是否使用alpha-to-coverage来作为渲染像素的多重采样技术,默认关闭,它是一种多重采样技术,在渲染植物的叶子或铁丝网纹理时非常有用 默认值:False https://blog.csdn.net/butwang/article/details/6257076
    //使用alpha-to-coverage需要开启多重采样(即,后台和深度缓冲创建时需要开启多重采样)
    BOOL IndependentBlendEnable;  // Direct3D 11支持同时绘制到8个渲染目标。当这个标志设置为true时,表示可以在不同的渲染目标上进行不同的混合处理(不同的混合因子、混合操作,混合开启/关闭等)。如果设置为false,则表示所有渲染目标都使用D3D11_BLEND_DESC::RenderTarget数组中第一个元素的混合状态,多重渲染目标用于高级的算法,目前为止,我们一次只使用一个渲染目标。 默认值:False
    D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[ 8 ];  // 包含8个D3D11_RENDER_TARGET_BLEND_DESC元素的数组,第i个元素描述了第i个多重渲染目标的混合方式。如果IndependentBlendEnable设置为false,则所有的渲染目标都使用RenderTarget[0]进行混合
} 	D3D11_BLEND_DESC;
typedef struct D3D11_RENDER_TARGET_BLEND_DESC
{
    BOOL BlendEnable;             // 是否使用混合 默认值:False
    D3D11_BLEND SrcBlend;         // Fsrc 默认值:D3D11_BLEND_ONE
    D3D11_BLEND DestBlend;        // Fdst 默认值Default:D3D11_BLEND_ZERO
    D3D11_BLEND_OP BlendOp;       // 颜色混合运算符 默认值:D3D11_BLEND_OP_ADD
    D3D11_BLEND SrcBlendAlpha;    // Asrc 默认值:D3D11_BLEND_ONE
    D3D11_BLEND DestBlendAlpha;   // Adst 默认值:D3D11_BLEND_ZERO
    D3D11_BLEND_OP BlendOpAlpha;  // Alpha混合运算符 默认值:D3D11_BLEND_OP_ADD
    UINT8 RenderTargetWriteMask;  // D3D11_COLOR_WRITE_ENABLE枚举类型来指定可以写入的颜色 默认值:D3D11_COLOR_WRITE_ENABLE_ALL
} 	D3D11_RENDER_TARGET_BLEND_DESC;

翻译翻译这些枚举的含义,原混合方程为
C = Csrc * Fsrc op1 Cdst * Fdst
混合系数Fsrc和Fdst用D3D11_BLEND定义

D3D11_BLEND描述
D3D11_BLEND_ZEROF = 0
D3D11_BLEND_ONEF = 1
D3D11_BLEND_SRC_COLORF = Csrc
D3D11_BLEND_INV_SRC_COLORF = 1 - Csrc
D3D11_BLEND_SRC_ALPHAAsrc
D3D11_BLEND_INV_SRC_ALPHAF = 1 − Asrc
D3D11_BLEND_DEST_ALPHAF = Adst
D3D11_BLEND_INV_DEST_ALPHAF = 1 – Adst
D3D11_BLEND_DEST_COLORF = Cdst
D3D11_BLEND_INV_DEST_COLORF = 1 – Cdst
D3D11_BLEND_SRC_ALPHA_SATF = sat(Asrc),sat()表示clamp(0,1)
D3D11_BLEND_BLEND_FACTORF 的值来自于ID3D11DeviceContext::OMSetBlendState方法的BlendFactor参数
D3D11_BLEND_INV_BLEND_FACTORF 的值来自于ID3D11DeviceContext::OMSetBlendState方法的BlendFactor参数,并设为1 - BlendFactor
D3D11_BLEND_SRC1_COLOR
D3D11_BLEND_INV_SRC1_COLOR
D3D11_BLEND_SRC1_ALPHA
D3D11_BLEND_INV_SRC1_ALPHA

混合操作op用D3D11_BLEND_OP定义

D3D11_BLEND_OP描述
D3D11_BLEND_OP_ADDC = Csrc ⊗ Fsrc + Cdst ⊗ Fdst
D3D11_BLEND_OP_SUBTRACTC = Cdst ⊗ Fdst - Csrc ⊗ Fsrc
D3D11_BLEND_OP_REV_SUBTRACTC = Csrc ⊗ Fsrc - Cdst ⊗ Fdst
D3D11_BLEND_OP_MINC = min(Csrc , Cdst)
D3D11_BLEND_OP_MAXC = max(Csrc , Cdst)

okay,后头再看一眼当前渲染水体使用的透明混合方程。

	rtDesc.SrcBlend = D3D11_BLEND_SRC_ALPHA;
	rtDesc.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	rtDesc.BlendOp = D3D11_BLEND_OP_ADD;
	rtDesc.SrcBlendAlpha = D3D11_BLEND_ONE;
	rtDesc.DestBlendAlpha = D3D11_BLEND_ZERO;
	rtDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD;

翻译出来就是

  • 对于颜色C
    Fsrc = Asrc
    Fdst = 1 - Asrc
    op = +
    所以颜色C的混合公式就是
    C = Csrc * Asrc + Cdst * (1 - Asrc); 标准的透明混合公式

  • 对于透明度A
    Fsrc = 1
    Fdst = 0
    op = +
    所以透明度A的混合公式就是
    A = Asrc * 1 + Adst * 0; 换句话说,就是只保留水体的透明度,丢弃水面下的物体的透明度。
    最终的Alpha值是多少并不影响前面的运算,因此可以设为任意值,这里设为源像素Alpha值:
    定义完之后调用即可

	m_pd3dImmediateContext->OMSetBlendState(RenderStates::BSTransparent.Get(), nullptr, 0xFFFFFFFF);
	```
	void ID3D11DeviceContext::OMSetBlendState( 
    ID3D11BlendState *pBlendState,  //混合状态对象的指针。
    const FLOAT BlendFactor,  //用于描述RGBA颜色向量的浮点数组。当混合因子指定为D3D11_BLEND_BLEND_FACTOR或D3D11_BLEND_INV_BLEND_FACTOR时,Direct3D将以该颜色向量作为混合系数。
    UINT SampleMask); //多重采样最多可以支持32个采样源。这个32位整数用于启用和禁用采样源。例如,当第5个二进制位设为0时,表示屏蔽第5个采样源。当然,如果实际使用的多重采样源数量少于5个,那么屏蔽第5个采样源是没有什么实际意义的。当应用程序只使用一个采样源时,该参数只有第1个二进制位有效(参见练习1)。通常,该参数以0xffffffff作为默认值,表示不屏蔽任何采样源。

篱笆盒

在这里插入图片描述
读取之后,在像素着色器中用clip函数抛弃掉几乎透明的部分。

// 提前进行裁剪,对不符合要求的像素可以避免后续运算
    float4 texColor = g_Tex.Sample(g_SamLinear, pIn.Tex);
    clip(texColor.a - 0.1f);//为避免精度误差,接近0的都算0

此外,为了能透过方块的某个面看到面后面的铁丝网,需要设置光栅化状态的CULLMODE设为D3D11_CULL_NONE,取消背面剔除,让背面朝摄像机的面也能渲染。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值