DirectX11 With Windows SDK--17 利用幾何着色器實現公告板效果

前言

上一章咱們知道了如何使用幾何着色器將頂點經過流輸出階段輸出到綁定的頂點緩衝區。接下來咱們繼續利用它來實現一些新的效果,在這一章,你將瞭解:html

  1. 實現公告板效果
  2. Alpha-To-Coverage
  3. 對GPU資源進行讀/寫操做
  4. 紋理數組
  5. 實現霧效

在此以前須要額外瞭解的章節以下:git

章節回顧
深刻理解與使用2D紋理資源(重點閱讀紋理數組)
15 幾何着色器初探

DirectX11 With Windows SDK完整目錄github

Github項目源碼數組

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。app

實現霧效

雖然這部分與幾何着色器並無什麼關係,可是霧的效果在該Demo中會用到,而且前面也沒有講過這部份內容,故先在這裏提出來。dom

有時候咱們須要在遊戲中模擬一些特定的天氣條件,好比說大霧。它可讓物體平滑出現而不是忽然蹦出來那樣(物體的一部分留在視錐體內使得只能看到該部分,而後在逐漸靠近該物體的時候,該物體就像通過了一個無形的掃描門被逐漸構造出來那樣)。經過讓霧在某一範圍內具備必定的層次(讓不可見區域比視錐體裁剪區域還近),咱們能夠避免上面所說的狀況。但即使是晴朗的天氣,你可能仍但願包含一個較廣範圍的霧效,即距離達到很遠的地方纔逐漸看不清物體。函數

咱們可使用這種方式來實現霧效:指定霧的顏色,以攝像機爲原點的霧開始的最小距離,霧效範圍值(超過起始距離+霧效範圍值的範圍外的顏色皆被指定的霧色取代)。在須要繪製的三角形內,某一像素片元的顏色以下:
\(\begin{align} foggedColor &= litColor + s(fogColor - litColor)\\ &= (1-s) \cdot litColor + s \cdot fogColor\\ \end{align}\)ui

該函數對應HLSL中的lerp函數,s取0的時候最終顏色爲litColor,而後逐漸增大並逼近1的時候,最終顏色就逐漸趨近於fogColor。而後參數s的值取決於下面的函數:
\(s = saturate(\frac{dist(\mathbf{p},\mathbf{E}) - fogStart}{fogRange})\)
\(saturate(x) = \begin{cases} x, 0 \le x \le 1\\ 0, x < 0\\ 1, x > 1\\ \end{cases}\)spa

其中dist(p,E)指的是兩點之間的距離值。配合下面的圖去理解:
3d

還有注意一點,在每次清空從新繪製的時候,要用霧的顏色進行清空。

HLSL代碼

與霧效相關的值存儲在下面的常量緩衝區中,而且繪製3D物體的頂點沒有發生變化:

// Basic.fx
// ...

cbuffer CBDrawingStates : register(b2)
{
    float4 g_FogColor;
    int g_FogEnabled;
    float g_FogStart;
    float g_FogRange;
    float g_Pad2;
}

// ...
struct VertexPosNormalTex
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float2 Tex : TEXCOORD;
};

struct VertexPosHWNormalTex
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION; // 在世界中的位置
    float3 NormalW : NORMAL; // 法向量在世界中的方向
    float2 Tex : TEXCOORD;
};

Basic_VS.hlsl也與以前同樣,沒有什麼變更:

// Basic_VS.hlsl
#include "Basic.hlsli"

// 頂點着色器
VertexPosHWNormalTex VS(VertexPosNormalTex vIn)
{
    VertexPosHWNormalTex vOut;
    
    matrix viewProj = mul(g_View, g_Proj);
    vector posW = mul(float4(vIn.PosL, 1.0f), g_World);

    vOut.PosW = posW.xyz;
    vOut.PosH = mul(posW, viewProj);
    vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);
    vOut.Tex = vIn.Tex;
    return vOut;
}

Basic_PS.hlsl如今使用了4盞方向光以保證4種不一樣方向的光可以均勻照射,並添加了霧效部分的處理:

// Basic_PS.hlsl
#include "Basic.hlsli"

// 像素着色器
float4 PS(VertexPosHWNormalTex pIn) : SV_Target
{
    // 提早進行裁剪,對不符合要求的像素能夠避免後續運算
    float4 texColor = g_Tex.Sample(g_Sam, pIn.Tex);
    clip(texColor.a - 0.05f);

    // 標準化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 求出頂點指向眼睛的向量,以及頂點與眼睛的距離
    float3 toEyeW = normalize(g_EyePosW - pIn.PosW);
    float distToEye = distance(g_EyePosW, pIn.PosW);

    // 初始化爲0 
    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);
    float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);

    [unroll]
    for (int i = 0; i < 4; ++i)
    {
        ComputeDirectionalLight(g_Material, g_DirLight[i], pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }
    
    float4 litColor = texColor * (ambient + diffuse) + spec;

    // 霧效部分
    [flatten]
    if (g_FogEnabled)
    {
        // 限定在0.0f到1.0f範圍
        float fogLerp = saturate((distToEye - g_FogStart) / g_FogRange);
        // 根據霧色和光照顏色進行線性插值
        litColor = lerp(litColor, g_FogColor, fogLerp);
    }

    litColor.a = texColor.a * g_Material.Diffuse.a;
    return litColor;
}

對於白天來講,咱們可使用RGBA=(0.75f, 0.75f, 0.75f, 1.0f)來做爲霧的顏色。

而對於黑夜來講,這個霧效更像是戰爭迷霧的效果,咱們使用RGBA=(0.0f, 0.0f, 0.0f, 1.0f)來做爲霧的顏色,這樣遠處的物體咱們就讓它看不見,而在可視範圍內,距離越遠的物體能見度越低。

具體的演示效果在最後能夠看到。

樹的公告板效果

當一棵樹離攝像機太遠的話,咱們可使用公告板技術,用一張樹的貼圖來進行繪製,取代原來繪製3D樹模型的方式。首先咱們給出樹的紋理貼圖組成:

關注Alpha通道部分,白色區域指代Alpha值爲1.0(徹底不透明),而黑色區域指代Alpha值0.0(徹底透明)。因此在渲染樹紋理的時候,咱們只須要對Alpha值爲0.0的像素區域進行裁剪便可。

實現公告板的關鍵點在於:公告板要永遠正向攝像機(即視線要與公告板表面垂直),使得用戶的視線在x0z面上的投影一直與貼圖表面垂直。這樣作省去了大量頂點的輸入和處理,顯得更加高效,而且這個小技巧還可以欺騙玩家讓人誤覺得仍是原來的3D模型(眼尖的玩家仍是有可能認得出來),只要你別一開始就告訴人家這棵樹的繪製用了公告板原理就好了(→_→)。

如今不考慮座標系的Y軸部分(即從上方俯視),從下面的圖能夠看到,公告板投影的中心部分的法向量是直接指向攝像機的。

所以咱們能夠獲得公告板的u軸, v軸和w軸單位向量以及根據公告板構建的局部座標系:
\(\mathbf{w}=\frac{(E_x-C_x,0,E_z-C_z)}{E_x-C_x,0,E_z-C_z}\)
\(\mathbf{v}=(0,1,0)\)
\(\mathbf{u}=\mathbf{v}\times\mathbf{w}\)

而後已知中心頂點位置、樹寬度和高度,就能夠求得2D樹矩形的四個頂點了:

// 計算出公告板矩形的四個頂點
//            up
//       v1___|___v3
//        |   |   |
// right__|___|   |
//        |__/____|
//       v0 /     v2
//        look  
v[0] = float4(center + halfWidth * right - halfHeight * up, 1.0f);
v[1] = float4(center + halfWidth * right + halfHeight * up, 1.0f);
v[2] = float4(center - halfWidth * right - halfHeight * up, 1.0f);
v[3] = float4(center - halfWidth * right + halfHeight * up, 1.0f);

注意上面的加減運算是針對float3進行的,而後用1.0f填充成4D向量。而且因爲每一個公告板所處的局部座標系不同,咱們須要對它們分別計算出對應的座標軸向量。

若如今咱們須要繪製公告板,則在輸入的時候僅提供對應的中心頂點,而後圖元類型選擇D3D11_PRIMITIVE_TOPOLOGY_POINTLIST,在幾何着色階段咱們直接將頂點直傳到幾何着色階段,這些頂點傳遞給幾何着色器後就會解釋成一個個矩形(兩個三角形),產生公告板。

HLSL代碼

下面是Basic.hlsli的完整代碼:

// Basic.hlsli

#include "LightHelper.hlsli"

Texture2D g_Tex : register(t0);
Texture2DArray g_TexArray : register(t1);
SamplerState g_Sam : register(s0);


cbuffer CBChangesEveryDrawing : register(b0)
{
    matrix g_World;
    matrix g_WorldInvTranspose;
    Material g_Material;
}

cbuffer CBChangesEveryFrame : register(b1)
{
    matrix g_View;
    float3 g_EyePosW;
    float g_Pad;
}

cbuffer CBDrawingStates : register(b2)
{
    float4 g_FogColor;
    int g_FogEnabled;
    float g_FogStart;
    float g_FogRange;
    float g_Pad2;
}

cbuffer CBChangesOnResize : register(b3)
{
    matrix g_Proj;
}

cbuffer CBChangesRarely : register(b4)
{
    DirectionalLight g_DirLight[5];
    PointLight g_PointLight[5];
    SpotLight g_SpotLight[5];
}



struct VertexPosNormalTex
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float2 Tex : TEXCOORD;
};

struct VertexPosHWNormalTex
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION; // 在世界中的位置
    float3 NormalW : NORMAL; // 法向量在世界中的方向
    float2 Tex : TEXCOORD;
};

struct PointSprite
{
    float3 PosW : POSITION;
    float2 SizeW : SIZE;
};

struct BillboardVertex
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION;
    float3 NormalW : NORMAL;
    float2 Tex : TEXCOORD;
    uint PrimID : SV_PrimitiveID;
};

而幾何着色器的代碼以下:

// Billboard_GS.hlsl

#include "Basic.hlsli"

// 節省內存資源,先用float4向量聲明。
static const float4 g_Vec[2] = { float4(0.0f, 1.0f, 0.0f, 0.0f), float4(1.0f, 1.0f, 1.0f, 0.0f) };
static const float2 g_TexCoord[4] = (float2[4])g_Vec;

[maxvertexcount(4)]
void GS(point PointSprite input[1], uint primID : SV_PrimitiveID, 
    inout TriangleStream<BillboardVertex> output)
{
    // 計算公告板所處的局部座標系,其中公告板至關於
    // 被投影在了局部座標系的xy平面,z=0

    float3 up = float3(0.0f, 1.0f, 0.0f);
    float3 look = g_EyePosW - input[0].PosW;
    look.y = 0.0f;  // look向量只取投影到xz平面的向量
    look = normalize(look);
    float3 right = cross(up, look);

    // 計算出公告板矩形的四個頂點
    //            up
    //      v1 ___|___ v3
    //        |   |   |
    // right__|___|   |
    //        |  /    |
    //        |_/_____|
    //      v0 /       v2
    //       look  
    float4 v[4];
    float3 center = input[0].PosW;
    float halfWidth = 0.5f * input[0].SizeW.x;
    float halfHeight = 0.5f * input[0].SizeW.y;
    v[0] = float4(center + halfWidth * right - halfHeight * up, 1.0f);
    v[1] = float4(center + halfWidth * right + halfHeight * up, 1.0f);
    v[2] = float4(center - halfWidth * right - halfHeight * up, 1.0f);
    v[3] = float4(center - halfWidth * right + halfHeight * up, 1.0f);

    // 對頂點位置進行矩陣變換,並以TriangleStrip形式輸出
    BillboardVertex gOut;
    matrix viewProj = mul(g_View, g_Proj);
    [unroll]
    for (int i = 0; i < 4; ++i)
    {
        gOut.PosW = v[i].xyz;
        gOut.PosH = mul(v[i], viewProj);
        gOut.NormalW = look;
        gOut.Tex = g_TexCoord[i];
        gOut.PrimID = primID;
        output.Append(gOut);
    }

}

首先一開始不用float2數組是由於每一個float2元素會單獨打包,浪費了一半的空間,所以這裏採起一種特殊的語法形式使得內存能夠獲得充分利用。

而後要注意maxvertexcount的值要設爲4,儘管Append的次數爲4,但實際上輸出的三角形頂點數爲6。

圖元ID

如今講述系統值SV_PrimitiveID,咱們能夠將它做爲函數的額外形參進行提供。它告訴咱們在輸入裝配階段下自動分配的圖元ID值。當咱們調用了一個draw方法,須要繪製n個圖元,那麼第一個圖元對應的ID值爲0,第二個爲1,直到最後一個爲n-1.當前的全部圖元ID僅在當前的單次調用繪製是惟一的。其中該系統值的寫入操做容許在幾何着色器和像素着色器進行,而讀取操做則容許在幾何/像素/外殼/域着色器中進行。

在上面的例子中,咱們將一個頂點產生的矩形四個頂點都標記爲同一個圖元ID,是由於到後續的像素着色器中,咱們用該圖元ID映射到紋理數組的索引值,來對應到要繪製的樹的紋理。

注意: 若是幾何着色器沒有提供圖元ID,在像素着色器中也能夠將它加進參數列表中以使用:

float4 PS(Vertex3DOut pin, uint primID : SV_PrimitiveID) : SV_Target
{
// Pixel shader body…
}

但若是像素着色器提供了圖元ID,渲染管線又綁定了幾何着色器,則幾何着色器必須提供該參數。在幾何着色器中你可使用或修改圖元ID值。

頂點ID

緊接着是系統值SV_VertexID,在輸入裝配階段的時候渲染管線就會爲這些輸入的頂點分配頂點ID值。若使用的是Draw方法,則這些頂點將會按順序從0到n-1被標記(n爲頂點數目);若使用的是DrawIndexed方法,則頂點ID對應到的是該頂點所處的索引值。該參數僅能在頂點着色器的參數列表中提供:

VertexOut VS(VertexIn vin, uint vertID : SV_VertexID)
{
// vertex shader body…
}

最後給出像素着色器的代碼:

// Billboard_PS.hlsl

#include "Basic.hlsli"

float4 PS(BillboardVertex pIn) : SV_Target
{
    // 每4棵樹一個循環,儘可能保證出現不一樣的樹
    float4 texColor = g_TexArray.Sample(g_Sam, float3(pIn.Tex, pIn.PrimID % 4));
    // 提早進行裁剪,對不符合要求的像素能夠避免後續運算
    clip(texColor.a - 0.05f);

    // 標準化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 求出頂點指向眼睛的向量,以及頂點與眼睛的距離
    float3 toEyeW = normalize(g_EyePosW - pIn.PosW);
    float distToEye = distance(g_EyePosW, pIn.PosW);

    // 初始化爲0 
    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);
    float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);

    [unroll]
    for (int i = 0; i < 4; ++i)
    {
        ComputeDirectionalLight(g_Material, g_DirLight[i], pIn.NormalW, toEyeW, A, D, S);
        ambient += A;
        diffuse += D;
        spec += S;
    }

    float4 litColor = texColor * (ambient + diffuse) + spec;

    // 霧效部分
    [flatten]
    if (g_FogEnabled)
    {
        // 限定在0.0f到1.0f範圍
        float fogLerp = saturate((distToEye - g_FogStart) / g_FogRange);
        // 根據霧色和光照顏色進行線性插值
        litColor = lerp(litColor, g_FogColor, fogLerp);
    }

    litColor.a = texColor.a * g_Material.Diffuse.a;
    return litColor;
}

這裏加上了剛纔的霧效,並使用了紋理數組。若是你對紋理數組這部份內容不熟悉的話,請回到開頭閱讀"深刻理解與使用2D紋理資源"中的紋理數組部分

Alpha-To-Coverage

在Demo運行的時候,仔細觀察能夠發現樹公告板的某些邊緣部分有一些比較突出的黑邊。

這是由於當前默認使用的是Alpha Test,即HLSL中使用clip函數將Alpha值爲0的像素點給剔除掉,這些像素也不是樹的一部分。該函數決定某一像素是留下仍是拋棄,這會致使不平滑的過渡現象,在攝像機逐漸靠近該紋理時,圖片自己也在不斷放大,硬邊部分也會被放大,就像下面那張圖:

固然,你也可使用透明混合的方式,可是透明混合對繪製的順序是有要求的,要求透明物體按從後到前的順序進行繪製,即須要在繪製透明物體前先對物體按到攝像機的距離排個序。固然若是須要繪製大量的草叢的話,這種方法所須要的開銷會變得很是大,操做起來也十分麻煩。

固然,咱們能夠考慮下使用MSAA(多重採樣抗鋸齒),並配合Alpha Test進行。MSAA能夠用於將多邊形的鋸齒邊緣平滑處理,而後讓Direct3D開啓alpha-to-coverage技術,標記邊緣部分。

在建立後備緩衝區、深度/模板緩衝區前須要打開4倍多重採樣的支持,目前的項目在d3dApp中已經默認開啓好了。

而後在以前的例子裏,咱們已經在RenderStates類中預先建立好了混合狀態:

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.ReleaseAndGetAddressOf()));

而後只須要在須要的時候綁定該狀態便可。

BasicEffect的變化

BasicEffect::SetRenderBillboard方法--公告板繪製

該方法要考慮輸入的是一系列頂點圖元:

void BasicEffect::SetRenderBillboard(ID3D11DeviceContext * deviceContext, bool enableAlphaToCoverage)
{
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosSizeLayout.Get());
    deviceContext->VSSetShader(pImpl->m_pBillboardVS.Get(), nullptr, 0);
    deviceContext->GSSetShader(pImpl->m_pBillboardGS.Get(), nullptr, 0);
    deviceContext->RSSetState(RenderStates::RSNoCull.Get());
    deviceContext->PSSetShader(pImpl->m_pBillboardPS.Get(), nullptr, 0);
    deviceContext->PSSetSamplers(0, 1, RenderStates::SSLinearWrap.GetAddressOf());
    deviceContext->OMSetDepthStencilState(nullptr, 0);
    deviceContext->OMSetBlendState(
        (enableAlphaToCoverage ? RenderStates::BSAlphaToCoverage.Get() : nullptr),
        nullptr, 0xFFFFFFFF);

}

參數enableAlphaToCoverage決定是否要綁定渲染狀態對象RenderStates::BSAlphaToCoverage

GameApp類的變化

GameApp::InitPointSpritesBuffer方法--初始化存放點精靈的緩衝區

該方法會生成20個頂點,均勻並略帶隨機性地環繞在原點周圍。這些頂點一經建立就不能夠被修改了,它們將會被用於公告板的建立:

void GameApp::InitPointSpritesBuffer()
{
    srand((unsigned)time(nullptr));
    VertexPosSize vertexes[16];
    float theta = 0.0f;
    for (int i = 0; i < 16; ++i)
    {
        // 取20-50的半徑放置隨機的樹
        float radius = (float)(rand() % 31 + 20);
        float randomRad = rand() % 256 / 256.0f * XM_2PI / 16;
        vertexes[i].pos = XMFLOAT3(radius * cosf(theta + randomRad), 8.0f, radius * sinf(theta + randomRad));
        vertexes[i].size = XMFLOAT2(30.0f, 30.0f);
        theta += XM_2PI / 16;
    }

    // 設置頂點緩衝區描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_IMMUTABLE;  // 數據不可修改
    vbd.ByteWidth = sizeof (vertexes);
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vbd.CPUAccessFlags = 0;
    // 新建頂點緩衝區
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertexes;
    HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, mPointSpritesBuffer.GetAddressOf()));
}

GameApp::InitResource方法--初始化資源

該方法集成了全部資源的初始化,注意樹的紋理數組要提供到輸入槽1,對應紋理寄存器t1的Texture2DArray

bool GameApp::InitResource()
{
    // ******************
    // 初始化各類物體
    //

    // 初始化樹紋理資源
    ComPtr<ID3D11Texture2D> test;
    HR(CreateDDSTexture2DArrayFromFile(
        m_pd3dDevice.Get(),
        m_pd3dImmediateContext.Get(),
        std::vector<std::wstring>{
            L"Texture\\tree0.dds",
            L"Texture\\tree1.dds",
            L"Texture\\tree2.dds",
            L"Texture\\tree3.dds"},
        test.GetAddressOf(),
        mTreeTexArray.GetAddressOf()));
    m_BasicEffect.SetTextureArray(mTreeTexArray.Get());

    // 初始化點精靈緩衝區
    InitPointSpritesBuffer();

    // 初始化樹的材質
    m_TreeMat.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    m_TreeMat.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    m_TreeMat.specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 16.0f);

    ComPtr<ID3D11ShaderResourceView> texture;
    // 初始化地板
    m_Ground.SetBuffer(m_pd3dDevice.Get(), Geometry::CreatePlane(XMFLOAT3(0.0f, -5.0f, 0.0f), XMFLOAT2(100.0f, 100.0f), XMFLOAT2(10.0f, 10.0f)));
    HR(CreateDDSTextureFromFile(m_pd3dDevice.Get(), L"Texture\\Grass.dds", nullptr, texture.GetAddressOf()));
    m_Ground.SetTexture(texture.Get());
    Material material{};
    material.ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
    material.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    material.specular = XMFLOAT4(0.2f, 0.2f, 0.2f, 16.0f);
    m_Ground.SetMaterial(material);

    // ******************
    // 初始化不會變化的值
    //

    // 方向光
    DirectionalLight dirLight[4];
    dirLight[0].ambient = XMFLOAT4(0.1f, 0.1f, 0.1f, 1.0f);
    dirLight[0].diffuse = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);
    dirLight[0].specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 1.0f);
    dirLight[0].direction = XMFLOAT3(-0.577f, -0.577f, 0.577f);
    dirLight[1] = dirLight[0];
    dirLight[1].direction = XMFLOAT3(0.577f, -0.577f, 0.577f);
    dirLight[2] = dirLight[0];
    dirLight[2].direction = XMFLOAT3(0.577f, -0.577f, -0.577f);
    dirLight[3] = dirLight[0];
    dirLight[3].direction = XMFLOAT3(-0.577f, -0.577f, -0.577f);
    for (int i = 0; i < 4; ++i)
        m_BasicEffect.SetDirLight(i, dirLight[i]);

    // ******************
    // 初始化攝像機
    //
    auto camera = std::shared_ptr<FirstPersonCamera>(new FirstPersonCamera);
    m_pCamera = camera;
    camera->SetViewPort(0.0f, 0.0f, (float)m_ClientWidth, (float)m_ClientHeight);
    camera->SetPosition(XMFLOAT3());
    camera->SetFrustum(XM_PI / 3, AspectRatio(), 1.0f, 1000.0f);
    camera->LookTo(
        XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f),
        XMVectorSet(0.0f, 0.0f, 1.0f, 1.0f),
        XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
    camera->UpdateViewMatrix();

    m_BasicEffect.SetWorldMatrix(XMMatrixIdentity());
    m_BasicEffect.SetViewMatrix(camera->GetViewXM());
    m_BasicEffect.SetProjMatrix(camera->GetProjXM());
    m_BasicEffect.SetEyePos(camera->GetPositionXM());

    // ******************
    // 初始化霧效和天氣等
    //

    m_BasicEffect.SetFogState(m_FogEnabled);
    m_BasicEffect.SetFogColor(XMVectorSet(0.75f, 0.75f, 0.75f, 1.0f));
    m_BasicEffect.SetFogStart(15.0f);
    m_BasicEffect.SetFogRange(75.0f);
    
    return true;
}

其他方法限於篇幅就不放在這裏了,讀者能夠查看源碼觀察剩餘部分的代碼實現。如今來看實現效果吧。

實現效果

能夠觀察到,在與公告版近距離接觸時能夠很明顯地看到公告板在跟着攝像機旋轉。若是距離很遠的話轉動的幅度就會很小,用戶纔會比較難以分辨出遠處物體是否爲公告板或3D模型了。

下面演示了白天和黑夜的霧效

最後則是Alpha-To-Coverage的開啓/關閉效果對比

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。

相關文章
相關標籤/搜索