当渲染一个完全封闭的 mesh ,如果里面是隐藏的,则不会被渲染。然而 ,如果这个封闭的mesh有个洞,那么里面就可以有一部分是可见的,则需要被渲染出来。在这中情况下,如果不做处理,可能用shader渲染出的光照效果是不正确的(由于mesh表面法线的方向问题)。为了克服这个问题 需要把mesh的表面法线向量反过来,即-normal,然后计算光照模型。
如图
左边的光照模型采用ADS模式(不正确的光照模型),右边的用ADS光照模型和双边光照模型(Two-sided rendering technique)
顶点shader
#version 430
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;
out vec3 FrontColor;
out vec3 BackColor;
struct LightInfo {
vec4 Position; // Light position in eye coords.
vec3 La; // Ambient light intensity
vec3 Ld; // Diffuse light intensity
vec3 Ls; // Specular light intensity
};
uniform LightInfo Light;
struct MaterialInfo {
vec3 Ka; // Ambient reflectivity
vec3 Kd; // Diffuse reflectivity
vec3 Ks; // Specular reflectivity
float Shininess; // Specular shininess factor
};
uniform MaterialInfo Material;
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 ProjectionMatrix;
uniform mat4 MVP;
vec3 phongModel( vec4 position, vec3 normal ) {
vec3 s = normalize(vec3(Light.Position - position));
vec3 v = normalize(-position.xyz);
vec3 r = reflect( -s, normal );
vec3 ambient = Light.La * Material.Ka;
float sDotN = max( dot(s,normal), 0.0 );
vec3 diffuse = Light.Ld * Material.Kd * sDotN;
vec3 spec = vec3(0.0);
if( sDotN > 0.0 )
spec = Light.Ls * Material.Ks *
pow( max( dot(r,v), 0.0 ), Material.Shininess );
return ambient + diffuse + spec;
}
void main()
{
vec3 tnorm = normalize( NormalMatrix * VertexNormal);
vec4 eyeCoords = ModelViewMatrix * vec4(VertexPosition,1.0);
FrontColor = phongModel( eyeCoords, tnorm );
BackColor = phongModel( eyeCoords, -tnorm );
gl_Position = MVP * vec4(VertexPosition,1.0);
}
片元shader
#version 430
in vec3 FrontColor;
in vec3 BackColor;
layout( location = 0 ) out vec4 FragColor;
void main() {
if( gl_FrontFacing ) {
FragColor = vec4(FrontColor, 1.0);
} else {
// FragColor = mix( vec4(BackColor, 1.0), vec4(1.0,0.0,0.0,1.0), 0.7 );
FragColor = vec4(BackColor, 1.0);
}
}
在vertex shader中,ADS光照模型 计算方法放在了phongModel中,这个函数调用了两次,第一次用想模型本身的normal vertor(transformed into eye coordinates ) ,第二次用负normal vector,对应的结果存在Frontcolor 和 Backcolor 中,然后传到片元shader中。
能够优化的地方:
Note that there are a few aspects of the shading model that are independent of the orientation of the normal vector (such as the ambient component). One could optimize this code by rewriting it so that the redundant calculations are only done once. However, in this recipe we compute the entire shading model twice in the interest of making things clear and readable.
注意:在片元shader中,判断是mesh模型的 front 还是back 是通过内置变量gl_FrontFacing判断的,这个判断是基于多边形的环绕方向,而不是法线方向!
A polygon is said to have counter-clockwise winding(逆时针环绕) if the vertices are specified in counter-clockwise order as viewed from the front side of the polygon
默认情况下,如果屏幕上的顶点顺序是逆时针顺序(counter-clockwise order) ,则说明这是一个front facing polygon,然而我们可以通过glFrontFace 来改变。
需要注意的地方
In the vertex shader we determine the front side of the polygon by the direction of the normal vector, and in the fragment shader, the determination is based on the polygon's winding. For this to work properly, the normal vector must be defined appropriately for the face determined by the setting of glFrontFace.
用two-sided rendering 来调试信息
有时候我们想知道哪些是一个mesh的front facing ,哪个是back facing。我们可以用上面的方法,通过在片元shader中中把mesh的back faces 或者 front faces 设置成一个单一颜色来辨认,例如,对于上面的Frag shader ,我们可以在else 中 改为
FragColor = mix( vec4(BackColor,1.0),vec4(1.0,0.0,0.0,1.0), 0.7 );
如下图: