【OpenGL】GLSL-双面渲染技术

当我们渲染一个完全封闭的物体的时候,多边形的背面都是隐藏的,我们无法看见。但是如果这个物体有一些空洞,那么一些背面就可以被看见了。然而因为这些多边形的法向量的方向在这个时候不是正确的,所以会渲染出错误的结果。为了合理的渲染这些背面,我们必须反转法向量,然后基于这些反转的法向量来计算光照。

下图显示了一个采用Phong光照模型绘制茶壶使用双面渲染与否的结果对比:
01
图一 使用双面渲染与不使用的效果对比

一、固定管线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);
}
效果:
02
02-1

二、可编程管线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);
}
}

--电子创新网--
粤ICP备12070055号