Shader快速复习:Per Pixel Lighting(逐像素光照)

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

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

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

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

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

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

pixel color = ambient_color + diffuse_color + specular_color

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

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

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

在像素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) ;


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

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

这里的半向量 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.  
  3. float amb = 0.3;
  4. float diff = 0.4;
  5. 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.  
  5.  gl_FragColor = vec4(amb*ambient.xyz + diff * diffuse.xyz * diffusefract                  
  6.                 + spec * specular.xyz * specularfract ,1.0);

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

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

效果查看(RenderMonkey):


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

如果打算直接从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

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

Share on facebook

你可能感兴趣的:(GLSL)