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的“流向”。
但是这里先别理会自发光emission,不理会全局环境光,也不理会光的衰减等等。关注最重心的PHONG模型,有:
pixel color = ambient_color + diffuse_color + specular_color
像素最终颜色由3项直接组成,分别是环境光分量(底色),谩反射分量(由光源和物体的相对位置确定,一般作为主分量),镜面光分量(你可以看作物体外覆盖的一层“膜”,它除受光源和物体的相对位置影响外,还受视线影响)。更多相关内容可看任意一本图形学的书。综上,因此,传入shader的必要物是:物体位置,光源位置,眼睛位置;三种光的颜色;法线。另外还需要一个与镜面光分量相关的shiness参数,决定高光范围。
顶点shader要做的仅仅是把“力所能及”的东西做好。计算正确的光源向量,法线向量和半向量。半向量是“光向量与视线向量的‘半’”,等会再多解释。
- uniform vec3 lightpos;
- uniform vec4 eyepos;
- varying vec3 lightdir;
- varying vec3 halfvec;
- varying vec3 norm;
- void main(void)
- {
- vec4 pos = gl_ModelViewMatrix * gl_Vertex;
- pos = pos / pos.w;
- lightdir = normalize(lightpos - pos.xyz);
- vec3 eyedir = normalize(eyepos.xyz - pos.xyz);
- halfvec = normalize(lightdir + eyedir);
- norm = normalize(gl_NormalMatrix * gl_Normal);
- gl_FrontColor = gl_Color;
- gl_Position = ftransform();
- }
在像素shader里,首先是把从顶点shader插值而来的向量重新单位化。原因是插值过程中这些向量会把“大小”也插值了,而我们必须保证这些向量的单位化,从而保证不破坏光照模型。光源向量也可以不重新插值(如果它是方向光而不是点光源的话——取决于应用)。
- lightdir = normalize(lightdir);
- norm = normalize(norm);
- 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,因此需要给予权值再加~)。
- uniform vec4 ambient, diffuse, specular;
- float amb = 0.3;
- float diff = 0.4;
- float spec = 0.3;
最后就是这样了:
- float diffusefract = max( dot(lightdir,norm) , 0.0);
- float specularfract = max( dot(halfvec,norm) , 0.0);
- specularfract = pow(specularfract, shiness);
- gl_FragColor = vec4(amb*ambient.xyz + diff * diffuse.xyz * diffusefract
- + spec * specular.xyz * specularfract ,1.0);
恩.....考虑到cos(Θ) 、cos(α)小于0的时候说明该物体部分不接受光照,直接取0.0(不产生光照颜色)就行。啊,对了,既然该部分不接受光照,那该部分何必还继续半向量呀取指数呀的计算?浪费。于是,最后的像素shader如下:
- uniform float shiness;
- uniform vec4 ambient, diffuse, specular;
- float amb = 0.3;
- float diff = 0.4;
- float spec = 0.3;
- varying vec3 lightdir;
- varying vec3 halfvec;
- varying vec3 norm;
- void main(void)
- {
- lightdir = normalize(lightdir);
- norm = normalize(norm);
- float diffusefract = max( dot(lightdir,norm) , 0.0);
- float specularfract = 0.0;
- if( diffusefract > 0.0){
- halfvec = normalize(halfvec);
- specularfract = max( dot(halfvec,norm) , 0.0);
- specularfract = pow(specularfract, shiness);
- }
- gl_FragColor = vec4(amb*ambient.xyz
- + diff * diffuse.xyz * diffusefract
- + spec * specular.xyz * specularfract ,1.0);
- }
效果查看(RenderMonkey):
如果打算直接从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
(碎碎念:明明是快速复习的说,明明是快速复习的说.....)