简单的光照模型

龙书和SDK光照的例子也看到几个了,今天看《GPU 编程与CG 语言之阳春白雪下里巴人》系统的学了学,也能写写shader练练手了。1个月前让我抱着一本图形学书死啃的话,我肯定读不出来个所以然,因为那时才刚刚学完固定流水线,对3D还处于混沌的状态。今天学习光照模型,所以特意翻开了
图形学书查看资料,惊奇的发现,我实践中学到的大部分不甚明了的知识,在书中都有解答。现在明白了,为什么
图形学是基本功,过段时间我得找本好点的图形学圣经,好好看看了。光是畏惧严谨的原理阐述和数学知识是不行得。

还有,学了shader这么些天,看倒是看得明白,今天自己写简单的shader,发现问题真是不少,看来真是纸上得来终觉浅,绝知此事要躬行啊。

编程,就是要多动手实验,测试,才能练出真功夫。

 

1。Lambert光照模型,也就是漫反射光照模型。它考虑的是ambient光和diffuse光对物体的综合影响。

下面是我写的shader程序:

 

 

复制代码
matrix WorldMatrix;
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;
=  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 WorldMatrix;
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 WorldMatrix;
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( );
}
}
复制代码

 

效果图:

 

 可以很清楚地看出这种各向异性光照效果。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值