Directx11学习笔记【十六】 光照模型的实现
本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5579289.html
在之前的场景绘制中我们都是给每个顶点指定了单一颜色 ,然后由系统插值计算各个部分颜色,最终显示出来。接下来我们将学习dx11中比较有意思的一部分——光照,通过光照和材质的相互作用来实现更真实的场景。
1. 光照
1.1 光照效果
简单举个例子,看龙书中的一张图:
a图没加光照看起来像一个2D图形,而加了光照的b图则看起来像一个3D图形。由此可见,光照在3D渲染方面是尤为重要的,通常借助光照可以让场景显得更加真实。
1.2 材质
材质可以说是决定光如何作用在物体表面的一种属性。例如,光在物体表面反射和吸收的颜色,它的反射率、透明度、光滑程度等属性组成了一个物体表面的材质。在我们下面的示例一般只考虑光的反射和吸收颜色以及光滑程度用来计算全反射,暂不考虑其它因素。
2.法线
2.1面法线(face normal)
什么是面法线呢?A face normal is a vector that describesthe direction a polygon is facing。
计算面法线也很简单,在面内找到三点,取得两个向量作×积然后单位化即可。
2.2 顶点法线(vertex normal)
在directx中我们需要知道顶点的法线,法线决定了光照射平面的角度。光线会被应用到每个顶点,并且根据面法线和光照方向的点积去调整光线颜色的强度。那么怎么计算顶点法线呢?下面的图介绍的很清楚了:
2.3 法线变换
在一个顶点进行空间变换时,法线通常也需要进行变换。但是注意:顶点和法线的变换矩阵并不相同!
对于一个顶点的变换矩阵A,其对应的法线变换时A的逆矩阵的转置。
2.4 朗伯余弦定律
当一个面元的辐射亮度和光亮度在其表面上半球的所有方向相等时,并符合I(θ) = INcosθ时称为朗伯余弦定律。
I(θ)----面元在θ角(与表面法线夹角)方向及其法线方向的辐射强度
IN----面元在θ角方向及其法线方向的发光亮度
3. 光照计算处理的三部分
光照计算处理共有三个部分:环境光(ambient)、漫反射光(diffuse)以及全反射光(又称高光,specular)。
3.1 环境光(ambient)
在现实当中,光照是一个很复杂的物理现象。一个物体所接受的光,除了直接来自光源的部分外,还包括光源经过环境中其他各个物体的反射而来的部分。而在图形学中,我们默认的光照模型为局部光模型,即一个顶点的光照计算只跟该点信息与光源信息有关,而不考虑环境中其他物体的影响,比如阴影等。
3.2 漫反射光(diffuse)
光照射在物体表面后,其反射光沿随机方向均匀的分布,即"漫反射”。反射光的强度与光照方向与表面法线的夹角theta相关,满足比例关系:I = Io * cos(theta)。由于反射光方向随机,因此该部分的计算与观察点无关,而只与光线方向与法线相关。
3.3 全反射光(specular)
光线照射在光滑物体表面后,在特定方向上会有很强的反射,即发生全反射。全反射光主要集中在一个近似圆锥角的范围内。如下图所示:
n为法线,l为光线入射方向,r为全反射方向,E为观察点,因此v为视角方向。全反射光进入眼睛的强度与v和r的角度theta有关,随着该角度增大,全反射强度下降,其下降辐度与物体表面光滑程序相关。因此,对于该部分光的计算,除了需要光线方向、法线等信息外,还与观察点的位置有很大关系。具体计算公式在本文后面会详细给出。
4. 光源模型
4.1 平行光
平行光是最简单的一种光照模型,光照方向不变而且光照强度不随空间位置改变,可以用平行光来模拟日常生活中的太阳光。
c++程序中平行光的定义:
struct DirectionalLight { DirectionalLight() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient;//环境光 XMFLOAT4 Diffuse;//漫反射光 XMFLOAT4 Specular;//高光 XMFLOAT3 Direction;//光照方向 float Pad; // Pad the last float so we can set an array of lights if we wanted.用于与HLSL中“4D向量”对齐规则匹配 };
4.2 点光源
c++程序中点光源的定义:
struct PointLight { PointLight() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient; XMFLOAT4 Diffuse; XMFLOAT4 Specular; // Packed into 4D vector: (Position, Range) XMFLOAT3 Position;//光源位置 float Range; //光照范围 // Packed into 4D vector: (A0, A1, A2, Pad) XMFLOAT3 Att; //衰减系数 float Pad; // Pad the last float so we can set an array of lights if we wanted. };
4.3 聚光灯
c++程序中聚光灯定义:
struct SpotLight { SpotLight() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient; XMFLOAT4 Diffuse; XMFLOAT4 Specular; // Packed into 4D vector: (Position, Range) XMFLOAT3 Position;//光照位置 float Range; //光照范围 // Packed into 4D vector: (Direction, Spot) XMFLOAT3 Direction;//光照方向 float Spot; //光照强度系数 // Packed into 4D vector: (Att, Pad) XMFLOAT3 Att; //衰减系数 float Pad; // Pad the last float so we can set an array of lights if we wanted. };
4.4 HLSL中三种光源的定义
1 struct DirectionalLight 2 { 3 float4 Ambient; 4 float4 Diffuse; 5 float4 Specular; 6 float3 Direction; 7 float pad; 8 }; 9 10 struct PointLight 11 { 12 float4 Ambient; 13 float4 Diffuse; 14 float4 Specular; 15 16 float3 Position; 17 float Range; 18 19 float3 Att; 20 float pad; 21 }; 22 23 struct SpotLight 24 { 25 float4 Ambient; 26 float4 Diffuse; 27 float4 Specular; 28 29 float3 Position; 30 float Range; 31 32 float3 Direction; 33 float Spot; 34 35 float3 Att; 36 float pad; 37 };
4.5 材质
材质同样有环境光、漫反射光和高光三种成分,此外还有一个材质的镜面反射系数即表示光滑程度。
c++程序中定义:
struct Material { Material() { ZeroMemory(this, sizeof(this)); } XMFLOAT4 Ambient; XMFLOAT4 Diffuse; XMFLOAT4 Specular;//w表示高光强度 XMFLOAT4 Reflect; };
HLSL定义:
struct Material { float4 Ambient; float4 Diffuse; float4 Specular; float4 Reflect; };
5. 光照计算
光照计算无疑是最重要的部分,这一部分在HLSL中实现。
5.1 平行光
void ComputeDirectionalLight(Material mat, //材质 DirectionalLight L, //平行光 float3 normal, //顶点法线 float3 toEye, //顶点到眼睛的向量 out float4 ambient, //计算结果:环境光 out float4 diffuse, //计算结果:漫反射光 out float4 spec) //计算结果:高光 { // 结果初始化为0 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); spec = float4(0.0f, 0.0f, 0.0f, 0.0f); // 光线方向 float3 lightVec = -L.Direction; // 环境光直接计算 ambient = mat.Ambient * L.Ambient; // 计算漫反射系数 //光线、法线方向归一化 float diffuseFactor = dot(lightVec, normal); // 顶点背向光源不再计算 [flatten] if (diffuseFactor > 0.0f) { float3 v = reflect(-lightVec, normal); float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); //计算漫反射光 diffuse = diffuseFactor * mat.Diffuse * L.Diffuse; //计算高光 spec = specFactor * mat.Specular * L.Specular; } }
5.2 点光源
1 void ComputePointLight(Material mat, //材质 2 PointLight L, //点光源 3 float3 pos, //顶点位置 4 float3 normal, //顶点法线 5 float3 toEye, //顶点到眼睛的向量 6 out float4 ambient, //计算结果:环境光 7 out float4 diffuse, //计算结果:漫反射光 8 out float4 spec) //计算结果:高光 9 { 10 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); 11 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); 12 spec = float4(0.0f, 0.0f, 0.0f, 0.0f); 13 14 //光照方向:顶点到光源 15 float3 lightVec = L.Position - pos; 16 17 //顶点到光源距离 18 float d = length(lightVec); 19 20 //超过范围不再计算 21 if (d > L.Range) 22 return; 23 24 //归一化光照方向 25 lightVec /= d; 26 27 //计算环境光 28 ambient = mat.Ambient * L.Ambient; 29 30 //漫反射系数 31 float diffuseFactor = dot(lightVec, normal); 32 33 [flatten] 34 if (diffuseFactor > 0.0f) 35 { 36 float3 v = reflect(-lightVec, normal); 37 float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); 38 //计算漫反射光 39 diffuse = diffuseFactor * mat.Diffuse * L.Diffuse; 40 //计算高光 41 spec = specFactor * mat.Specular * L.Specular; 42 } 43 44 // 计算衰减 45 float att = 1.0f / dot(L.Att, float3(1.0f, d, d*d)); 46 47 diffuse *= att; 48 spec *= att; 49 }
5.3 聚光灯
1 void ComputeSpotLight(Material mat, //材质 2 SpotLight L, //聚光灯 3 float3 pos, //顶点位置 4 float3 normal, //顶点法线 5 float3 toEye, //顶点到眼睛向量 6 out float4 ambient, //计算结果:环境光 7 out float4 diffuse, //计算结果:漫反射光 8 out float4 spec) //计算结果:高光 9 { 10 //初始化结果 11 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f); 12 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f); 13 spec = float4(0.0f, 0.0f, 0.0f, 0.0f); 14 15 //光照方向:顶点到光源 16 float3 lightVec = L.Position - pos; 17 18 //顶点到光源距离 19 float d = length(lightVec); 20 21 //距离大于光照方向不再计算 22 if (d > L.Range) 23 return; 24 25 //归一化光照方向 26 lightVec /= d; 27 28 //计算环境光 29 ambient = mat.Ambient * L.Ambient; 30 31 32 //计算漫反射系数 33 float diffuseFactor = dot(lightVec, normal); 34 35 [flatten] 36 if (diffuseFactor > 0.0f) 37 { 38 float3 v = reflect(-lightVec, normal); 39 float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w); 40 //漫反射光 41 diffuse = diffuseFactor * mat.Diffuse * L.Diffuse; 42 //高光 43 spec = specFactor * mat.Specular * L.Specular; 44 } 45 46 //聚光衰减系数 47 float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot); 48 49 //衰减系数 50 float att = spot / dot(L.Att, float3(1.0f, d, d*d)); 51 52 ambient *= spot; 53 diffuse *= att; 54 spec *= att; 55 }
6.程序中使用的shader文件
#include "LightHelper.fx" cbuffer cbPerFrame { DirectionalLight gDirLight; PointLight gPointLight; SpotLight gSpotLight; float3 gEyePosW; //观察点 }; cbuffer cbPerObject { float4x4 gWorld; float4x4 gWorldInvTranspose;//世界矩阵的逆矩阵的转置 float4x4 gWorldViewProj; Material gMaterial; }; struct VertexIn { float3 PosL : POSITION; //顶点坐标 float3 NormalL : NORMAL; //顶点法线 }; struct VertexOut { float4 PosH : SV_POSITION; //投影后的坐标 float3 PosW : POSITION; //世界变换后的坐标 float3 NormalW : NORMAL; //世界变换后的顶点法线 }; VertexOut VS(VertexIn vin) { VertexOut vout; vout.PosW = mul(float4(vin.PosL, 1.0f), gWorld).xyz; vout.NormalW = mul(vin.NormalL, (float3x3)gWorldInvTranspose); vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj); return vout; } float4 PS(VertexOut pin) : SV_Target { //插值运算有可能使法线不再单位化,重新单位化法线 pin.NormalW = normalize(pin.NormalW); //顶点到观察点向量,归一化 float3 toEyeW = normalize(gEyePosW - 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, D, S; //每个光源计算后将ADS更新到最终结果中 ComputeDirectionalLight(gMaterial, gDirLight, pin.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; ComputePointLight(gMaterial, gPointLight, pin.PosW, pin.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; ComputeSpotLight(gMaterial, gSpotLight, pin.PosW, pin.NormalW, toEyeW, A, D, S); ambient += A; diffuse += D; spec += S; float4 litColor = ambient + diffuse + spec; //最终颜色透明度使用漫反射光的 litColor.a = gMaterial.diffuse.a; return litColor; } technique11 LightTech { pass P0 { SetVertexShader(CompileShader(vs_5_0, VS())); SetGeometryShader(NULL); SetPixelShader(CompileShader(ps_5_0, PS())); } }
7.程序运行结果
由于代码比较多,这里就不给出了,有兴趣的可以下载看看注释也比较详细
源码下载:https://files.cnblogs.com/files/zhangbaochong/LightDemo.zip