第九章 混合

觀察圖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,是相機位置和表面點之間的距離的函數。隨着表面點與眼睛之間的距離增加,霧點越來越模糊。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章