当我们渲染一个完全封闭的物体的时候,多边形的背面都是隐藏的,我们无法看见。但是如果这个物体有一些空洞,那么一些背面就可以被看见了。然而因为这些多边形的法向量的方向在这个时候不是正确的,所以会渲染出错误的结果。为了合理的渲染这些背面,我们必须反转法向量,然后基于这些反转的法向量来计算光照。
下图显示了一个采用Phong光照模型绘制茶壶使用双面渲染与否的结果对比:
图一 使用双面渲染与不使用的效果对比
一、固定管线OpenGL实现双面渲染
在固定管线中,我们需要用一个函数来启用双面光照:
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
这样,OpenGL将会反转表面的法线的方向,表示背面多边形的法线。
实例:
void GLWidget::initLight() { GLfloat light_ambient[]={0.7,0.8,0.9,1.0}; GLfloat light_diffuse[]={1.0,1.0,1.0,1.0}; GLfloat light_specular[]={1.0,1.0,1.0,1.0}; GLfloat light_position[]={2.0,2.0,2.0,0.0}; GLfloat mat_diffuse[]={0.8,0.8,0.8,1.0}; glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient); glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse); glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular); glLightfv(GL_LIGHT0,GL_POSITION,light_position); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_AUTO_NORMAL); glEnable(GL_NORMALIZE); //glEnable(GL_CULL_FACE); //启用双面光照 glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE); glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,mat_diffuse); }效果:
二、可编程管线OpenGL实现双面渲染
假设我们的光照(采用Phong光照模型)计算是基于顶点的。在顶点着色器中,我们需要为每个顶点分别计算两次:front face和back face。两次计算采用的法向量不同。
#version 400 layout (location = 0) in vec3 VertexPosition; layout (location = 1) in vec3 VertexNormal; out vec3 FrontColor; out vec3 BackColor; struct LightInfo{ vec4 Position; vec3 La; vec3 Ld; vec3 Ls; }; uniform LightInfo Light; struct MaterialInfo{ vec3 Ka; vec3 Kd; vec3 Ks; float Shininess; }; uniform MaterialInfo Material; uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP; //转化到视空间 void getEyeSpace(out vec3 tnorm,out vec4 eyeCoords) { //convert normal and position to eye coords tnorm = normalize(NormalMatrix * VertexNormal); eyeCoords = ModelViewMatrix * vec4(VertexPosition,1.0); } //计算Phong光照模型 vec3 phongModel(vec4 eyeCoords,vec3 tnorm) { vec3 s = normalize(vec3(Light.Position - eyeCoords)); vec3 v = normalize(-eyeCoords.xyz); vec3 r = reflect(-s,tnorm); vec3 ambient = Light.La * Material.Ka; float sDotN = max(dot(s,tnorm),0.0); vec3 diffuse = Light.Ld * Material.Kd * sDotN; vec3 specular = vec3(0.0); if(sDotN > 0) specular = Light.Ls * Material.Ks * pow(max(dot(r,v),0.0),Material.Shininess); return (ambient + diffuse + specular); } void main() { vec3 tnorm; vec4 eyeCoords; getEyeSpace(tnorm,eyeCoords); FrontColor = phongModel(eyeCoords,tnorm); BackColor = phongModel(eyeCoords,-tnorm); gl_Position = MVP * vec4(VertexPosition,1.0); }在片断着色器中,我们根据关键字gl_FrontFacing的值来为片断赋予颜色值。
gl_FrontFacing是GLSL内置的关键字,它代表图元是面向光源还是背向光源。
片断着色器 twosided.frag:
#version 400 in vec3 FrontColor; in vec3 BackColor; layout (location = 0) out vec4 FragColor; void main() { if(gl_FrontFacing) { FragColor = vec4(FrontColor,1.0); } else { FragColor = vec4(BackColor,1.0); } }