[DirectX12学习笔记] 混合

  • 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。

在场景里加入透明效果和雾效


混合状态

混合遵循以下等式

其中 C C C是最终得到的颜色, C s r c C_{src} Csrc是新来到的fragment的颜色, C d s t C_{dst} Cdst是back buffer里已有的像素的颜色, F d s t F_{dst} Fdst F s r c F_{src} Fsrc是两个系数,两个乘是component wise的乘法,中间的运算符号可以由我们规定。

Alpha通道也满足下式
在这里插入图片描述
Alpha和RGB的Operation和Factor都可以分别各自选取。

以下是支持的一些Operation
[DirectX12学习笔记] 混合_第1张图片
此外还有逻辑运算的Operation,但是用逻辑运算就不能用以上的这些运算符号了,在设置blend state的时候要设置一下LogicOpEnable,具体有哪些逻辑算符不再罗列。

然后以下是支持的一些Factor

D3D12_BLEND_ZERO: F = (0, 0, 0) and F = 0
D3D12_BLEND_ONE: F = (1, 1, 1) and F = 1
D3D12_BLEND_SRC_COLOR: F = (rs, gs, bs)
D3D12_BLEND_INV_SRC_COLOR: Fsrc = (1 − rs, 1 − gs, 1 − bs)
D3D12_BLEND_SRC_ALPHA: F = (as, as, as) and F = as
D3D12_BLEND_INV_SRC_ALPHA: F = (1 − as, 1 − as, 1 − as) and F = (1 − as)
D3D12_BLEND_DEST_ALPHA: F = (ad, ad, ad) and F = ad
D3D12_BLEND_INV_DEST_ALPHA: F = (1 − ad, 1 − ad, 1 − ad) and F = (1 − ad)
D3D12_BLEND_DEST_COLOR: F = (rd, gd, bd)
D3D12_BLEND_INV_DEST_COLOR: F = (1 − rd, 1 − gd, 1 − bd)
D3D12_BLEND_SRC_ALPHA_SAT: F = (a′s, a′s, a′s) and F = a′s where a′s = clamp(as, 0, 1)
D3D12_BLEND_BLEND_FACTOR: F = (r, g, b) and F = a, where the color (r, g, b, a) is supplied to the second parameter of the ID3D12GraphicsCommandList::OMSetBlendFactor method. This allows you to specify the blend factor color to use directly; however, it is constant until you change the blend state.
D3D12_BLEND_INV_BLEND_FACTOR: F = (1 − r, 1 − g, 1 − b) and F =1 − a, where the color (r, g, b, a) is supplied by the second parameter of the ID3D12GraphicsCommandList::OMSetBlendFactor method. This allows you to specify the blend factor color to use directly; however, it is constant until you change the blend state.

然后是Blend State,Blend State在创建PSO的时候设置,需要指定以下Desc

typedef struct D3D12_BLEND_DESC {
BOOL AlphaToCoverageEnable; // Default: False
BOOL IndependentBlendEnable; // Default: False
D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;

其中AlphaToCoverageEnable是制定一种对植被等渲染的时候有用的一种多重采样技术,默认是关的。
第二个参数IndependentBlendEnable是允许各个RenderTarget使用不同的混合方式(最多八个),如果关闭,则八个RenderTarget都用数组里的0号元素的混合方式。
第三个参数是混合方式数组,最多8个,注意这里虽然名字叫RenderTarget但是类型其实是D3D11_RENDER_TARGET_BLEND_DESC,接下来介绍这个DESC。

typedef struct D3D12_RENDER_TARGET_BLEND_DESC
{
BOOL BlendEnable; // Default: False
BOOL LogicOpEnable; // Default: False
D3D12_BLEND SrcBlend; // Default: D3D12_BLEND_ONE
D3D12_BLEND DestBlend; // Default: D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOp; // Default:
D3D12_BLEND_OP_ADD
D3D12_BLEND SrcBlendAlpha; // Default:
D3D12_BLEND_ONE
D3D12_BLEND DestBlendAlpha; // Default:
D3D12_BLEND_ZERO
D3D12_BLEND_OP BlendOpAlpha; // Default:
D3D12_BLEND_OP_ADD
D3D12_LOGIC_OP LogicOp; // Default:D3D12_LOGIC_OP_NOOP
UINT8 RenderTargetWriteMask; // Default:D3D12_COLOR_WRITE_ENABLE_ALL
} D3D12_RENDER_TARGET_BLEND_DESC;

前两个参数最多只能有一个为true,表示使用混合,两个都为false则关闭混合,第一个是普通的混合,第二个是前面提到的使用逻辑运算的混合,然后接下来几个参数是F和Op的设置,具体有哪些取值前面已经提到,然后接下来是Alpha的F和Op,然后最后一个参数可以注意下,这个是写入的Mask,取值如下

typedef enum D3D12_COLOR_WRITE_ENABLE {
D3D12_COLOR_WRITE_ENABLE_RED = 1,
D3D12_COLOR_WRITE_ENABLE_GREEN = 2,
D3D12_COLOR_WRITE_ENABLE_BLUE = 4,
D3D12_COLOR_WRITE_ENABLE_ALPHA = 8,
D3D12_COLOR_WRITE_ENABLE_ALL =
	( D3D12_COLOR_WRITE_ENABLE_RED |D3D12_COLOR_WRITE_ENABLE_GREEN |
		D3D12_COLOR_WRITE_ENABLE_BLUE |D3D12_COLOR_WRITE_ENABLE_ALPHA )
} D3D12_COLOR_WRITE_ENABLE;

比如有的时候只想写入alpha,不想写入rgb,那就可以通过设置这个mask参数来达到效果。或者有的时候只想写入深度缓冲,那么就把这个mask设置成0即可。
一个设置blend state的示例如下

// Start from non-blended 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”])));

Clip

在shader里可以用clip(x)来裁剪像素,如果x<0,那么shader会直接舍弃掉这个像素然后返回,之后的代码也不会运行。这个函数只能在像素着色器里被调用,例如:

float4 diffuseAlbedo = gDiffuseMap.Sample(gsamAnisotropicWrap, pin.TexC) * gDiffuseAlbedo;
	
#ifdef ALPHA_TEST
	// Discard pixel if texture alpha < 0.1.  We do this test as soon 
	// as possible in the shader so that we can potentially exit the
	// shader early, thereby skipping the rest of the shader code.
	clip(diffuseAlbedo.a - 0.1f);
#endif

因为采样的时候会有filtering所以贴图会稍微模糊一点,alpha通道也会是一个插值的结果,所以想要滤掉alpha为0的像素,最好给一个小点的判断范围,比如这里写的是alpha小于0.1就裁掉。
此外,这里需要定义ALPHA_TEST才执行这个裁剪步骤,我们可以单独给一个PSO用来渲染有alpha测试的物体,这个PSO中我们需要定义一个定义了ALPHA_TEST的shader版本,并且将关掉back culling(因为有透明所以能够看到物体内部了,因此大多数情况下我们希望能看到背面),C++中可以这样写:

	const D3D_SHADER_MACRO alphaTestDefines[] =
	{
		"FOG", "1",
		"ALPHA_TEST", "1",
		NULL, NULL
	};

	mShaders["alphaTestedPS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", alphaTestDefines, "PS", "ps_5_0");

	...

	//
	// PSO for alpha tested objects
	//

	D3D12_GRAPHICS_PIPELINE_STATE_DESC alphaTestedPsoDesc = opaquePsoDesc;
	alphaTestedPsoDesc.PS = 
	{ 
		reinterpret_cast<BYTE*>(mShaders["alphaTestedPS"]->GetBufferPointer()),
		mShaders["alphaTestedPS"]->GetBufferSize()
	};
	alphaTestedPsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
	ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&alphaTestedPsoDesc, IID_PPV_ARGS(&mPSOs["alphaTested"])));

雾气效果

一个简单的雾气效果可以这样写
[DirectX12学习笔记] 混合_第2张图片
其中s是雾的浓度,从start位置开始直到start+range位置s从0线性增长到1
[DirectX12学习笔记] 混合_第3张图片
写进shader里,代码如下:

// Constant data that varies per pass.
cbuffer cbPass : register(b1)
{
	···
	// Allow application to change fog parameters once per frame.
	// For example, we may only use fog for certain times of day.
	float4 gFogColor;
	float gFogStart;
	float gFogRange;
	float2 cbPerObjectPad2;
	···
};
float4 PS(VertexOut pin) : SV_Target
{
    ···
	#ifdef FOG
		float fogAmount = saturate((distToEye - gFogStart) / gFogRange);
		litColor = lerp(litColor, gFogColor, fogAmount);
	#endif
	···
}

然后和刚才的alpha test一样在读取shader的时候把fog定义上即可

透明度Demo

修改之前的demo并加入透明效果和雾气,要做这么几件事情:

读shader的时候,分成做ALPHA_TEST的和不做ALPHA_TEST的(虽然这个demo里全部都定义的话效果也一样,但是真正做大点的项目的时候还是要分的)

void BlendApp::BuildShadersAndInputLayout()
{
	const D3D_SHADER_MACRO defines[] =
	{
		"FOG", "1",
		NULL, NULL
	};

	const D3D_SHADER_MACRO alphaTestDefines[] =
	{
		"FOG", "1",
		"ALPHA_TEST", "1",
		NULL, NULL
	};

	mShaders["standardVS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", nullptr, "VS", "vs_5_0");
	mShaders["opaquePS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", defines, "PS", "ps_5_0");
	mShaders["alphaTestedPS"] = d3dUtil::CompileShader(L"Shaders\\Default.hlsl", alphaTestDefines, "PS", "ps_5_0");
	
    mInputLayout =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    };
}

定义一个render layer和render item layer,然后build render item的时候没定义一个render item都把物体放到对应的layer里去

enum class RenderLayer : int
{
	Opaque = 0,
	Transparent,
	AlphaTested,
	Count
};

···

// Render items divided by PSO.
std::vector<RenderItem*> mRitemLayer[(int)RenderLayer::Count];

···

mRitemLayer[(int)RenderLayer::Transparent].push_back(wavesRitem.get());

然后对opaque、alpha tested和transparent分别定义三个不同的PSO

void BlendApp::BuildPSOs()
{
    D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;

	//
	// PSO for opaque objects.
	//
    ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
	opaquePsoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
	opaquePsoDesc.pRootSignature = mRootSignature.Get();
	opaquePsoDesc.VS = 
	{ 
		reinterpret_cast<BYTE*>(mShaders["standardVS"]->GetBufferPointer()), 
		mShaders["standardVS"]->GetBufferSize()
	};
	opaquePsoDesc.PS = 
	{ 
		reinterpret_cast<BYTE*>(mShaders["opaquePS"]->GetBufferPointer()),
		mShaders["opaquePS"]->GetBufferSize()
	};
	opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
	opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
	opaquePsoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
	opaquePsoDesc.SampleMask = UINT_MAX;
	opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
	opaquePsoDesc.NumRenderTargets = 1;
	opaquePsoDesc.RTVFormats[0] = mBackBufferFormat;
	opaquePsoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	opaquePsoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	opaquePsoDesc.DSVFormat = mDepthStencilFormat;
    ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&opaquePsoDesc, IID_PPV_ARGS(&mPSOs["opaque"])));

	//
	// PSO for transparent objects
	//

	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 for alpha tested objects
	//

	D3D12_GRAPHICS_PIPELINE_STATE_DESC alphaTestedPsoDesc = opaquePsoDesc;
	alphaTestedPsoDesc.PS = 
	{ 
		reinterpret_cast<BYTE*>(mShaders["alphaTestedPS"]->GetBufferPointer()),
		mShaders["alphaTestedPS"]->GetBufferSize()
	};
	alphaTestedPsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
	ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&alphaTestedPsoDesc, IID_PPV_ARGS(&mPSOs["alphaTested"])));
}

最后在渲染的时候,按顺序分别渲染opaque、alpha tested和transparent这三个层

DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);

	mCommandList->SetPipelineState(mPSOs["alphaTested"].Get());
	DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::AlphaTested]);

	mCommandList->SetPipelineState(mPSOs["transparent"].Get());
	DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Transparent]);

注意这里我们没有对透明物体进行排序,因为只有一个水面是透明的,如果有很多个透明物体的话,按理说要从深到浅排序并依次渲染。但是,还有更复杂的情况,那就是同一个物体自我纠缠或者多个物体相互纠缠的情况,这样的话按物体排序也没用,解决办法有这么几种:
1.最简单的情况,这个物体只需要对背后的物体透明,不需要对自己透明,那么开深度写入,分两个pass,第一个pass只写入深度,第二个pass正常渲染即可。
2.alpha to coverage,基于MSAA,其实相当于把他当成非透明物体来渲染。只是最后输出的颜色会乘以alpha值。
3.OIT(Order-Independent Transparency),可能需要很多RenderTarget。可以看这个https://github.com/candycat1992/OIT_Lab
4.离线排序,把所有三角面排好序再渲染

最后得到的效果如图

你可能感兴趣的:(DirectX12学习笔记)