Windows 8 Directx 開發學習筆記(八)要有光

上一篇已經完成水波紋模型,但是只是在線框模式下能清晰的看到波動效果,實體填充時無法看出水面變化,主要原因就是沒有引入光照。這裏通過更改頂點着色器和像素着色器,引入水面的漫反射效果,讓整個模型更加真實。

爲簡化漫反射模型,假設光照射物體時,反射光會在物體表面均勻散開。這樣,無論觀察點在哪裏,總能看到反射光。物體表面漫反射光的顏色可以用蘭伯特餘弦定理進行計算,如下圖所示:


n是法向量,L是與光照方向相反的單位向量。n*L就是當前點的受光強度,再乘入射光的顏色就是實際照在物體表面的光,最後將其與物體本身顏色混合,得到漫反射後的表面顏色。計算光照可由GPU完成,即頂點着色器和像素着色器,所以明白算法之後開始更改HLSL代碼。

根據上面的公式,計算光照時需要用到法向量、光照方向、光照顏色及物體表面顏色。其中光照方向和光照顏色可以在像素着色器中定義,物體表面顏色已經在之前的文章中定義。只需在頂點着色器和像素着色器的輸入輸出增加法向量成員。代碼如下:

struct VertexShaderInput
{
    float3 pos : POSITION;
    float3 color : COLOR;
    float3 normal : NORMAL;
};
 
struct VertexShaderOutput
{
    float4 pos : SV_POSITION;
    float3 color : COLOR;
    float3 normal : NORMAL;
};
struct PixelShaderInput
{
    float4 pos : SV_POSITION;
    float3 color : COLOR;
    float3 normal : NORMAL;
};

頂點着色器只需將法向量轉換到世界空間並按公式要求歸一化,所以在其main方法裏添加如下代碼:

float4 normal = float4(input.normal, 0.0f);
normal = mul(normal, model);
output.normal = normalize(normal.xyz);

計算物體的最終顏色是像素着色器的任務。這裏設光照顏色爲(0.9,0.9,0.9),光照方向爲(1,-1,0),相當於從屏幕右上角斜45度入射。四個變量都已經定義,可以按照公式分三步計算出最終的顏色,代碼如下:

lightIntensity = saturate(dot(input.normal, lightDir));
finalColor = saturate(diffuseColor* lightIntensity);
finalColor = finalColor * float4(input.color,1.0f);

至此,GPU部分的代碼編寫完成。

之前的文章中提到,在主程序代碼中載入頂點着色器和像素着色器時,需要對其結構進行說明,接下來要做的就是修改主程序中涉及着色器的代碼。首先更改Direct3Dbase.h中結構體VertexPositionColor的定義,因爲在着色器中多出一個normal成員。

struct VertexPositionColor
{
    DirectX::XMFLOAT3 pos;
    DirectX::XMFLOAT3 color;
    DirectX::XMFLOAT3 normal;
};

接着切換到Renderer.cpp文件,修改CreateDeviceResources方法中輸入佈局的定義:

const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
       {
           { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
           { "COLOR",    0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
           { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
       };

然後更改山峯和水面模型中,負責生成頂點的代碼。如今多了一個normal成員,需要在生成頂點時一併算出其法向量。

山峯模型各頂點的法向量就是對其生成公式求導,可用一個方法專門計算。代碼如下:

XMFLOAT3 HillModel::GetHillNormal(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;
}

完成後在HillModel的Initialize方法裏添加以下代碼,生成法向量。

Vertices[xRange*row + col].normal = GetHillNormal(xPos,zPos);

水模型的法向量計算比較麻煩,因爲波動時水面的法向量方向也在變化。在WaterModel類裏添加一個mNormal成員專門保存水面的法向量。

在初始化水平面時,法向量肯定都平行於y軸方向,均設爲(0,1,0)

mNormal[xRange*row + col] = XMFLOAT3(0.0f, 1.0f, 0.0f);

在更新水面波動狀態時,除了計算水面各頂點的位置外,現在還要增加各頂點法向量的計算。在Update方法中增加以下代碼更新法向量緩衝區:

for(int row = 1; row < (xRange-1); ++row)
{
	for(int col = 1; col < (zRange-1); ++col)
	{
		float l = mCurrSolution[row*xRange+col-1].y;
		float r = mCurrSolution[row*xRange+col+1].y;
		float t = mCurrSolution[(row-1)*xRange+col].y;
		float b = mCurrSolution[(row+1)*xRange+col].y;
		mNormal[row*xRange+col].x = -r+l;
		mNormal[row*xRange+col].y = 2.0f*mSpatialStep;
		mNormal[row*xRange+col].z = b-t;

		XMVECTOR n = XMVector3Normalize(XMLoadFloat3(&mNormal[row*xRange+col]));
		XMStoreFloat3(&mNormal[row*xRange+col], n);
	}
}

最後將山峯模型和水模型的渲染模式設爲實體填充,顯示效果如下:


在現實世界中,光照模型不只包括漫反射光,還有其他像點光、平行光、高光等模型。現在實現的效果離實際情況還有很遠的距離,需要引入更多參數。


附本篇文章的源代碼:

Direct3DApp_HillWaveExample

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章