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

 

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