shader 获取法线_Shader快速复习:Per Pixel Lighting(逐像素光照)

f16667eabf2f2e399921326ab04d6bb7.png

Shader快速复习,巩固知识加强感觉。今天的内容是Per Pixel Lighting(逐像素光照)。——ZwqXin.com

抛开光线跟踪和辐射度算法,现在的实时渲染主要用的是GOURAUD模型和PHONG模型,粗俗地说,就是一个是顶点级别的一个是像素级别的,对应per vertex lighting和per pixel lighting。细节点说,就是一个是对顶点插值,一个是对法线插值。具体的区别网络上到处可见辨析。我真正“接触”per pixel lighting,是在初学GLSL的时候。

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
原文地址:http://www.zwqxin.com/archives/shaderglsl/review-per-pixel-lighting-shader.html

在shader中,我们需要自己计算光照。这最初觉得应该算是麻烦活了,要知道,固定管道中只是简单的API啊,可是那毕竟只是per vertex lighting,在shader里写光照模型最好的就是可以方便地实现per pixel lighting。例如我的shadow map demo就用了,不过只计算散射光而已。

如上所言,per pixel lighting最重要的是法线插值这步,接下来得十分留意场景传入的法线在shader里的“流向”;同时不得不“提防”的还有半向量halfvector的“流向”。

经典的opengl光照模型是如下这样的:

c3c6f31926f585bfac5a7a285df9e530.png

但是这里先别理会自发光emission,不理会全局环境光,也不理会光的衰减等等。关注最重心的PHONG模型,有:

pixel color = ambient_color + diffuse_color + specular_color

像素最终颜色由3项直接组成,分别是环境光分量(底色),谩反射分量(由光源和物体的相对位置确定,一般作为主分量),镜面光分量(你可以看作物体外覆盖的一层“膜”,它除受光源和物体的相对位置影响外,还受视线影响)。更多相关内容可看任意一本图形学的书。综上,因此,传入shader的必要物是:物体位置,光源位置,眼睛位置;三种光的颜色;法线。另外还需要一个与镜面光分量相关的shiness参数,决定高光范围。

顶点shader要做的仅仅是把“力所能及”的东西做好。计算正确的光源向量,法线向量和半向量。半向量是“光向量与视线向量的‘半’”,等会再多解释。

  1. uniform vec3 lightpos;
  2. uniform vec4 eyepos;
  3. varying vec3 lightdir;
  4. varying vec3 halfvec;
  5. varying vec3 norm;
  6. void main(void)
  7. {
  8. vec4 pos = gl_ModelViewMatrix * gl_Vertex;
  9. pos = pos / pos.w;
  10. lightdir = normalize(lightpos - pos.xyz);
  11. vec3 eyedir = normalize(eyepos.xyz - pos.xyz);
  12. halfvec = normalize(lightdir + eyedir);
  13. norm = normalize(gl_NormalMatrix * gl_Normal);
  14. gl_FrontColor = gl_Color;
  15. gl_Position = ftransform();
  16. }

在像素shader里,首先是把从顶点shader插值而来的向量重新单位化。原因是插值过程中这些向量会把“大小”也插值了,而我们必须保证这些向量的单位化,从而保证不破坏光照模型。光源向量也可以不重新插值(如果它是方向光而不是点光源的话——取决于应用)。

  1. lightdir = normalize(lightdir);
  2. norm = normalize(norm);
  3. halfvec = normalize(halfvec);

三种光的公式:

  • ambient_color = ambient_light_color * ambient_material_color ;
  • diffuse_color = diffuse_light_color * diffuse_material_color * cos(Θ);
  • specular_color = specular_light_color * specular_material_color * cos(α)shiness.

其中,如果能保证各向量的正确单位化的话,有:

cos(Θ) = dot(light_vec , normal_vec) ;cos(α) = dot(reflect_vec , eye_vec) ;

2d1d3bc874224411c99779c21122539e.png

(from LightHouse3D)

190a7749269ab6c015b97740ad406c67.png

(from LightHouse3D)

reflect_vec的求解比较麻烦(主要是reflect这个函数有点耗GPU了),因此按blin模型,可以这样:

dot(reflect_vec , eye_vec) = dot(halfvec , normal_vec)

682c9ba75acb020eb81cd2e708dbad99.png

(from LightHouse3D)

这里的半向量 halfvec = eyevec - Lightvec(input),注意图中的都是入射光向量Lightvec(input) (= gl_vertex - lightpos) ,但我们shader里是用Lightvec (= lightpos - gl_vertex) ,因此halfvec的计算应该是 halfvec = eyevec + Lightvec,即vertex shader里那样。

简化一下,我shader里就预先把材质颜色和光源颜色合在一起传入,即ambient, diffuse, specular。另外设置三种光所占最终颜色的百分比(这个重要,因为即使是最终颜色,也不过是个0.0~1.0的颜色值,三种光的结果各自就是个0.0~1.0的颜色值,它们要求直接加合的话肯定得超过1.0,因此需要给予权值再加~)。

  1. uniform vec4 ambient, diffuse, specular;
  2. float amb = 0.3;
  3. float diff = 0.4;
  4. float spec = 0.3;

最后就是这样了:

  1. float diffusefract = max( dot(lightdir,norm) , 0.0);
  2. float specularfract = max( dot(halfvec,norm) , 0.0);
  3. specularfract = pow(specularfract, shiness);
  4. gl_FragColor = vec4(amb*ambient.xyz + diff * diffuse.xyz * diffusefract
  5. + spec * specular.xyz * specularfract ,1.0);

恩.....考虑到cos(Θ) 、cos(α)小于0的时候说明该物体部分不接受光照,直接取0.0(不产生光照颜色)就行。啊,对了,既然该部分不接受光照,那该部分何必还继续半向量呀取指数呀的计算?浪费。于是,最后的像素shader如下:

  1. uniform float shiness;
  2. uniform vec4 ambient, diffuse, specular;
  3. float amb = 0.3;
  4. float diff = 0.4;
  5. float spec = 0.3;
  6. varying vec3 lightdir;
  7. varying vec3 halfvec;
  8. varying vec3 norm;
  9. void main(void)
  10. {
  11. lightdir = normalize(lightdir);
  12. norm = normalize(norm);
  13. float diffusefract = max( dot(lightdir,norm) , 0.0);
  14. float specularfract = 0.0;
  15. if( diffusefract > 0.0){
  16. halfvec = normalize(halfvec);
  17. specularfract = max( dot(halfvec,norm) , 0.0);
  18. specularfract = pow(specularfract, shiness);
  19. }
  20. gl_FragColor = vec4(amb*ambient.xyz
  21. + diff * diffuse.xyz * diffusefract
  22. + spec * specular.xyz * specularfract ,1.0);
  23. }

效果查看(RenderMonkey):

f058ed8b4a70030126007a10566d0467.png


(改变模型,颜色,增加镜面光权值,减小shiness后:)

76baa68a3f06833f2fe5f388cb708d54.png

如果打算直接从opengl应用中取值,那注意下面这个内置的gl_LightSourceParameters结构,把相应的变量替换就可以了,例如上面的ambient, diffuse, specular等等,可以不用,而以gl_LightSource[0].ambient*gl_FrontMaterial].ambient表示; 另外halfvector甚至不用自己算。但这样做之前可记得在opengl实现里要指定glLightXXX这类函数喔。

获取应用中设定的光源特性,gl_LightSource[i]对应第i号光源struct gl_LightSourceParameters {vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;vec3 spotDirection;float spotExponent;float spotCutoff; // (range: [0.0,90.0], 180.0)float spotCosCutoff; // (range: [1.0,0.0],-1.0)float constantAttenuation;float linearAttenuation;float quadraticAttenuation;};uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights];

获取应用中设定的全局环境光 struct gl_LightModelParameters {vec4 ambient;};uniform gl_LightModelParameters gl_LightModel;

获取应用中设定的材质struct gl_MaterialParameters {vec4 emission; vec4 ambient; vec4 diffuse; vec4 specular; float shininess;};uniform gl_MaterialParameters gl_FrontMaterial;uniform gl_MaterialParameters gl_BackMaterial;

参考资料:LightHouse3D

(碎碎念:明明是快速复习的说,明明是快速复习的说.....)

2009-3-6

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值