尝试在脑子中想象一个3D立方体,数数你从任意方向最多能同时看到几个面。如果你的想象力不是过于丰富了,你应该能得出最大的面数是3。你可以从任意位置和任意方向看向这个球体,但你永远不能看到3个以上的面。所以我们为什么要浪费时间绘制我们不能看见的那3个面呢?如果我们能够以某种方式丢弃这几个看不见的面,我们能省下超过50%的片段着色器执行数!
我说的是超过50%而不是50%,因为从特定角度来看的话只能看见2个甚至是1个面。在这种情况下,我们就能省下超过50%了。
这是一个很好的主意,但我们仍有一个问题需要解决:我们如何知道一个物体的某一个面不能从观察者视角看到呢?
如果我们想象任何一个闭合形状,它的每一个面都有两侧,每一侧要么面向用户,要么背对用户。如果我们能够只绘制面向观察者的面呢?
这正是面剔除(Face Culling)
所做的。OpenGL能够检查所有面向(Front Facing)观察者
的面,并渲染它们,而丢弃那些背向(Back Facing)的面
,节省我们很多的片段着色器调用(它们的开销很大!)。但我们仍要告诉OpenGL哪些面是正向面(Front Face),哪些面是背向面(Back Face)
。OpenGL使用了一个很聪明的技巧,分析顶点数据的环绕顺序(Winding Order)
。
环绕顺序
当我们定义一组三角形顶点时,我们会以特定的环绕顺序来定义它们,可能是顺时针(Clockwise)
的,也可能是逆时针(Counter-clockwise)
的。每个三角形由3个顶点所组成,我们会从三角形中间来看,为这3个顶点设定一个环绕顺序。
可以看到,我们首先定义了顶点1,之后我们可以选择定义顶点2或者顶点3,这个选择将定义了这个三角形的环绕顺序。下面的代码展示了这点:
float vertices[] = {
// 顺时针
vertices[0], // 顶点1
vertices[1], // 顶点2
vertices[2], // 顶点3
// 逆时针
vertices[0], // 顶点1
vertices[2], // 顶点3
vertices[1] // 顶点2
};
每组组成三角形图元的三个顶点就包含了一个环绕顺序。OpenGL在渲染图元的时候将使用这个信息来决定一个三角形是一个正向三角形
还是背向三角形
。默认情况下,逆时针顶点
所定义的三角形将会被处理为正向三角形
。
当你定义顶点顺序的时候,你应该想象对应的三角形是面向你的,所以你
定义的三角形从正面看去应该是逆时针的
。这样定义顶点很棒的一点是,实际的环绕顺序是在光栅化阶段进行的,也就是顶点着色器运行之后。这些顶点就是从观察者视角所见的了。
观察者所面向的所有三角形顶点就是我们所指定的正确环绕顺序了,而立方体另一面的三角形顶点则是以相反的环绕顺序所渲染的。这样的结果就是,我们所面向的三角形将会是正向三角形,而背面的三角形则是背向三角形。下面这张图显示了这个效果:
在顶点数据中,我们将两个三角形都以逆时针顺序定义(正面的三角形是1、2、3,背面的三角形也是1、2、3(如果我们从正面看这个三角形的话))。然而,如果从观察者当前视角使用1、2、3的顺序来绘制的话,
从观察者的方向来看,背面的三角形将会是以顺时针顺序渲染的
。虽然背面的三角形是以逆时针定义的,它现在是以顺时针顺序渲染的了
。这正是我们想要剔除(Cull,丢弃)的不可见面了!
在顶点数据中,我们定义的是两个逆时针顺序的三角形。然而,从观察者的方面看,后面的三角形是顺时针的,如果我们仍以1、2、3的顺序以观察者当面的视野看的话。即使我们以逆时针顺序定义后面的三角形,它现在还是变为顺时针。它正是我们打算剔除(丢弃)的不可见的面!
面剔除
在本节的开头我们就说过,OpenGL能够丢弃那些渲染为背向三角形的三角形图元。既然已经知道如何设置顶点的环绕顺序了,我们就可以使用OpenGL的面剔除选项了,它默认是禁用状态的
。
要想启用面剔除,我们只需要启用OpenGL的GL_CULL_FACE
选项:
glEnable(GL_CULL_FACE);
从这一句代码之后,所有背向面都将被丢弃(尝试飞进立方体内部,看看所有的内面是不是都被丢弃了)。目前我们在渲染片段的时候能够节省50%以上的性能,但注意这只对像立方体这样的封闭形状有效。当我们想要绘制上篇博客中的草时,我们必须要再次禁用面剔除,因为它们的正向面和背向面都应该是可见的。
就是面剔除只对立方体有效.平面无效
OpenGL允许我们改变需要剔除的面的类型。如果我们只想剔除正向面而不是背向面会怎么样?我们可以调用glCullFace
来定义这一行为:
glCullFace(GL_FRONT);
glCullFace函数有三个可用的选项:
- GL_BACK:只剔除背向面。
- GL_FRONT:只剔除正向面。
- GL_FRONT_AND_BACK:剔除正向面和背向面。
glCullFace
的初始值是GL_BACK
。除了需要剔除的面之外,我们也可以通过调用glFrontFace,告诉OpenGL我们希望将顺时针的面(而不是逆时针的面)定义为正向面:
glFrontFace(GL_CCW);
默认值是 GL_CCW
,它代表的是逆时针的环绕顺序,另一个选项是 GL_CW
,它(显然)代表的是顺时针顺序。
demo演示
我们绘制一个立方体,立方体沿着x,y轴旋转30度
基本代码
#import "FaceCullingDefaultViewController.h"
#import "FaceCullingDefaultBindObject.h"
#import "CubeManager.h"
float FCD_planeVertices[] = {
// positions // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat)
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};
@interface FaceCullingDefaultViewController ()
@property (nonatomic ,strong) Vertex * vertex;
@property (nonatomic ,strong) Vertex * planeVertex ;
@property (nonatomic ,strong) TextureUnit * cubeUnit ;
@property (nonatomic ,strong) TextureUnit * floorUnit ;
@end
@implementation FaceCullingDefaultViewController
-(void)initSubObject{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
self.bindObject = [FaceCullingDefaultBindObject new];
}
-(void)loadVertex{
self.vertex= [Vertex new];
int vertexNum =[CubeManager getTextureNormalVertexNum];
[self.vertex allocVertexNum:vertexNum andEachVertexNum:5];
for (int i=0; i<vertexNum; i++) {
float onevertex[5];
for (int j=0; j<3; j++) {
onevertex[j]=[CubeManager getTextureNormalVertexs][i*8+j];
}
for (int j=0; j<2; j++) {
onevertex[j+3]=[CubeManager getTextureNormalVertexs][i*8+6+j];
}
[self.vertex setVertex:onevertex index:i];
}
[self.vertex bindBufferWithUsage:GL_STATIC_DRAW];
self.planeVertex= [Vertex new];
vertexNum =6;
[self.planeVertex allocVertexNum:vertexNum andEachVertexNum:5];
for (int i=0; i<vertexNum; i++) {
float onevertex[5];
for (int j=0; j<3; j++) {
onevertex[j]=FCD_planeVertices[i*5+j];
}
for (int j=0; j<2; j++) {
onevertex[j+3]=FCD_planeVertices[i*5+3+j];
}
[self.planeVertex setVertex:onevertex index:i];
}
[self.planeVertex bindBufferWithUsage:GL_STATIC_DRAW];
}
//-(void)createShader{
// [super createShader];
// self.stencilShader = [Shader new];
// [self.stencilShader compileLinkSuccessShaderName:@"BlendColor" completeBlock:^(GLuint program) {
// [self.bindObjectColor BindAttribLocation:program];
// }];
// [self.bindObjectColor setUniformLocation:self.stencilShader.program];
//}
-(void)createTextureUnit{
self.cubeUnit = [TextureUnit new];
[self.cubeUnit setImage:[UIImage imageNamed:@"marble.jpg"] IntoTextureUnit:GL_TEXTURE0 andConfigTextureUnit:nil];
self.floorUnit = [TextureUnit new];
[self.floorUnit setImage:[UIImage imageNamed:@"metal.png"] IntoTextureUnit:GL_TEXTURE1 andConfigTextureUnit:^{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}];
}
-(void)_bindViewMatrix4:(GLBaseBindObject*)bindObject{
GLKMatrix4 viewMatrix =
GLKMatrix4MakeLookAt(
0.0, 0.0, 3.0, // Eye position
0.0, 0.0, 0.0, // Look-at position
0.0, 1.0, 0.0); // Up direction
glUniformMatrix4fv(bindObject->uniforms[FCD_uniform_view], 1, 0,viewMatrix.m);
}
-(void)_bindProjectionMatrix4:(GLBaseBindObject*)bindObject{
GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
GLKMatrix4 projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(100.0f),
aspectRatio,
0.1f,
100.0f);
glUniformMatrix4fv(bindObject->uniforms[FCD_uniform_projection], 1, 0,projectionMatrix.m);
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
/// 模板测试虽然开启了. 但是不起作用.
glClearStencil(0);
glClearColor(1,0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLKMatrix4 cubeMode1 =[self _getModeMatrix4Location:GLKVector3Make(0.0f, 0.0f, 1.0f)];
cubeMode1 = GLKMatrix4Rotate(cubeMode1, 30*M_PI/180.0, 1, 1, 0);
[self.shader use];
[self _bindViewMatrix4:self.bindObject];
[self _bindProjectionMatrix4:self.bindObject];
[self _drawFloor:self.bindObject];
[self _drawCubeMode:cubeMode1 bindObject:self.bindObject];
}
-(GLKMatrix4)_getModeMatrix4Location:(GLKVector3)location{
GLKMatrix4 mode = GLKMatrix4Identity;
mode = GLKMatrix4TranslateWithVector3(mode, location);
// mode = GLKMatrix4Rotate(mode, 30*M_PI/180.0, 0, 1, 0);
return mode;
}
-(void)_drawCubeMode:(GLKMatrix4)mode bindObject:(GLBaseBindObject*)bindObject{
glUniformMatrix4fv(bindObject->uniforms[FCD_uniform_model], 1, 0,mode.m);
[self.cubeUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:bindObject->uniforms[FCD_uniform_Texture]];
[self.vertex enableVertexInVertexAttrib:FCD_aPos numberOfCoordinates:3 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:FCD_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
[self.vertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:[CubeManager getTextureNormalVertexNum]];
}
-(void)_drawFloor:(GLBaseBindObject*)bindObject{
GLKMatrix4 mode = GLKMatrix4Identity;
glUniformMatrix4fv(bindObject->uniforms[FCD_uniform_model], 1, 0,mode.m);
[self.floorUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:bindObject->uniforms[FCD_uniform_Texture]];
[self.planeVertex enableVertexInVertexAttrib:FCD_aPos numberOfCoordinates:3 attribOffset:0];
[self.planeVertex enableVertexInVertexAttrib:FCD_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
[self.planeVertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:6];
}
@end
shader
precision mediump float;
attribute vec3 aPos;
//attribute vec3 aNormal;
attribute vec2 aTexCoords;
varying vec2 vTexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
vTexCoords = aTexCoords;
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;
void main()
{
vec4 textureColor = texture2D(uTexture, vTexCoords);
gl_FragColor = textureColor;
}
#import "GLBaseBindObject.h"
NS_ASSUME_NONNULL_BEGIN
// Attribute identifiers
typedef enum {
FCD_aPos,
// aNormal,
FCD_aTexCoords,
} FCD_BaseBindAttribLocation;
typedef union {
struct{
GLKVector3 aPos;
// GLKVector3 aNormal;
GLKVector2 aTexCoords;
};
float a[5];
}FCD_BindAtt;
typedef enum {
FCD_uniform_model,
FCD_uniform_view,
FCD_uniform_projection,
FCD_uniform_Texture
} FCD_UniformLocation;
@interface FaceCullingDefaultBindObject : GLBaseBindObject
@end
NS_ASSUME_NONNULL_END
#import "FaceCullingDefaultBindObject.h"
@implementation FaceCullingDefaultBindObject
-(void)BindAttribLocation:(GLuint)program{
glBindAttribLocation(program, FCD_aPos, "aPos");
glBindAttribLocation(program, FCD_aTexCoords, "aTexCoords");
}
-(void)setUniformLocation:(GLuint)program{
self->uniforms[FCD_uniform_model] = glGetUniformLocation(program, "model");
self->uniforms[FCD_uniform_view] = glGetUniformLocation(program, "view");
self->uniforms[FCD_uniform_projection] = glGetUniformLocation(program, "projection");
self->uniforms[FCD_uniform_Texture] = glGetUniformLocation(program, "uTexture");
}
-(NSString *)getShaderName{
return @"FaceCullingDefault";
}
@end
正常渲染立方体的顶点
float textureNormalvertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
选入的立方体结果是
没有顶点顺序的立方体开启剔除面
#import "FaceCullingOutOrderViewController.h"
@interface FaceCullingOutOrderViewController ()
@end
@implementation FaceCullingOutOrderViewController
-(void)initSubObject{
[super initSubObject];
//开启剔除面
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
}
@end
#import "GLBaseViewController.h"
#import "FaceCullingDefaultViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface FaceCullingOutOrderViewController : FaceCullingDefaultViewController
@end
NS_ASSUME_NONNULL_END
我们发现,要是立方体的顶点没有按照一定顺序排列,那么剔除面的结果是不好预测的.
有顺序的剔除面
顶点是逆时针排列
顶点坐标如下
unsigned int cubeFaceCullingVertuceNum = 36;
float cubeFaceCullingVertices[] = {
// Back face
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // Bottom-left
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // bottom-left
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
// Front face
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // top-right
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, // top-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Left face
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-right
// Right face
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // bottom-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // top-left
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-left
// Bottom face
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
0.5f, -0.5f, -0.5f, 1.0f, 1.0f, // top-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // bottom-left
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // bottom-right
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // top-right
// Top face
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // top-right
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // bottom-right
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // top-left
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f // bottom-left
};
开启剔除面结果
-(void)initSubObject{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
self.bindObject = [FaceCullingOrderFrontBindObject new];
}
剔除前面的面
-(void)initSubObject{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
self.bindObject = [FaceCullingOrderFrontBindObject new];
glCullFace(GL_FRONT);
}
剔除后面的面
-(void)initSubObject{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
self.bindObject = [FaceCullingOrderFrontBindObject new];
glCullFace(GL_BACK);
}
默认是开启剔除后面的面的
剔除面顺序
我们计算剔除面默认是逆时针的,改变成顺时针的结果是啥样子的呢?
-(void)initSubObject{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
self.bindObject = [FaceCullingOrderFrontBindObject new];
glCullFace(GL_BACK);
glFrontFace(GL_CW);
}
顺序改变,剔除的面正好相反.