1. 程式人生 > >D3D12渲染技術之光源

D3D12渲染技術之光源

燈光有Point光源,Spot光源,Directional光源,Area光源等等,網上這方面的文章很多,在此我們就不詳細介紹每個光源的計算公式了,我們直接介紹將光源應用到我們的案例中。先看我們已經實現好的案例截圖:
在這裡插入圖片描述

頂點格式

照明計算需要表面法線, 我們使用頂點定義法線,然後在三角形的畫素上插入這些法線,以便我們可以對每個畫素進行光照計算。 而且,我們不再指定頂點顏色, 相反,通過對每個畫素應用照明方程來生成畫素顏色, 為了支援頂點法線,我們修改我們的頂點結構,如下所示:

// C++ Vertex structure
struct Vertex
{
  DirectX::XMFLOAT3 Pos;
  DirectX::XMFLOAT3 Normal;
};
 
// Corresponding HLSL vertex structure
struct VertexIn
{
  float3 PosL  : POSITION;
  float3 NormalL : NORMAL;
};
When we add a new vertex format, we need to describe it with a new input layout description:
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 }
};

法線計算

GeometryGenerator中的形狀函式已經建立了具有頂點法線的資料,因此我們都設定在那裡。 但是,因為我們在這個演示中修改網格的高度使其看起來像地形,我們需要自己為地形生成法線向量。
因為我們的地形表面由函式y = f(x,z)給出,我們可以使用微積分直接計算法向量,而不是前面描述的常規平均計算。 對於曲面上的每個點,我們通過取偏導數在+ x-和+ z-方向上形成兩個切向量:
在這裡插入圖片描述
這兩個向量位於表面點的切平面中, 計演算法線向量得到:
在這裡插入圖片描述
我們用於生成地形網格的函式是:
在這裡插入圖片描述
偏導數是:
在這裡插入圖片描述
因此,表面點(x,f(x,z),z)處的表面法線由下式給出:
在這裡插入圖片描述
我們注意到這個曲面法線不是單位長度,因此需要在光照計算之前進行標準化。特別是,我們在每個頂點處進行上述常規計算以獲得頂點法線:

XMFLOAT3 LitWavesApp::GetHillsNormal(float x, float z)const
{
  // n = (-df/dx, 1, -df/dz)
  XMFLOAT3 n( -0.03f*z*cosf(0.1f*x) - 0.3f*cosf(0.1f*z),
    1.0f,
    -0.3f*sinf(0.1f*x) + 0.03f*x*sinf(0.1f*z));
 
  XMVECTOR unitNormal = XMVector3Normalize(XMLoadFloat3(&n));
  XMStoreFloat3(&n, unitNormal);
 
  return n;
}

水面的法向向量以類似的方式完成, 可以使用有限差分方案來近似每個頂點處的切向量。

更新燈光方向

我們的Lights陣列被放入per-pass常量緩衝區, 該案例使用一個方向燈來表示太陽,並允許使用者使用向左,向右,向上和向下箭頭鍵旋轉太陽位置。 因此,每一幀,我們都需要從太陽計算新的光方向,並將其設定為通道常量緩衝區。
我們以球座標(ρ,θ,φ)跟蹤太陽位置,但半徑ρ無關緊要,因為我們假設太陽是無限遠的。 特別是,我們只使用ρ= 1使其位於單位球面上並將(1,θ,φ)解釋為朝向太陽的方向,以下是更新太陽的相關程式碼。

float mSunTheta = 1.25f*XM_PI;
float mSunPhi = XM_PIDIV4;
 
void LitWavesApp::OnKeyboardInput(const GameTimer& gt)
{
  const float dt = gt.DeltaTime();
 
  if(GetAsyncKeyState(VK_LEFT) & 0x8000)
    mSunTheta -= 1.0f*dt;
 
  if(GetAsyncKeyState(VK_RIGHT) & 0x8000)
    mSunTheta += 1.0f*dt;
 
  if(GetAsyncKeyState(VK_UP) & 0x8000)
    mSunPhi -= 1.0f*dt;
    if(GetAsyncKeyState(VK_DOWN) & 0x8000)
    mSunPhi += 1.0f*dt;
 
  mSunPhi = MathHelper::Clamp(mSunPhi, 0.1f, XM_PIDIV2);
}
 
void LitWavesApp::UpdateMainPassCB(const GameTimer& gt)
{
    …
  XMVECTOR lightDir = -MathHelper::SphericalToCartesian(1.0f, mSunTheta, mSunPhi);
 
  XMStoreFloat3(&mMainPassCB.Lights[0].Direction, lightDir);
  mMainPassCB.Lights[0].Strength = { 0.8f, 0.8f, 0.7f };
 
  auto currPassCB = mCurrFrameResource->PassCB.get();
  currPassCB->CopyData(0, mMainPassCB);
}
 

Light陣列放入per-pass常量緩衝區意味著每個渲染通道不能超過16個(我們支援的最大光數)燈, 這對於Demo來說已經足夠了。 然而,對於大型遊戲世界來說,這還不夠,因為你可以想象有數百個燈光的遊戲關卡遍佈整個關卡, 解決此問題的一種方法是將Light陣列移動到每個物件的常量緩衝區。 然後,對於每個物件,搜尋場景並找到影響物件的燈光,並將這些燈光繫結到常量緩衝區。 影響物件的燈光是其體積(點光源和聚光燈錐體)與其相交的燈光, 另一種流行的策略是使用延遲渲染或前向+渲染。

Lighting為我們的著色器程式引入了一個新的材質常量緩衝區, 為了支援這一點,我們需要更新根簽名以支援額外的常量緩衝區。 與每個物件常量緩衝區一樣,我們使用材質常量緩衝區的根描述符來支援直接繫結常量緩衝區,而不是通過描述符堆。