Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十章:混合

原文: Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十章:混合

代码工程地址:

https://github.com/jiabaodan/Direct12BookReadingNotes



学习目标

  1. 理解混合的工作原理和如何在D3D中使用它;
  2. 学习D3D支持的不同的混合模式;
  3. 学习Alpha组件是如何控制透明基元的;
  4. 学习如何通过HLSL的clip函数防止像素被绘制到后置缓冲中。


1 混合方程

C s r c C_{src} 为从像素着色器输出的ij个像素的颜色,令 C d s t C_{dst} 当前后置缓冲中第ij个像素的颜色。如果没有混合, C s r c C_{src} 将重写 C d s t C_{dst} 的颜色;但是增加混合后,它们将会按照下面的公式计算出新的颜色:
在这里插入图片描述
F s r c F_{src} F d s t F_{dst} 可以是第三节中的任意值。
上面的混合公式只用以计算RGB,Alpha组件使用的是一个分开的相似工程:
在这里插入图片描述
两个方程本质上是一样的,但是有时混合因子和计算方式是不一样的,所以将RGB和Alpha分开可以让我们将它们分开处理,这样就可以达到更多的效果。

混合中Alpha组件的使用频率要比RGB少很多,主要是因为我们不关心后置缓冲中的alpha值。后置缓冲中的alpha值只有在你的算法真正需要它的时候我们才会考虑它。



2 混合运算

混合运算可能是下面枚举中的一种:
在这里插入图片描述
在min/max运算中,混合因子会被无视。

这些运算也可以被应用于Alpha组件的计算,你也可以为RGB和Alpha设置不同的运算公式,比如:
在这里插入图片描述
最近D3D新加一个特性,可以为混合使用逻辑运算符,可以使用的逻辑运算如下:

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;

你不能同时使用传统的混合方程和逻辑运算;你只能选择一种。另外选择的逻辑运算必须是之前这只的渲染目标格式所支持的-----格式必须是UINT的,否则会有下面的报错:

D3D12 ERROR:
ID3D12Device::CreateGraphicsPipelineState: The
render target format at slot 0 is format
(R8G8B8A8_UNORM). This format does not support
logic ops. The Pixel Shader output signature
indicates this output could be written, and the
Blend State indicates logic op is enabled for this
slot. [ STATE_CREATION ERROR #678:
CREATEGRAPHICSPIPELINESTATE_OM_RENDER_TARGET_DOES_NOT_SUPPORT_D3D12 WARNING:
ID3D12Device::CreateGraphicsPipelineState: Pixel
Shader output ‘SV_Target0’ has type that is NOT
unsigned int, while the corresponding Output Merger
RenderTarget slot [0] has logic op enabled. This
happens to be well defined: the raw bits output
from the shader will simply be interpreted as UINT
bits in the blender without any data conversion.
This warning is to check that the application
developer really intended to rely on this behavior.
[ STATE_CREATION WARNING #677:
CREATEGRAPHICSPIPELINESTATE_PS_OUTPUT_TYPE_MISMATCH]


3 混合因子

下面列出一些基本混合因子的描述,还有一些高级的混合因子,可以查看SDK文档中的D3D12_BLEND枚举描述:

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)是作为第二个参数在ID3D12GraphicsCommandList::OMSetBlendFactor函数中设置。该值在你修改它之前是固定的。
上面所有的因子适用于RGB,对于Alpha组件,所有以_COLOR结尾的都不能使用。

我们可以通过下面的方式:

void ID3D12GraphicsCommandList::OMSetBlendFactor( const FLOAT BlendFactor[ 4 ]);

传递一个默认因子(1, 1, 1, 1)。



4 混合状态

混合状态是PSO的一部分,目前为止,我们都是使用默认混合状态(禁用混合):

D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;
ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
… 
opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);

为了定义一个非默认的混合状态,我们需要填充D3D12_BLEND_DESC结构:

typedef struct D3D12_BLEND_DESC {
	BOOL AlphaToCoverageEnable; // Default: False
	BOOL IndependentBlendEnable; // Default: False
	D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
	} D3D11_BLEND_DESC;
  1. AlphaToCoverageEnable:指定为true,启用alpha-to-coverage,是一个多重纹理映射技术,在绘制植物叶子等纹理的时候非常有用。它需要多重纹理映射是启用的。
  2. IndependentBlendEnable:D3D支持同时绘制8个渲染目标,所以设置为true是,可以支持对每个渲染目标使用不同的混合模式。如果设置为false,代表所有渲染目标只能使用D3D12_BLEND_DESC::RenderTarget数组中第一个混合模式来渲染。
  3. RenderTarget:8个D3D12_RENDER_TARGET_BLEND_DESC元素的数组,表示i个元素是第i个同时渲染目标的混合模式。如果IndependentBlendEnable设置为false,那么所有渲染目标使用RenderTarget[0]来混合。

D3D12_RENDER_TARGET_BLEND_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;
  1. BlendEnable:BlendEnable和LogicOpEnable不能同时设置为true;
  2. LogicOpEnable:BlendEnable和LogicOpEnable不能同时设置为true;
  3. SrcBlend:Src RGB因子;
  4. DestBlend:Dst RGB因子;
  5. BlendOp:D3D12_BLEND_OP枚举中的值,定义RGB混合公式;
  6. SrcBlendAlpha:Src Alpha因子;
  7. DestBlendAlpha:Dst Alpha 因子;
  8. BlendOpAlpha:D3D12_BLEND_OP枚举中的值,定义Alpha混合公式;
  9. LogicOp:D3D12_LOGIC_OP枚举中的值,定义逻辑运算的公式;
  10. RenderTargetWriteMask:下面一个或者多个标签的混合:
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;

这些标签控制哪些通道在混合后会被写入后置缓冲。
混合操作是需要更多的逐像素计算,所以是消耗较多的性能,所以只有当需要使用的时候打开它,使用完成后就关闭它。

下面的代码展示了创建和设置一个混合状态:

// 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"])));

对于其他PSO,你需要在程序初始化时创建它们,然后在需要使用的时候使用ID3D12GraphicsCommandList::SetPipelineState方法来切换它们。



5 例子

这节,我们看一些通过混合因子的组合达到的一些效果。我们只描述RGB,Alpha的思路类似。


5.1 不写入颜色

如果我们要保持原来的目标颜色不改变,这个是比较有用的。比如我们只希望写到深度/模板缓冲中,而不是后置缓冲。可以设置src因子D3D12_BLEND_ZERO,dst因子为D3D12_BLEND_ONE,然后混合运算为:D3D12_BLEND_OP_ADD:
在这里插入图片描述
还有一种其他的方法,设置D3D12_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask为0,那么没有颜色通道会被写入:


5.2 相加/相减

假设我们要将源像素和目标像素相加,我们可以将因子设置为D3D12_BLEND_ONE和D3D12_BLEND_ONE,运算设置为D3D12_BLEND_OP_ADD:
在这里插入图片描述
在这里插入图片描述
如果要相减,就把运算修改为D3D12_BLEND_OP_SUBTRACT:
在这里插入图片描述


5.3 相乘

可以设置因子为:D3D12_BLEND_ZERO和D3D12_BLEND_SRC_COLOR,然后运算设置为D3D12_BLEND_OP_ADD:
在这里插入图片描述
在这里插入图片描述


5.4 透明

如果要通过源像素的不透明度来混合,设置因子为:D3D12_BLEND_SRC_ALPHA和D3D12_BLEND_INV_SRC_ALPHA,然后运算设置为D3D12_BLEND_OP_ADD:
在这里插入图片描述
使用这种混合方法,我们就可以绘制透明物体了。但是这种方法,绘制的物体的顺序就很重要,我们使用下面的规则:
先绘制不需要混合的物体,然后对需要混合运算的物体根据距离摄像机的位置来排序,最后对需要使用混合的物体从后往前绘制。
对于5.1的混合方法,物体绘制顺序不重要,但是5.2和5.3绘制顺序还是很重要,我们还是要先绘制没有混合的物体。但是我们不需要对需要混合的物体排序,因为这些操作是满足交换律的,所以我们做N个相加/相减/相乘运算后,即使顺序不同,结果还是一致的。


5.5 混合和深度缓冲

当我们使用相加/相减/相乘进行混合时,有一个深度缓冲的问题。如果我们渲染一组需要混合的物体,我们假设它们是互相不阻挡的,因为如果它们互相阻挡(并且没有进行从后往前的排序渲染),那么被遮挡的像素在深度测试的时候就不会写入后置缓冲中,这样就导致混合无法准确计算。所以我们希望这组需要混合的物体不进行深度测试。
我们可以通过禁用写入深度缓冲的方法,在绘制这组需要混合的物体的时候禁用深度测试;但是对于不需要混合的物体,深度读取和测试还是启用的。



6 Alpha 通道

可以通过PS中返回Alpha值:

float4 PS(VertexOut pin) : SV_Target
{
	float4 diffuseAlbedo = gDiffuseMap.Sample(
	gsamAnisotropicWrap, pin.TexC) *
	gDiffuseAlbedo;
	…
	// Common convention to take alpha from diffuse
	albedo.
	litColor.a = diffuseAlbedo.a;
	return litColor;
}

添加Alpha通道的图像可以通过各种软件,比如PS,导出支持Alpha通道的文件格式,比如DDS。



7 裁剪像素

HLSL内置的clip(x)可以裁剪掉像素,让它不再进行后续的计算。该函数只能在PS中调用,当x小于0时,就会裁剪掉当前像素。它对下面这种类型的贴图就很适用:
在这里插入图片描述
在PS中,我们取出Alpha值进行裁剪判定:

float4 PS(VertexOut pin) : SV_Target
{
	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
	…
	// Common convention to take alpha from diffuse albedo.
	litColor.a = diffuseAlbedo.a;
	
	return litColor;
}

代码中我们只有定义了ALPHA_TEST的时候才进行裁剪,因为有时候我们不希望进行裁剪。并且Alpha会消耗性能,所以只有当我们需要的时候再开启它。
得到的这个结果也可以用混合来得到,但是这种方法更高效。第一,没有混合计算;第二,绘制顺序也不重要;第三,在像素着色器中更早的裁剪到像素,它就不会进行后续的像素着色器运算。

因为滤波器,有些Alpha会被进行模糊运算,所以他们可能不是直接等于0,而是接近0。

下图是本章混合的Demo,水流是通过透明混合实现的,箱子贴条是通过裁剪实现的。还有一个问题,因为箱子的部分像素被裁剪了,所以可以看到后面的面,那么就需要禁用背面消除:
在这里插入图片描述

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"])));


8 雾

我们实现雾效果的策略:定义一个雾的颜色,雾开始距离,雾的范围。那么一个三角形点的颜色就有正常的颜色和雾的颜色的加权平均数来计算出来:
在这里插入图片描述
在这里插入图片描述
参数S是0到1的一个值,它是相机位置和表面顶点的距离的函数:
在这里插入图片描述
saturate会把范围截取到0到1:
在这里插入图片描述
下面的着色器代码展示了如何实现雾效果,我们在像素阶段计算出光照颜色后,计算和差值:

// Defaults for number of lights.
#ifndef NUM_DIR_LIGHTS
	#define NUM_DIR_LIGHTS 3
#endif

#ifndef NUM_POINT_LIGHTS
	#define NUM_POINT_LIGHTS 0
#endif

#ifndef NUM_SPOT_LIGHTS
	#define NUM_SPOT_LIGHTS 0
#endif

// Include structures and functions for lighting.
#include "LightingUtil.hlsl"

Texture2D gDiffuseMap : register(t0);
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);

// Constant data that varies per frame.
cbuffer cbPerObject : register(b0)
{
	float4x4 gWorld;
	float4x4 gTexTransform;
};

// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
	float4x4 gView;
	float4x4 gInvView;
	float4x4 gProj;
	float4x4 gInvProj;
	float4x4 gViewProj;
	float4x4 gInvViewProj;
	float3 gEyePosW;
	float cbPerPassPad1;
	float2 gRenderTargetSize;
	float2 gInvRenderTargetSize;
	float gNearZ;
	float gFarZ;
	float gTotalTime;
	float gDeltaTime;
	float4 gAmbientLight;
	
	**// 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 cbPerPassPad2;
	
	// Indices [0, NUM_DIR_LIGHTS) are directional lights;
	// indices [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;
	// indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS,
	// NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)
	// are spot lights for a maximum of MaxLights per object.
	Light gLights[MaxLights];
};

cbuffer cbMaterial : register(b2)
{
	float4 gDiffuseAlbedo;
	float3 gFresnelR0;
	float gRoughness;
	float4x4 gMatTransform;
};

struct VertexIn
{
	float3 PosL : POSITION;
	float3 NormalL : NORMAL;
	float2 TexC : TEXCOORD;
};

struct VertexOut
{
	float4 PosH : SV_POSITION;
	float3 PosW : POSITION;
	float3 NormalW : NORMAL;
	float2 TexC : TEXCOORD;
};

VertexOut VS(VertexIn vin)
{
	VertexOut vout = (VertexOut)0.0f;
	
	// Transform to world space.
	float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
	vout.PosW = posW.xyz;
	
	// Assumes nonuniform scaling; otherwise, need to use inverse-transpose
	// of world matrix.
	vout.NormalW = mul(vin.NormalL, (float3x3)gWorld);
	
	// Transform to homogeneous clip space.
	vout.PosH = mul(posW, gViewProj);
	
	// Output vertex attributes for interpolation across triangle.
	float4 texC = mul(float4(vin.TexC, 0.0f, 1.0f), gTexTransform);
	vout.TexC = mul(texC, gMatTransform).xy;
	
	return vout;
}

float4 PS(VertexOut pin) : SV_Target
{
	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
	
	// Interpolating normal can unnormalize it, so renormalize it.
	pin.NormalW = normalize(pin.NormalW);
	
	// Vector from point being lit to eye.
	float3 toEyeW = gEyePosW - pin.PosW;
	float distToEye = length(toEyeW);
	toEyeW /= distToEye; // normalize
	
	// Light terms.
	float4 ambient = gAmbientLight*diffuseAlbedo;
	const float shininess = 1.0f - gRoughness;
	Material mat = { diffuseAlbedo, gFresnelR0, shininess };
	float3 shadowFactor = 1.0f;
	float4 directLight = ComputeLighting(gLights, mat, pin.PosW, pin.NormalW, toEyeW, shadowFactor);
	float4 litColor = ambient + directLight;
	
	**#ifdef FOG
		float fogAmount = saturate((distToEye - gFogStart) / gFogRange);
		litColor = lerp(litColor, gFogColor, fogAmount);
	#endif**
	
	// Common convention to take alpha from diffuse albedo.
	litColor.a = diffuseAlbedo.a;
	
	return litColor;
}

一些场景可能不需要雾,所以我们在编译Shader的时候,可选择的FOG宏定义。在我们的Demo中,我们增加D3D_SHADER_MACRO到CompileShader函数:

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

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


9 总结

  1. 混合技术是可以让当前像素和在后置缓冲中像素以特定的方式进行混合的技术;
  2. 混合公式为(RGB和Alpha计算是分开的):
    在这里插入图片描述
  3. F是混合因子,是D3D12_BLEND枚举中的类型,对于Alpha计算,以_COLOR结尾的不能使用;
  4. 源alpha数据来自于漫反射材质,在我们的框架中漫反射材质由纹理贴图定义,Alpha值由Alpha通道定义;
  5. HLSL的Clip(x)函数可以剪切像素,不进行后续的像素着色器运算 ,该函数只能在像素着色器中进行调用,当X小于0是生效,它可以用来优化像素着色器;
  6. 使用雾化效果可以修改天气效果和大气层透视。隐藏遥远的渲染物体和屏蔽突现的问题。在我们的线性雾化模型中,我们定义雾的颜色,开始位置和雾的范围,顶点的颜色有正常的光效效果和雾的颜色进行加权平均值来计算:
    在这里插入图片描述


10 练习题

1. 尝试不同的混合因子:
在BuildPSOs()函数中修改下面代码即可:

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;

2. 修改Demo为先绘制水,解释结果:
修改Draw函数中的代码顺序即可:

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

// 绘制山脉
mCommandList->SetPipelineState(mPSOs["opaque"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);

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

在这里插入图片描述
没有产生透明效果,是因为先绘制的水流,而后置缓冲中还没有像素,导致无法进行正确的混合。

5. 修改Demo混合渲染状态中,不写入红色和绿色通道:
修改BuildPSOs函数中代码即可:

transparencyBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_BLUE;

在这里插入图片描述

猜你喜欢

转载自www.cnblogs.com/lonelyxmas/p/10817161.html