第九章 混合

观察图9.1 我们开始渲染帧,首先绘制地形,然后是木箱,以便地形和箱子像素位于后台缓冲区。然后,我们使用混合将水面绘制到后部缓冲区,使得水像素与地形混合,并在后部缓冲区中创建像素,以便地形和箱子通过水面显示。在本章中,我们将研究混合技术,它们允许我们将当前正在栅格化的像素(所谓的源像素)与之前栅格化的像素混合(组合)到后台缓冲区(所谓的目标像素)。除此之外,这种技术使我们能够渲染诸如水和玻璃之类的半透明物体。

NOTE:为了讨论,我们特别提到了后台缓冲区作为渲染目标。不过,我们稍后会显示,我们也可以渲染“屏幕外”渲染目标。混合适用于这些渲染目标相同,目标像素是以前栅格化到这些屏幕渲染目标的像素值。

学习目标:
1.了解混合是如何工作的以及其D3D实现。
2.了解Direct3D支持的不同混合模式。
3.了解如何使用alpha组件来控制基元的透明度。
4.学习如何通过使用HLSL剪辑函数来防止像素被完全绘制到后台缓冲区。

9-1
图9.1 一个半透明的水面。

9.1混合方程式

设Csrc为当前正在栅格化的第ij个像素(源像素)的像素着色器的颜色输出,Cdst 为当前位于后台缓冲区(目标像素)上的第ij个像素的颜色。没有混合,Csrc 会覆盖Cdst (假设它通过深度/模板测试)并成为第ij个后缓冲区像素的新颜色。但是,在混合CsrcCdst 混合在一起以获得将重写Cdst 的新颜色C(即混合颜色C将被写入后缓冲器的第ij个像素)。Direct3D使用以下混合公式来混合源和目标像素颜色:

C=CsrcFsrcCdstFdst

Fsrc (源混合因子)和Fdst (目标混合因子)的颜色可以是§9.3中描述的任何值,它们允许我们以各种方式修改原始的源和目标像素,从而实现不同的效果。⊗运算符表示在§5.3.1中定义的颜色矢量的分量乘法; ⊞运算符可以是§9.2中定义的任何二进制运算符。

前面的混合等式仅适用于颜色的RGB分量。alpha分量实际上是由一个单独的类似的方程来处理的:

A=AsrcFsrcAdstFdst

该公式基本相同,但混合因子和二元运算可能不同。 将RGB与alpha分离的动机是简单的,以便我们可以独立处理它们,因此也就不同。

NOTE:混合alpha组件比混合RGB组件要少得多。 这主要是因为我们不关心后台缓冲区的alpha值。 如果您有一些算法需要目标Alpha值,则后台缓冲区Alpha值才是重要的。

9.2混合操作

混合等式中使用的二元运算符⊞可以是下列之一:

typedef enum D3D11_BLEND_OP
{
    D3D11_BLEND_OP_ADD = 1,
    D3D11_BLEND_OP_SUBTRACT = 2,
    D3D11_BLEND_OP_REV_SUBTRACT = 3,
    D3D11_BLEND_OP_MIN = 4,
    D3D11_BLEND_OP_MAX = 5,
} D3D11_BLEND_OP;

NOTE:在最小/最大操作中忽略混合因子。

这些相同的算子也适用于α混合方程。 另外,可以为RGB和alpha指定不同的运算符。例如,可以添加两个RGB项,但是可以减去两个alpha项:

C=CsrcFsrc+CdstFdstA=AsrcFsrcAdstFdst

9.3混合因素

通过为源和目标混合因子以及不同的混合算子设置不同的组合,可以实现数十种不同的混合效果。我们将在第9.5节中举例说明一些组合,但是你需要尝试与其他人一起来了解他们所做的事情。以下列表描述了适用于FsrcFdst 的基本混合因子。 请参阅SDK文档中的D3D11_BLEND枚举类型,了解一些其他高级混合因子。 让Csrc=(rs,gs,bs)Asrc=as (RGBA 值从像素着色器输出),Cdst=(rd,gd,bd)Adst=ad (已经存储在渲染目标中的RGBA值),FFsrcFdstFFsrcFdst ,我们有:
D3D11_BLEND_ZERO: F = (0,0,0) and F = 0
D3D11_BLEND_ONE: F = (1,1,1) and F = 1
D3D11_BLEND_SRC_COLOR: F=(rs,gs,bs)
D3D11_BLEND_INV_SRC_COLOR: F=(1rs,1gs,1bs)
D3D11_BLEND_SRC_ALPHA: F=(as,as,as)andF=as
D3D11_BLEND_INV_SRC_ALPHA: F=(1as,1as,1as)andF=1as
D3D11_BLEND_DEST_ALPHA: F=(ad,ad,ad)andF=ad
D3D11_BLEND_INV_DEST_ALPHA: F=(1ad,1ad,1ad)andF=1ad
D3D11_BLEND_DEST_COLOR: F=(rd,gd,bd)
D3D11_BLEND_INV_DEST_COLOR: F=(1rd,1gd,1bd)
D3D11_BLEND_SRC_ALPHA_SAT: F=(as,as,as)andF=as 其中as=clamp(as,0,1)
D3D11_BLEND_BLEND_FACTOR:F=rgbF=a ,其中颜色rgba 被提供给ID3D11DeviceContext :: OMSetBlendState方法(§9.4)的第二个参数。 这使您可以指定直接使用的混合因子颜色;但是,直到您更改混合状态为止,颜色才是常量。
D3D11_BLEND_INV_BLEND_FACTOR: F=1r1g1bF=1a ,其中颜色rgbaID3D11DeviceContext :: OMSetBlendState方法的第二个参数(第9.4节)。 这允许您指定直接使用的混合因子颜色; 然而,在你改变混合状态之前,它是不变的。

NOTE:钳位功能定义如下:

(1)clamp(x,a,b)={x,axba,x<ab,x>b

所有以前的混合因子适用于RGB混合方程。对于alpha混合公式,不允许以_COLOR结尾的混合因子。

9.4 混叠阶段

已经谈到了混合算子和混合因子,但是我们在哪里使用Direct3D来设置这些值? 这些混合设置由ID3D11BlendState接口控制。通过填充D3D11_BLEND_DESC结构,然后调用ID3D11Device :: CreateBlendState,可以找到这样一个接口:

HRESULT ID3D11Device::CreateBlendState(
    const D3D11_BLEND_DESC *pBlendStateDesc,
    ID3D11BlendState **ppBlendState);

1 . pBlendStateDesc:指向填充D3D11_BLEND_DESC结构的指针,用于描述要创建的混合状态。
2 . ppBlendState:返回一个指向创建的混合状态接口的指针。

D3D11_BLEND_DESC:Structure定义如下:

typedef struct D3D11_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,这是渲染树叶或门纹理时有用的多重采样技术。指定false以禁用alpha-to-coverage。 Alpha覆盖需要启用多重采样(即,使用多重采样创建背景和深度缓冲区)。我们将在第11章中展示一个使用alpha-to-coverage的示例。
2 . IndependentBlendEnable:Direct3D 11支持同时渲染多达8个渲染目标。如果此标志设置为true,则表示可以对每个渲染目标进行不同的混合(不同的混合因子,不同的混合操作,混合禁用/启用等)。如果此标志设置为false,则意味着所有渲染目标将按照D3D11_BLEND_DESC :: RenderTarget数组中第一个元素所描述的方式进行混合。多个渲染目标用于高级算法;现在假设我们一次只渲染一个渲染目标。
3 . RenderTarget:具有8个D3D11_RENDER_TARGET_BLEND_DESC元素的数组,其中第i个元素描述了如何对第i个同时渲染目标进行混合。如果IndependentBlendEnable设置为false,则所有渲染目标都使用RenderTarget [0]进行混合。

D3D11_RENDER_TARGET_BLEND_DESC结构定义如下:

typedef struct D3D11_RENDER_TARGET_BLEND_DESC {
    BOOL BlendEnable; // Default: False
    D3D11_BLEND SrcBlend; // Default: D3D11_BLEND_ONE
    D3D11_BLEND DestBlend; // Default: D3D11_BLEND_ZERO
    D3D11_BLEND_OP BlendOp; // Default: D3D11_BLEND_OP_ADD
    D3D11_BLEND SrcBlendAlpha; // Default: D3D11_BLEND_ONE
    D3D11_BLEND DestBlendAlpha; // Default: D3D11_BLEND_ZERO
    D3D11_BLEND_OP BlendOpAlpha; // Default: D3D11_BLEND_OP_ADD
    UINT8 RenderTargetWriteMask; // Default: D3D11_COLOR_WRITE_ENABLE_ALL
} D3D11_RENDER_TARGET_BLEND_DESC;

1 . BlendEnable:指定true以启用混合,false指定将其禁用。
2 . SrcBlendD3D11_BLEND枚举类型的成员,它指定RGB混合的源混合因子Fsrc。
3 . DestBlendD3D11_BLEND枚举类型的成员,用于指定RGB混合的目标混合因子Fdst。
4 . BlendOp:指定RGB混合运算符的D3D11_BLEND_OP枚举类型的成员。
5 . SrcBlendAlphaD3D11_BLEND枚举类型的成员,指定alpha的目标混合因子Fsrc 混合。
6 . DestBlendAlphaD3D11_BLEND枚举类型的成员,它指定alpha的目标混合因子$F_{dst}混合。
7 . BlendOpAlpha:指定alpha混合运算符的D3D11_BLEND_OP枚举类型的成员。
8 . RenderTargetWriteMask:一个或多个以下标志的组合:

typedef enum D3D11_COLOR_WRITE_ENABLE {
    D3D11_COLOR_WRITE_ENABLE_RED = 1,
    D3D11_COLOR_WRITE_ENABLE_GREEN = 2,
    D3D11_COLOR_WRITE_ENABLE_BLUE = 4,
    D3D11_COLOR_WRITE_ENABLE_ALPHA = 8,
    D3D11_COLOR_WRITE_ENABLE_ALL =
    (D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_GREEN |
        D3D11_COLOR_WRITE_ENABLE_BLUE | D3D11_COLOR_WRITE_ENABLE_ALPHA)
} D3D11_COLOR_WRITE_ENABLE;

这些标志控制混合后写入后缓冲区中的哪些颜色通道。例如,可以通过指定D3D11_COLOR_WRITE_ENABLE_ALPHA来禁止写入RGB通道,并仅写入Alpha通道。这种灵活性对于高级技术是有用的。当禁用混合时,使用从像素着色器返回的颜色而不应用写入蒙版。

为了将混合状态对象绑定到管道的输出合并阶段,我们调用:

void ID3D11DeviceContext::OMSetBlendState(
    ID3D11BlendState *pBlendState,
    const FLOAT BlendFactor,
    UINT SampleMask);

1 . pBlendState:一个指向混合状态对象的指针,用于使能设备。
2 . BlendFactor:一个由四个浮点数组成的数组,定义一个RGBA颜色矢量。 当指定了D3D11_BLEND_BLEND_FACTORD3D11_BLEND_INV_BLEND_FACTOR时,使用此颜色矢量作为混合因子。
3 . SampleMask:多重采样可能需要多达32个采样。 这个32位整数值用于启用/禁用采样。 例如,如果您关闭第五位,那么第五个采样将不会被采用。 当然,如果实际上使用至少5个采样的多重采样,则禁用第五个采样只会有任何结果。 如果应用程序使用单个采样,则只有该参数的第一位很重要(请参阅练习1)。 通常使用默认值0xffffffff,这不会禁用应用程序可能采取的任何示例。

与其他状态块一样,有一个默认的混合状态(禁用混合)。如果您使用null调用OMSetBlendState,则会恢复默认混合状态。我们注意到混合确实需要额外的每像素工作,所以只有在需要的时候启用它,并在完成后关闭它。

以下代码显示了创建和设置混合状态的示例:

D3D11_BLEND_DESC transparentDesc = {0};
transparentDesc.AlphaToCoverageEnable = false;
transparentDesc.IndependentBlendEnable = false;
transparentDesc.RenderTarget[0].BlendEnable = true;
transparentDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
transparentDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
transparentDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
transparentDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
transparentDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
transparentDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
transparentDesc.RenderTarget[0].RenderTargetWriteMask =
D3D11_COLOR_WRITE_ENABLE_ALL;
ID3D11BlendState* TransparentBS;
HR(device->CreateBlendState(&transparentDesc, &TransparentBS));
…
float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
md3dImmediateContext->OMSetBlendState(
TransparentBS, blendFactor, 0xffffffff);
As with other state block interfaces, you should create them all at application initialization time, and then just switch
between the state interfaces as needed.
A blend state object can also be set and defined in an effect file:
BlendState blend
{
    // Blending state for first render target.
    BlendEnable[0] = TRUE;
    SrcBlend[0] = SRC_COLOR;
    DestBlend[0] = INV_SRC_ALPHA;
    BlendOp[0] = ADD;
    SrcBlendAlpha[0] = ZERO;
    DestBlendAlpha[0] = ZERO;
    BlendOpAlpha[0] = ADD;
    RenderTargetWriteMask[0] = 0x0F;
    // Blending state for second simultaneous render target.
    BlendEnable[1] = True;
    SrcBlend[1] = One;
    DestBlend[1] = Zero;
    BlendOp[1] = Add;
    SrcBlendAlpha[1] = Zero;
    DestBlendAlpha[1] = Zero;
    BlendOpAlpha[1] = Add;
    RenderTargetWriteMask[1] = 0x0F;
};
technique11 Tech
{
    pass P0
    {
        …
        // Use &quot;blend&quot; for this pass.
        SetBlendState(blend, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xffffffff);
    }
}

除非没有前缀,否则分配给混合状态对象的值与您分配给C ++结构的值相同。 例如,我们不是指定D3D11_BLEND_SRC_COLOR,而是在效果代码中指定SRC_COLOR。还要注意,状态属性的赋值不区分大小写。

9.5 例子

在下面的小节中,我们看看用于获得特定效果的一些混合因子组合。在这些例子中,我们只看RGB混合。阿尔法混合是类似的处理。

9.5.1无颜色写入

假设我们想保持原来的目标像素是完全不变的,而不是覆盖它,或者将它与当前被光栅化的源像素混合。 例如,如果您只是要写入深度/模板缓冲区而不是后缓冲区,则此功能非常有用。为此,请将源像素混合因子设置为D3D11_BLEND_ZERO,将目标混合因子设置为D3D11_BLEND_ONE,并将混合 运算符D3D11_BLEND_OP_ADD。 通过这种设置,混合等式可以简化为:

C=CsrcFsrcCdstFdstC=Csrc(0,0,0)+Cdst(1,1,1)C=Cdst

9-2
图9.2 添加源和目标颜色。 添加创建一个更明亮的图像,因为正在添加颜色

这是一个人为的例子。 另一种实现相同的方法是将D3D11_RENDER_TARGET_BLEND_DESC :: RenderTargetWriteMask成员设置为0,以便不写入任何颜色通道。

9.5.2 加/减

假设我们要添加具有目标像素的源像素(见图9.2)。 为此,请将源混合因子设置为D3D11_BLEND_ONE,将目标混合因子设置为D3D11_BLEND_ONE,将混合运算符设置为D3D11_BLEND_OP_ADD。 通过这种设置,混合等式可以简化为:

C=CsrcFsrcCdstFdstC=Csrc(1,1,1)+Cdst(1,1,1)C=Csrc+Cdst

我们可以使用之前的混合因子从目标像素中减去源像素,并用D3D11_BLEND_OP_SUBTRACT替换混合运算(图9.3)。
9-3
图9.3 从目标颜色中减去源颜色。 因为颜色正在被删除,所以减法会创建较暗的图像。

9-4
图9.4 将源颜色和目标颜色相乘。

9.5.3乘法

假设我们想将一个源像素与其相应的目标像素相乘(见图9.4)。 为此,我们将源混合因子设置为D3D11_BLEND_ZERO,将目标混合因子设置为D3D11_BLEND_SRC_COLOR,将混合运算符设置为D3D11_BLEND_OP_ADD。 通过这种设置,混合等式可以简化为:

C=CsrcFsrcCdstFdstC=Csrc(0,0,0)+CdstCsrcC=CsrcCdst

9.5.4透明度

让源alpha分量被认为是控制源像素的不透明度的百分比(例如,0α表示0%不透明,0.4表示40%不透明,1.0表示100%不透明)。 不透明度和透明度之间的关系简单地是T = 1 -A,其中A是不透明度,T是透明度。 例如,如果0.4是不透明的,则透明度是1-0.4 = 0.6。 现在假设我们想要根据源像素的不透明度来混合源像素和目标像素。 为此,请将源混合因子设置为D3D11_BLEND_SRC_ALPHA,将目标混合因子设置为D3D11_BLEND_INV_SRC_ALPHA,将混合运算符设置为D3D11_BLEND_OP_ADD。 通过这种设置,混合等式可以简化为:

C=CsrcFsrcCdstFdstC=Csrc(0,0,0)+CdstCsrcC=CsrcCdst

例如,假设as=0.25 ,也就是说源像素只有25%不透明。然后,当源像素和目标像素混合在一起时,我们预期最终颜色将是源像素的25%和目标像素的75%(源像素之后的像素)的组合,因为源像素是 75%透明。上面的等式给了我们这个:
C=asCdst+(1as)CsrcC=0.25Cdst+0.75Csrc

使用这种混合方法,我们可以画出如图9.1所示的透明物体。应该注意的是,用这种混合方法,绘制对象的顺序很重要。 我们使用以下规则:
绘制不首先使用混合的对象。接下来,按距离相机的距离对使用混合的对象进行排序。最后,按照从前到后的顺序绘制使用混合的对象。

背对背绘制顺序的原因是,物体与空间背后的物体混合在一起。因为如果一个物体是透明的,我们可以透过它看到它背后的景象。所以透明物体后面的所有像素都必须写入后台缓冲区,所以我们可以将透明源像素与其后面的场景的目标像素混合。

对于第9.5.1节中的混合方法,绘制顺序并不重要,因为它可以防止源像素写入后台缓冲区。对于第9.5.2节和第9.5.3节讨论的混合方法,我们仍然先绘制非混合对象 最后混合物体; 这是因为我们希望在开始混合之前先将所有非混合几何图形放置在后台缓冲区中。 但是,我们不需要对使用混合的对象进行排序。 这是因为这些操作是可交换的。 也就是说,如果您从后缓冲区像素颜色B开始,然后对该像素执行n个加法/减法/乘法混合,则顺序无关紧要:

B=B+C0+C1+...+Cn1B=BC0C1...Cn1B=BC0C1...Cn1

9.5.5混合和深度缓冲区

当混合加法/减法/乘法混合时,深度测试会出现问题。为了举例,我们将只用加法混合来解释,但是减法/乘法混合也是一样的。如果我们用叠加混合来渲染一组对象S,那么这个想法就是S中的对象不会互相混淆;相反,它们的颜色只是简单的积累(见图9.5)。因此,我们不想在S中的对象之间进行深度测试;因为如果我们这样做了,没有后端的绘制顺序,S中的一个对象会隐藏S中的另一个对象,从而导致像素片段由于深度测试而被拒绝,这意味着对象的像素颜色不会被累积进入混合总和。我们可以禁用S中的对象之间的深度测试,通过在S中渲染对象时禁止对深度缓冲区的写入。因为深度写入被禁用,所以使用加法混合绘制的S中的对象的深度不会写入深度缓冲区;因此,由于深度测试,这个对象不会隐藏在其后的S中的任何后来绘制的对象。请注意,我们只在绘制S中的对象(用添加混合绘制的对象集)时禁用深度写入。深度读取和深度测试仍然启用。这是因为非混合几何体(在混合几何体之前绘制)仍将隐藏其后面的混合几何体。例如,如果在墙后面有一组添加混合的对象,则不会看到混合的对象,因为实心墙遮挡了它们。如何禁用深度写入,更一般的配置深度测试设置将在下一章中介绍。

9-5
图9.5 使用添加剂混合,在更多颗粒重叠并被添加在一起的源点附近,强度更大。 随着颗粒的扩散,强度减弱,因为有较少的颗粒重叠并被加在一起。

9.6 alpha 通道

§9.5.4中的例子显示,源代码alpha组件可以用于RGB混合来控制透明度。 混合方程中使用的源颜色来自像素着色器。 正如我们在上一章中看到的,我们将漫反射材质的alpha值作为像素着色器的alpha输出返回。因此漫反射贴图的alpha通道用于控制透明度。

9-6
图9.6 RGB图像(左)和灰度图像(右)。 灰度图像将被插入纹理的alpha通道中。

float4 PS(VertexOut pin) : SV_Target
{
…
    // Common to take alpha from diffuse material and texture.
    litColor.a = gMaterial.Diffuse.a * texColor.a;
    return litColor;
}

您通常可以在任何流行的图像编辑软件(如Adobe Photoshop)中添加Alpha通道,然后将图像保存为支持Alpha通道(例如,32位.bmp格式或.dds格式)的格式。 然而,在这里我们展示了一个使用上一章讨论过的DXTex实用程序插入一个alpha通道的替代方法。

我们首先假定我们有两个图像 - 一个彩色的RGB图像和一个灰度图像,将被插入到alpha通道(见图9.6)。

现在,打开DXTex工具,打开位于本章的示例文件夹中的fire_rgb.bmp文件。 火纹理以24位RGB纹理(即,D3DFMT_R8G8B8)自动加载,每个像素具有8位红色,8位绿色和8位蓝色。 我们需要将格式更改为支持Alpha通道的格式,例如32位ARGB纹理格式D3DFMT_A8R8G8B8,或者使用支持alpha D3DFMT_DXT5等格式的压缩格式。 从菜单中选择格式并选择Change_Surface格式。 弹出如图9.7所示的对话框,选择DXT5格式,然后按OK。

9-7
图9.7 改变纹理的格式

这将创建一个带有alpha通道的压缩纹理。 我们的下一个任务是将数据加载到alpha通道。 我们将把图9.6所示的8位灰度图加载到alpha通道中。 从菜单中选择“文件”,然后选择“打开此纹理的Alpha通道”,然后选择“格式” - >“生成Mip贴图”。 会弹出一个对话框,要求您找到包含要加载到Alpha通道的数据的图像文件。 选择位于本章演示文件夹中的fire_a.bmp文件。插入Alpha通道数据之后,程序如图9.8所示,纹理透明地与背景颜色混合。 可以通过从菜单栏选择视图,然后更改背景颜色…来更改背景颜色。 您也可以选择View-> Alpha Channel Only来查看Alpha通道。

9-8
图9.8 生成带有alpha通道的纹理。火纹理与蓝色背景颜色透明地混合。现在,用您选择的名称(例如“fire.dds”)保存纹理。

9.7剪切像素

有时我们想要完全拒绝源像素被进一步处理。这可以通过内在的HLSL clip(x)函数来完成。该函数只能在像素着色器中调用,并且如果x <0,则丢弃当前像素以进行进一步处理。例如,如图9.9所示,此函数对渲染钢丝网纹理非常有用。也就是说,对像素完全不透明或完全透明的像素进行渲染是非常有用的。

在像素着色器中,我们抓取纹理的alpha分量。如果它是一个接近于0的小值,这表示像素是完全透明的,那么我们将该像素从进一步处理中剪裁掉。

9-9
图9.9 带有alpha通道的铁丝网纹理。具有黑色alpha值的像素将被clip函数拒绝并且未被绘制; 因此,只剩下铁丝网。本质上,alpha通道用于遮挡纹理中的非栅栏像素。

float4 PS(VertexOut pin, uniform int gLightCount, uniform bool gUseTexure,uniform bool gAlphaClip, uniform bool gFogEnabled) : SV_Target
{
    // Interpolating normal can unnormalize it, so normalize it.
    pin.NormalW = normalize(pin.NormalW);
    // The toEye vector is used in lighting.
    float3 toEye = gEyePosW - pin.PosW;
    // Cache the distance to the eye from this surface point.
    float distToEye = length(toEye);
    // Normalize.
    toEye /= distToEye;
    // Default to multiplicative identity.
    float4 texColor = float4(1, 1, 1, 1);
    if(gUseTexure)
    {
        // Sample texture.
        texColor = gDiffuseMap.Sample(samAnisotropic, pin.Tex);
        if(gAlphaClip)
        {
            // Discard pixel if texture alpha < 0.1. Note that
            // we do this test as soon as possible so that we can
            // potentially exit the shader early, thereby skipping the
            // rest of the shader code.
            clip(texColor.a - 0.1f);
        }
    }
…

注意只在参数gAlphaClip为true时才统一剪辑; 这是因为我们可能不想为一些几何体调用剪辑,所以我们需要通过使用专门的着色器来打开/关闭它。

请注意,使用混合可以获得相同的结果,但这样更有效。首先,不需要进行混合计算(可以禁用混合)。另外,绘制顺序并不重要。此外,通过从像素着色器中提前丢弃像素,可以跳过剩余的像素着色器指令(对丢弃的像素进行计算没有意义)。

NOTE:由于过滤,alpha通道可能会模糊一点,所以在剪裁像素时应该留出一些缓冲空间。 例如,剪裁alpha值接近于0的像素,但不一定完全为零。

图9.10显示了“混合”演示的屏幕截图。它使用透明度混合渲染半透明的水,并使用剪辑测试呈现围栏的线框。另一个值得一提的变化是,因为我们现在可以看到带有栅栏纹理的盒子,所以我们要禁用背面剔除:

9-10
图9.10 “混合”演示的屏幕截图

ZeroMemory(&noCullDesc, sizeof(D3D11_RASTERIZER_DESC));
noCullDesc.FillMode = D3D11_FILL_SOLID;
noCullDesc.CullMode = D3D11_CULL_NONE;
noCullDesc.FrontCounterClockwise = false;
noCullDesc.DepthClipEnable = true;
ID3D11RasterizerState* NoCullRS;
HR(device->CreateRasterizerState(&noCullDesc, &NoCullRS));
…
// Since the fence texture has transparent regions, we can
// see through it, and thus see the backsides of the triangles.
// Therefore, we don&apos;t want to backface culling in this case.
md3dImmediateContext->RSSetState(NoCullRS);
boxTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
md3dImmediateContext->DrawIndexed(36, 0, 0);
// Restore default render state.
md3dImmediateContext->RSSetState(0);

9.8 雾

为了在我们的游戏中模拟某些类型的天气条件,我们需要能够实现雾效果; 见图9.11。除了雾的明显目的之外,雾提供了一些附带的好处。例如,它可以屏蔽远处的渲染伪像并防止弹出。 弹出是指由于相机移动,突然之前在远平面后面的物体出现在平截头体的前面,从而变得可见; 所以它似乎突然“弹出”到现场。通过在远处有一层雾,弹出隐藏。请注意,如果您的场景发生在一个晴朗的日子,您可能希望仍然在远处包含微量的雾,因为即使在晴朗的日子,远处的物体(如山脉)也会变得更加朦胧,并因深度而失去对比度, 我们可以用雾来模拟这种大气透视现象。

9-11
图9.11 启用了雾的“混合”演示的屏幕截图。

我们的雾实施策略如下:我们指定一个雾的颜色,从相机开始雾的开始距离,和一个雾的范围(即从雾的开始距离到雾完全隐藏任何物体的范围)。那么三角形上一个点的颜色就是其平常颜色和雾色的加权平均值:
foggedColor=litColor+s(fogColorlitColor)=(1s)·litColor+s·fogColor

参数s的范围从0到1,是相机位置和表面点之间的距离的函数。随着表面点与眼睛之间的距离增加,雾点越来越模糊。参数s定义如下:

float3 toEye = gEyePosW - pin.PosW;
// Cache the distance to the eye from this surface point.
float distToEye = length(toEye);
// Normalize.
toEye /= distToEye;
// Default to multiplicative identity.
float4 texColor = float4(1, 1, 1, 1);
if(gUseTexure)
{
// Sample texture.
texColor = gDiffuseMap.Sample(samAnisotropic, pin.Tex);
if(gAlphaClip)
{
// Discard pixel if texture alpha < 0.1. Note that
// we do this test as soon as possible so that we can
// potentially exit the shader early, thereby
// skipping the rest of the shader code.
clip(texColor.a - 0.1f);
}
}
//
// Lighting.
//
float4 litColor = texColor;
if(gLightCount > 0)
{
// Start with a sum of zero.
float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
// Sum the light contribution from each light source.
[unroll]
for(int i = 0; i < gLightCount; ++i)
{
float4 A, D, S;
ComputeDirectionalLight(gMaterial, gDirLights[i],
pin.NormalW, toEye,
A, D, S);
ambient += A;
diffuse += D;
spec += S;
}
// Modulate with late add.
litColor = texColor*(ambient + diffuse) + spec;
} //
// Fogging
//
if( gFogEnabled )
{
float fogLerp = saturate((distToEye - gFogStart) / gFogRange);
// Blend the fog color and the lit color.
litColor = lerp(litColor, gFogColor, fogLerp);
}
// Common to take alpha from diffuse material and texture.
litColor.a = gMaterial.Diffuse.a * texColor.a;
return litColor;
}

Note:观察雾计算中,我们使用distToEye值,我们也计算了toEye矢量的归一化。 不太理想的实现应该是写:
float3 toEye = normalize(gEyePosW - pin.PosW);
float distToEye = distance(gEyePosW, pin.PosW);
这基本上计算了toEye矢量的长度,一次在归一化函数中,一次在距离函数中计算。
Note:“混合”演示支持三种渲染模式,可以通过按“1”,“2”和“3”键进行切换。第一种模式仅使用光照渲染场景(见图9.14)。照明是一个过去的话题,但看看没有纹理的场景是什么样的。 第二种模式渲染场景的照明和纹理(图9.10)。第三种模式渲染场景的照明,纹理和雾(图9.11)。这些渲染模式开关演示了我们如何使用效果框架中的统一参数来生成功能性开启和关闭的专用着色器。 读者可能希望重新检查Basic.fx中的一些汇编语言输出。比较照明,照明和纹理,照明,纹理和雾的帧速率也是有趣的。

9-14
图9.14 “混合”演示只启用照明

9.9 总结

1.混合是一种技术,它允许我们将当前正在栅格化的像素(所谓的源像素)与先前栅格化到后端缓冲区的像素(所谓的目标像素)混合(组合)。 除此之外,这种技术使我们能够渲染诸如水和玻璃之类的半透明物体。
2.混合方程是:

C=CsrcFsrcCdstFdstA=AsrcFsrcAdstFdst

请注意,RGB组件独立地混合到alpha组件。⊞二元运算符可以是由D3D11_BLEND_OP枚举类型定义的运算符之一。
3.Fsrc,Fdst,Fsrc和Fdst被称为混合因子,它们为定制混合等式提供了一种手段。他们可以是D3D11_BLEND枚举类型的成员。对于alpha混合公式,不允许以_COLOR结尾的混合因子。
4.alpha由材质决定。漫反射材质由纹理贴图定义,纹理的alpha通道存储alpha信息。
5.使用本征HLSL clip(x)函数,可以完全拒绝源像素的进一步处理。该函数只能在像素着色器中调用,并且如果x <0,则丢弃当前像素以进行进一步处理。此外,此功能对于有效渲染像素(其中像素完全不透明或完全透明)是有用的(用于拒绝完全透明的像素 - α值接近0的像素)。
6.使用雾模拟各种天气影响和大气透视,以隐藏远处的渲染文物,并隐藏弹出。在我们的线性雾模型中,我们指定了一个雾的颜色,从相机的起雾距离和雾的范围。三角形上的点的颜色是其通常颜色和雾色的加权平均值:
foggedColor=litColor+s(fogColorlitColor)=(1s)·litColor+s·fogColor

参数s的范围从0到1,是相机位置和表面点之间的距离的函数。随着表面点与眼睛之间的距离增加,雾点越来越模糊。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章