龙书和SDK光照的例子也看到几个了,今天看《GPU 编程与CG 语言之阳春白雪下里巴人》系统的学了学,也能写写shader练练手了。1个月前让我抱着一本图形学书死啃的话,我肯定读不出来个所以然,因为那时才刚刚学完固定流水线,对3D还处于混沌的状态。今天学习光照模型,所以特意翻开了
图形学书查看资料,惊奇的发现,我实践中学到的大部分不甚明了的知识,在书中都有解答。现在明白了,为什么
图形学是基本功,过段时间我得找本好点的图形学圣经,好好看看了。光是畏惧严谨的原理阐述和数学知识是不行得。
还有,学了shader这么些天,看倒是看得明白,今天自己写简单的shader,发现问题真是不少,看来真是纸上得来终觉浅,绝知此事要躬行啊。
编程,就是要多动手实验,测试,才能练出真功夫。
1。Lambert光照模型,也就是漫反射光照模型。它考虑的是ambient光和diffuse光对物体的综合影响。
下面是我写的shader程序:
matrix ViewMatrix;
matrix ProjMatrix;
matrix WorldMatrix_IT; // 世界矩阵的逆再转置,为了把顶点法向量变换到世界空间
float3 g_LightPos = float3( - 3 , 3 , - 10 );
float3 g_ambient = float3( 0.2f , 0.2f , 0.2f );
float3 g_LightColor = float3( 1.0f , 1.0f , 1.0f );
struct VS_INPUT
{
float4 inPos : POSITION;
float4 inNormal : NORMAL;
};
struct VS_OUTPUT
{
float4 oPos : POSITION;
float4 color : COLOR0;
};
// 顶点着色器入口函数
VS_OUTPUT VS_MAIN ( VS_INPUT In )
{
VS_OUTPUT Out = ( VS_OUTPUT) 0 ;
float4 worldPos = mul(In.inPos, WorldMatrix);
Out.oPos = mul(worldPos, ViewMatrix);
Out.oPos = mul(Out.oPos, ProjMatrix);
float3 N = mul( In.inNormal, WorldMatrix_IT).xyz;
N = normalize(N);
// 计算入射光方向
float3 L = normalize( g_LightPos - worldPos.xyz);
// 计算漫反射光强
float3 diffuse = g_LightColor * saturate (dot( L, N) );
// 计算总光强
Out.color.xyz = diffuse + g_ambient;
Out.color.w = 1 ;
return Out;
}
float4 PS_MAIN( VS_OUTPUT In) : color0
{
return In.color;
}
technique LightAndTexture
{
pass P0
{
VertexShader = compile vs_2_0 VS_MAIN( );
PixelShader = compile ps_2_0 PS_MAIN( );
}
}
效果图:
2。Phong光照模型,这个模型计算了镜面高光(Specular):当入射光照射在一些光滑的表面时,在反射角的一定区域内,会
形成很强的光亮,因为反射光反射了入射光的大部分光强。这时候,当观察角度接近反射角时,就会看到物体表面的高光。
所以镜面反射的光强与反射光和视线的夹角相关。其计算公式为:
ks为材质的镜面反射系数, ns 是高光指数,V 表示从顶点到视点的观察方
向, R 代表反射光方向。
高光指数反映了物体表面的光泽程度。ns越大,反射光越集中,当偏离反射
方向时,光线衰减的越厉害,只有当视线方向与反射光线方向非常接近时才能看
到镜面反射的高光现象,此时,镜面反射光将会在反射方向附近形成亮且小的光
斑; ns越小,表示物体越粗糙,反射光分散,观察到的光斑区域小,强度弱。
将镜面反射与漫反射,环境光一起使用,能使得物体更具有真实感。
书上说:镜面反射的高光颜色应该等于光源的颜色,物体的的材质颜色应该使用在漫反射和环境光上。
SDK里面那个specular.fx写得很漂亮,可以用来学习。不过注意,SDK里面的顶点法向量变换到世界空间全是错了的,应该乘以世界矩阵的逆的转置.
下面是我写的shader,很恼火的是,开始看着画面亮亮的一团,根本看不出来高光区,怀疑是模型太简陋,又怀疑是光源离模型太近,
后来到处改,发现把diffuse光强改小点,才能看出高光。真是发晕。
这里我澄清了自己一直的一个迷惑,物体的颜色似乎既可以可以由灯光颜色决定,也可以由D3DMATERIAL9中的Emissive成员决定(它表示
物体自身发出光的颜色,即没有光照也能看到的颜色),还可以由物体材质决定。其实在shader中计算顶点最终颜色时,几个算式包含的内容已经说明了
这个问题。我们设定光源颜色为白色:float3(1.0, 1.0, 1.0),然后假设光照只由散射光组成。然后我们想让物体材质在散射光下反射红色(1.0, 0.0, 0.0),
那么直接给光源颜色乘以float3(1.0, 0.0, 0.0)。注意了,这个乘法就相当于在直接对光源颜色做过滤啊!所以把D3DMATERIAL9中的几个分量理解成材质颜色是不对的,而应该是反射颜色百分比。这就越说越绕了,其实这个计算过程非常符合光照的物理过程:光源发出灯光,在材质表面反射,材质决定吸收灯光的什么分量和反射什么分量,而材质本身是不具有颜色的!在现实世界中,我们看到的物体颜色不是属于物体的,而是光与其作用后反射的。就这么个问题,初中就学了,现在还混淆得不清楚,真是头痛!
//计算漫反射光强
float3 diffuse = g_LightColor * saturate (dot( L, N) ) * float3(1.0f, 0.0f, 0.0f) + g_ambient;
matrix ViewMatrix;
matrix ProjMatrix;
matrix WorldMatrix_IT; // 世界矩阵的逆再转置,为了把顶点法向量变换到世界空间
float3 g_LightPos = float3( - 2 , - 5 , - 10 );
float3 g_ambient = float3( 0.3f , 0.3f , 0.3f );
float3 g_LightColor = float3( 1.0f , 1.0f , 1.0f );
float g_shininess = 20.0f ;
float4 g_eyePos;
struct VS_INPUT
{
float4 inPos : POSITION;
float3 inNormal : NORMAL;
};
struct VS_OUTPUT
{
float4 oPos : POSITION;
float3 worldPos : texcoord0;
float3 worldNormal : texcoord1;
};
// 顶点着色器入口函数
VS_OUTPUT VS_MAIN ( VS_INPUT In )
{
VS_OUTPUT Out = ( VS_OUTPUT) 0 ;
float4 wPos = mul(In.inPos, WorldMatrix);
Out.worldPos = wPos.xyz;
Out.oPos = mul(wPos, ViewMatrix);
Out.oPos = mul(Out.oPos, ProjMatrix);
Out.worldNormal = normalize( mul( In.inNormal, WorldMatrix_IT ) );
return Out;
}
// 像素着色器入口函数
float4 PS_MAIN( VS_OUTPUT In) : color0
{
float3 N = In.worldNormal;
// 计算入射光方向
float3 L = normalize( g_LightPos - In.worldPos );
// 计算反射光方向
float3 R = reflect( - L, N );
// 计算视线方向
float3 V = normalize( g_eyePos.xyz - In.worldPos );
// 计算漫反射光强
float3 diffuse = g_LightColor * saturate (dot( L, N) ) + g_ambient;
// 计算镜面反射光强
float3 specular = g_LightColor * pow( saturate (dot( R, V) ), g_shininess );
// 计算总光强
float4 color;
color.xyz = diffuse * float3( 0.7f , 0.7f , 0.7f ) + specular ; // 漫反射光强改小了,才看出来高光效果
color.w = 1 ;
return color;
}
technique LightAndTexture
{
pass P0
{
VertexShader = compile vs_2_0 VS_MAIN( );
PixelShader = compile ps_2_0 PS_MAIN( );
}
}
效果图:
3.Blinn-phong光照模型。它是以Phong模型为基础的,效果是能让高光更加柔和,更平滑。其实这个模型的效果并不比Phong模型
高级,我看的书上说:使用blinn-phong 进行光照渲染,在同样的高光系数下,高光领域覆盖范围较大,明暗界限不明显。所以它真实感
还没Phong模型强。但是这个模型运算速度要快些。因此在DX中默认的高光模型就是它。Blinn-phong光照模型公式为:
公式与Phong模型公式不同的是,R点乘V变成了N点乘H。N是顶点法向量,H是所谓的半角向量(half way vector).它等于入射方向L和视线方向V
的中间向量。H有什么几何意义呢?我也还不知道..
代码就跟phong几乎一样了,就不贴了,看看效果图:
4.Cook-Torrance光照模型。
前面三个模型都是简单光照模型,它们没有考虑物体材质的细节,如粗糙平面,因此真实感不足。Cook-Torrance模型考虑了粗糙表面,它的漫反射光强
计算跟前面相同,只是高光计算部分不同。我们要认识到,既然要想模拟更为真实的高级光照,那么肯定要遵循物理世界的定律,模型的计算公式必定要
变复杂不少,还要加入很多物理参数。下面这个就是该光照模型的计算公式:
其中:V为视线方向向量,H为半角向量,L为入射向量,阿尔法是N和H的夹角,m度量表面的粗糙程度,越粗糙m越大,f0为入射角度接近0时的Fresnel 反射系数。
看到这么大个公式都让人晕啊,我猜想它的实用性应该不强吧,能用在实时光照中吗?速度怎么样?
我也懒得动手实验了,这个模型就先了解到这里吧。。贴个书上的效果图:
5.Bank BRDF光照模型。
BRDF就是双向反射分布函数的意思,它描述了入射光线在某个反射角度的反射光的相对能量。所以给定不同的BRDF函数,就能实现不同的光照效果。
这些数学问题不深入研究是搞不懂的,我学学模型的公式,了解一些原理就是了。 具体的BRDF模型有:HTSG BRDF模型,它擅长模拟很多物理现象,
是现今最完整的BRDF 模型,但是同时需要昂贵的计算开销;Ward BRDF ,用于各向异性表面的经验模型有些复杂,并且需要从实际物体表面来获取
BRDF 数据。
有一个实现简单,速度快的Bank BRDF模型,它能模拟各向异性光照效果,其镜面反射部分的计算公式是:
ks 、ns 分别表示镜面反射系数和高光系数;L 表示入射光线方向、V 表示视线方向,T 表示顶点的切向量。计算T的一种方法是用N和V叉乘得到。
下面是代码:
matrix ViewMatrix;
matrix ProjMatrix;
matrix WorldMatrix_IT; // 世界矩阵的逆再转置,为了把顶点法向量变换到世界空间
float3 g_LightPos = float3( - 3 , 0 , - 10 );
float3 g_ambient = float3( 0.3f , 0.3f , 0.3f );
float3 g_LightColor = float3( 1.0f , 1.0f , 1.0f );
float g_shininess = 20.0f ;
float4 g_eyePos;
// 顶点着色器入口函数
void VS_MAIN ( float4 inPos : POSITION,
float3 inNormal : NORMAL,
out float4 oPos : POSITION,
out float3 worldPos : texcoord0,
out float3 worldNormal : texcoord1 )
{
float4 wPos = mul( inPos, WorldMatrix);
worldPos = wPos.xyz;
oPos = mul(wPos, ViewMatrix);
oPos = mul(oPos, ProjMatrix);
worldNormal = normalize( mul( inNormal, ( float3x3 )WorldMatrix_IT ) );
}
// 像素着色器入口函数
float4 PS_MAIN( float3 worldPos : texcoord0,
float3 worldNormal : texcoord1) : color0
{
float3 N = worldNormal;
// 计算入射光方向
float3 L = normalize( g_LightPos - worldPos );
// 计算视线方向
float3 V = normalize( g_eyePos.xyz - worldPos );
// 计算漫反射光强
float3 diffuse = g_LightColor * saturate (dot( L, N) ) + g_ambient;
float3 specular = float3( 0.0f , 0.0f , 0.0f );
bool back = ( dot( L, N ) ) && ( dot( N, V ) > 0 );
// 若不满足条件则高光为0
if (back)
{
// 计算顶点切向量
float3 T = normalize( cross( N, V ) );
float3 LT = dot( L, T );
float3 VT = dot( V, T );
float3 a = sqrt( 1 - pow( LT, 2.0f ) ) * sqrt( 1 - pow( VT, 2.0f ) ) - LT * VT;
specular = pow( a, g_shininess );
}
// 计算总光强
float4 color;
color.xyz = diffuse * float3( 0.6f , 0.6f , 0.6f ) + specular ;
color.w = 1 ;
return color;
}
technique LightAndTexture
{
pass P0
{
VertexShader = compile vs_2_0 VS_MAIN( );
PixelShader = compile ps_2_0 PS_MAIN( );
}
}
效果图:
可以很清楚地看出这种各向异性光照效果。