简单的光照模型

【转载】学习了下简单的光照模型

龙书和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;
    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 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( );
    }
}



 

效果图:

 

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值