本章可以学到如何使用深度测试,以及参数选择的效果
在绘制3D 图像的时候,我们需要开启深度测试, 来防止被阻挡的面渲染到其它面的前面
. 本章将深入地讨论这些储存在深度缓冲(或z缓冲(z-buffer))中的深度值(Depth Value),以及它们是如何确定一个片段是处于其它片段后方的。
深度缓冲就像颜色缓冲(Color Buffer)(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度。深度缓冲是由系统自动创建的
,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。
当深度测试(Depth Testing)被启用的时候,OpenGL会将一个片段的的深度值与深度缓冲的内容进行对比。OpenGL会执行一个深度测试,如果这个测试通过了的话,深度缓冲将会更新为新的深度值。如果深度测试失败了,片段将会被丢弃。
深度缓冲是在片段着色器运行之后以及模板测试(Stencil Testing)运行之后在屏幕空间中运行的。
屏幕空间坐标与通过OpenGL的glViewport所定义的视口密切相关,并且可以直接使用GLSL内建变量gl_FragCoord从片段着色器中直接访问。gl_FragCoord的x和y分量代表了片段的屏幕空间坐标(其中(0, 0)位于左下角)。gl_FragCoord中也包含了一个z分量,它包含了片段真正的深度值。z值就是需要与深度缓冲内容所对比的那个值。
现在大部分的GPU都提供一个叫做提前深度测试(Early Depth Testing)的硬件特性。提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。
片段着色器通常开销都是很大的,所以我们应该尽可能避免运行它们
。当使用提前深度测试时,片段着色器的一个限制是你不能写入片段的深度值。如果一个片段着色器对它的深度值进行了写入,提前深度测试是不可能的。OpenGL不能提前知道深度值。
深度测试默认是禁用的,所以如果要启用深度测试的话,我们需要用GL_DEPTH_TEST
选项来启用它:
glEnable(GL_DEPTH_TEST);
当它启用的时候,如果一个片段通过了深度测试的话,OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。如果你启用了深度缓冲,你还应该在每个渲染迭代之前使用GL_DEPTH_BUFFER_BIT来清除深度缓冲,否则你会仍在使用上一次渲染迭代中的写入的深度值:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
可以想象,在某些情况下你会需要对所有片段都执行深度测试并丢弃相应的片段,但不希望更新深度缓冲。基本上来说,你在使用一个只读的(Read-only)深度缓冲。OpenGL允许我们禁用深度缓冲的写入,只需要设置它的深度掩码(Depth Mask)设置为GL_FALSE就可以了:
glDepthMask(GL_FALSE);
注意这只在深度测试被启用的时候才有效果。
开启深度测试和关闭深度测试的效果
我们绘制一个正方体来测试开启深度测试和关闭深度测试的效果
基础类代码
正方体坐标
float cubeVerts[]={
0.5, 0.5, 0.5,
-0.5, 0.5, 0.5,
-0.5,-0.5, 0.5,
0.5,-0.5, 0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5,-0.5,-0.5,
0.5,-0.5,-0.5,
};
float cubeColors[]={
1.0,0.0,0.0,
1.0,0.0,0.0,
1.0,0.0,0.0,
1.0,0.0,0.0,
0.0,1.0,0.0,
0.0,1.0,0.0,
0.0,1.0,0.0,
0.0,1.0,0.0,
};
#import "DepthTestIsOpenViewController.h"
#import "DepthTestIsOpenBindObject.h"
#import "CubeManager.h"
@interface DepthTestIsOpenViewController ()
@property (nonatomic ,strong) Vertex * vertex;
@property (nonatomic ,strong) VertexElement * vertexElement ;
@end
@implementation DepthTestIsOpenViewController
-(void)initSubObject{
self.bindObject = [DepthTestIsOpenBindObject new];
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT);
};
}
-(void)loadVertex{
self.vertex= [Vertex new];
int vertexNum =[CubeManager getVertexNum];
[self.vertex allocVertexNum:vertexNum andEachVertexNum:6];
for (int i=0; i<vertexNum; i++) {
float onevertex[6];
for (int j=0; j<3; j++) {
onevertex[j]=[CubeManager getCubeVerts][i*3+j];
}
for (int j=0; j<3; j++) {
onevertex[j+3]=[CubeManager cubeColors][i*3+j];
}
[self.vertex setVertex:onevertex index:i];
}
[self.vertex bindBufferWithUsage:GL_STATIC_DRAW];
[self.vertex enableVertexInVertexAttrib:DTIO_aPos numberOfCoordinates:3 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTIO_aColor numberOfCoordinates:3 attribOffset:sizeof(float)*3];
self.vertexElement = [VertexElement new];
[self.vertexElement allocWithIndexNum:[CubeManager getVertexElementsNum] indexArr:[CubeManager getVertexElements]];
}
-(void)createTextureUnit{
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(1.0,1.0,1.0, 1);
self.glDrawConfig();
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(self.bindObject->uniforms[DTIO_uniform_view], 1, 0,viewMatrix.m);
GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
GLKMatrix4 projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(85.0f),
aspectRatio,
0.1f,
100.0f);
glUniformMatrix4fv(self.bindObject->uniforms[DTIO_uniform_projection], 1, 0,projectionMatrix.m);
GLKMatrix4 mode = GLKMatrix4Identity;
static GLfloat angle=30;
angle +=3;
mode = GLKMatrix4Rotate(mode,angle*M_PI/180, 0, 1, 0);
glUniformMatrix4fv(self.bindObject->uniforms[DTIO_uniform_model], 1, 0,mode.m);
[self.vertexElement drawElementIndexWithMode:GL_TRIANGLES];
}
@end
#import "GLBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface DepthTestIsOpenViewController : GLBaseViewController
@property (nonatomic ,strong) void(^glDrawConfig)(void) ;
@end
NS_ASSUME_NONNULL_END
shader
attribute vec3 aPos;
attribute vec3 aColor;
varying vec3 vColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main (){
vColor = aColor;
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
precision mediump float;//mediump
varying vec3 vColor;
void main(){
lowp vec4 rgba = vec4(vColor,1);
gl_FragColor = rgba;
}
#import "GLBaseViewController.h"
@interface GLBaseViewController ()
@property (nonatomic ,strong) TextureUnit * textureUnit0 ;
@end
@implementation GLBaseViewController
-(void)createEagContext{
self.eagcontext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.eagcontext];
}
-(void)configure{
GLKView *view = (GLKView*)self.view;
view.context = self.eagcontext;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
}
-(void)initCustom{
}
-(void)initSubObject{
}
-(void)createTextureUnit{
}
-(void)createShader{
self.shader = [Shader new];
[self.shader compileLinkSuccessShaderName:self.bindObject.getShaderName completeBlock:^(GLuint program) {
[self.bindObject BindAttribLocation:program];
}];
[self.bindObject setUniformLocation:self.shader.program];
}
-(void)loadVertex{
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
}
-(void)viewDidLoad{
[super viewDidLoad];
[self createEagContext];
[self initSubObject];
[self configure];
[self initCustom];
[self createShader];
[self createTextureUnit];
[self loadVertex];
}
@end
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#import "OpenGLUtilsHeader.h"
#import "GLBaseBindObject.h"
NS_ASSUME_NONNULL_BEGIN
@interface GLBaseViewController : GLKViewController
@property (nonatomic ,strong) EAGLContext * eagcontext;
@property (nonatomic ,assign) GLuint program;
@property (nonatomic ,strong) Shader * shader ;
@property (nonatomic ,strong) GLBaseBindObject * bindObject ;
///眼的位置在 0,0,1 看向 原点 ,眼的正方向是y轴, 看的区域是0.1 到20 角度是85
-(GLKMatrix4 )mvp;
-(void)loadVertex;
-(void)initSubObject;
-(void)createTextureUnit;
@end
我这里只贴了主要的代码. 代码不全,可以下载demo来看.
未开启深度测试效果
我们发现 正方体是部分镂空.
开启深度测试效果
可以正常展示一个正方体了
#import "DepthTestOpenViewController.h"
@interface DepthTestOpenViewController ()
@end
@implementation DepthTestOpenViewController
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
}
@end
glDepthMask的使用
该函数起作用必须是我们要开启深度测试才行
测试代码做的事情是我们绘制三张图片,三张图片按照 绿色图片 ,箱子,红色图片的顺序绘制. 我们看glDepthMask对三张图片的影响
核心测试代码
#import "DepthTestMaskViewController.h"
#import "DepthTestMaskBindObject.h"
@interface DepthTestMaskViewController ()
@property (nonatomic ,strong) Vertex * vertex;
@property (nonatomic ,strong) TextureUnit * unitFront ;
@property (nonatomic ,strong) TextureUnit * unitMiddle;
@property (nonatomic ,strong) TextureUnit * unitBack;
@end
@implementation DepthTestMaskViewController
-(void)initSubObject{
//生命周期三秒钟
self.bindObject = [DepthTestMaskBindObject new];
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT);
};
self.glDrawMaskEndConfig = ^{
};
self.glDrawMaskBeginConfig = ^{
};
}
#define VertexNum 4
#define eachVertexNum 4
static GLfloat s_vertex[4*4] = {
0.5,0.5, 0,1, //1
-0.5,0.5,1,1,//0
-0.5,-0.5,1,0, //2
0.5,-0.5,0,0//3
};
-(void)loadVertex{
self.vertex = [Vertex new];
[self.vertex allocVertexNum:VertexNum andEachVertexNum:eachVertexNum];
for (int i =0; i<VertexNum; i++) {
GLfloat vertex[4];
vertex[0] = s_vertex[i*VertexNum];
vertex[1] = s_vertex[i*VertexNum+1];
vertex[2] = s_vertex[i*VertexNum+2];
vertex[3] = s_vertex[i*VertexNum+3];
[self.vertex setVertex:vertex index:i];
}
[self.vertex bindBufferWithUsage:GL_STATIC_DRAW];
[self.vertex enableVertexInVertexAttrib:DTM_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTM_aTexture numberOfCoordinates:2 attribOffset:2*sizeof(GLfloat)];
}
-(void)createTextureUnit{
self.unitFront = [TextureUnit new];
[self.unitFront setImage:[UIImage imageNamed:@"green.png"] IntoTextureUnit:GL_TEXTURE0 andConfigTextureUnit:nil];
self.unitBack = [TextureUnit new];
[self.unitBack setImage:[UIImage imageNamed:@"red.png"] IntoTextureUnit:GL_TEXTURE1 andConfigTextureUnit:nil];
self.unitMiddle = [TextureUnit new];
[self.unitMiddle setImage:[UIImage imageNamed:@"texture.jpg"] IntoTextureUnit:GL_TEXTURE2 andConfigTextureUnit:nil];
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(1,1,1, 1);
self.glDrawConfig();
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(self.bindObject->uniforms[DTM_uniform_view], 1, 0,viewMatrix.m);
GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
GLKMatrix4 projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(85.0f),
aspectRatio,
0.1f,
10.0);
glUniformMatrix4fv(self.bindObject->uniforms[DTM_uniform_projection], 1, 0,projectionMatrix.m);
//front
{
GLKMatrix4 mode = GLKMatrix4Identity;
glUniformMatrix4fv(self.bindObject->uniforms[DTM_uniform_model], 1, 0,mode.m);
[self.unitFront bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DTM_uniform_sam2D]];
[self.vertex enableVertexInVertexAttrib:DTM_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTM_aTexture numberOfCoordinates:2 attribOffset:sizeof(float)*2];
[self.vertex drawVertexWithMode:GL_TRIANGLE_FAN startVertexIndex:0 numberOfVertices:VertexNum];
}
// middle
self.glDrawMaskBeginConfig();
{
GLKMatrix4 mode = GLKMatrix4Identity;
mode = GLKMatrix4Translate(mode, 0.25, 0, 0);
glUniformMatrix4fv(self.bindObject->uniforms[DTM_uniform_model], 1, 0,mode.m);
[self.unitMiddle bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DTM_uniform_sam2D]];
[self.vertex enableVertexInVertexAttrib:DTM_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTM_aTexture numberOfCoordinates:2 attribOffset:sizeof(float)*2];
[self.vertex drawVertexWithMode:GL_TRIANGLE_FAN startVertexIndex:0 numberOfVertices:VertexNum];
}
//back
{
GLKMatrix4 mode = GLKMatrix4Identity;
mode = GLKMatrix4Translate(mode, 0.5, 0, 0);
glUniformMatrix4fv(self.bindObject->uniforms[DTM_uniform_model], 1, 0,mode.m);
[self.unitBack bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DTM_uniform_sam2D]];
[self.vertex enableVertexInVertexAttrib:DTM_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTM_aTexture numberOfCoordinates:2 attribOffset:sizeof(float)*2];
[self.vertex drawVertexWithMode:GL_TRIANGLE_FAN startVertexIndex:0 numberOfVertices:VertexNum];
}
self.glDrawMaskEndConfig();
}
@end
这里我们需要说下,深度值其实就是z值.深度测试就是测试z值的大小.
不开启深度测试的测试效果
我们发现 图片的图层顺序是 绿色最下,箱子中间 ,红色图片在上.
开启深度测试测测试效果
我们发现图片的顺序正好和未开启深度测试的顺序是相反的.绿色在上,箱子中间 ,红色在下.
这是因为,当开始测试的时候,绘制的时候就需要进行深度测试的比较了.
我们先绘制绿色图片,绿色图片所在屏幕上的像素点的深度值就大于0.
当我们绘制箱子的时候,箱子和绿色重叠的部分深度值等于绿色图片不绘制,而只绘制和绿色图片不重叠的部分
红色的图片同样的道理绘制
这里需要注意,我们绘制绿色图片的时候,坐标z轴是0,但是深度值不是0
没有绘制的颜色的区域的深度值是0
深度值越小越绘制
图例如下
开始深度测试并且使用遮罩效果
代码如下
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthMask(GL_FALSE);
};
self.glDrawMaskEndConfig = ^{
glDepthMask(GL_TRUE);
};
}
代码具体做的事情如下
- 1.我们开启 glEnable(GL_DEPTH_TEST); 和 glDepthMask(GL_TRUE);
- 2.绘制了绿色图片
- 3.关闭 glDepthMask(GL_FALSE);
- 4.绘制箱子和红色图片 最终的结果如上
具体行为分析如下
当开启测试的时候并且遮罩(mask)开启的时候,绘制的时候就需要进行深度测试的比较了.
我们先绘制绿色图片,绿色图片所在屏幕上的像素点的深度值就大于0.
关闭mask,绘制箱子的时候,箱子对应的深度值是0,那么,箱子和绿色重叠的部分进行深度值比较,明显小于深小于绿色图片的深度值,不绘制,而只绘制和绿色图片不重叠的部分,该部分深度值是默认值.
接着绘制红色图片,红色图片和箱子重叠部分,由于该部分的深度值是默认值,因此,红色覆盖该像素点了.
图例
深度测试函数
OpenGL允许我们修改深度测试中使用的比较运算符。这允许我们来控制OpenGL什么时候该通过或丢弃一个片段,什么时候去更新深度缓冲。我们可以调用glDepthFunc函数来设置比较运算符(或者说深度函数(Depth Function)):
glDepthFunc(GL_LESS);
这个函数接受下面表格中的比较运算符:
函数 | 描述 |
---|---|
GL_ALWAYS | 永远通过深度测试 |
GL_NEVER | 永远不通过深度测试 |
GL_LESS | 在片段深度值小于缓冲的深度值时通过测试 |
GL_EQUAL | 在片段深度值等于缓冲区的深度值时通过测试 |
GL_LEQUAL | 在片段深度值小于等于缓冲区的深度值时通过测试 |
GL_GREATER | 在片段深度值大于缓冲区的深度值时通过测试 |
GL_NOTEQUAL | 在片段深度值不等于缓冲区的深度值时通过测试 |
GL_GEQUAL | 在片段深度值大于等于缓冲区的深度值时通过测试 |
默认情况下使用的深度函数是GL_LESS,它将会丢弃深度值大于等于当前深度缓冲值的所有片段。
测试基本代码
#import "DepthTestFuncViewController.h"
#import "DepthTestFuncBindObject.h"
@interface DepthTestFuncViewController ()
@property (nonatomic ,strong) Vertex * vertex;
@property (nonatomic ,strong) TextureUnit * unitFront ;
@property (nonatomic ,strong) TextureUnit * unitMiddle;
@property (nonatomic ,strong) TextureUnit * unitBack;
@end
@implementation DepthTestFuncViewController
-(void)initSubObject{
//生命周期三秒钟
self.bindObject = [DepthTestFuncBindObject new];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskEndConfig = ^{
};
self.glDrawMaskBeginConfig = ^{
};
}
#define DTF_VertexNum 4
#define DTF_eachVertexNum 4
static GLfloat dtf_s_vertex[4*4] = {
0.5,0.5, 0,1, //1
-0.5,0.5,1,1,//0
-0.5,-0.5,1,0, //2
0.5,-0.5,0,0//3
};
-(void)loadVertex{
self.vertex = [Vertex new];
[self.vertex allocVertexNum:DTF_VertexNum andEachVertexNum:DTF_eachVertexNum];
for (int i =0; i<DTF_VertexNum; i++) {
GLfloat vertex[4];
vertex[0] = dtf_s_vertex[i*DTF_VertexNum];
vertex[1] = dtf_s_vertex[i*DTF_VertexNum+1];
vertex[2] = dtf_s_vertex[i*DTF_VertexNum+2];
vertex[3] = dtf_s_vertex[i*DTF_VertexNum+3];
[self.vertex setVertex:vertex index:i];
}
[self.vertex bindBufferWithUsage:GL_STATIC_DRAW];
[self.vertex enableVertexInVertexAttrib:DTF_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTF_aTexture numberOfCoordinates:2 attribOffset:2*sizeof(GLfloat)];
}
-(void)createTextureUnit{
self.unitFront = [TextureUnit new];
[self.unitFront setImage:[UIImage imageNamed:@"green.png"] IntoTextureUnit:GL_TEXTURE0 andConfigTextureUnit:nil];
self.unitBack = [TextureUnit new];
[self.unitBack setImage:[UIImage imageNamed:@"red.png"] IntoTextureUnit:GL_TEXTURE1 andConfigTextureUnit:nil];
self.unitMiddle = [TextureUnit new];
[self.unitMiddle setImage:[UIImage imageNamed:@"texture.jpg"] IntoTextureUnit:GL_TEXTURE2 andConfigTextureUnit:nil];
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(1,1,1, 1);
self.glDrawConfig();
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(self.bindObject->uniforms[DTF_uniform_view], 1, 0,viewMatrix.m);
GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
GLKMatrix4 projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(85.0f),
aspectRatio,
0.1f,
10.0);
glUniformMatrix4fv(self.bindObject->uniforms[DTF_uniform_projection], 1, 0,projectionMatrix.m);
self.glDrawMaskBeginConfig();
//front
{
GLKMatrix4 mode = GLKMatrix4Identity;
glUniformMatrix4fv(self.bindObject->uniforms[DTF_uniform_model], 1, 0,mode.m);
[self.unitFront bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DTF_uniform_sam2D]];
[self.vertex enableVertexInVertexAttrib:DTF_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTF_aTexture numberOfCoordinates:2 attribOffset:sizeof(float)*2];
[self.vertex drawVertexWithMode:GL_TRIANGLE_FAN startVertexIndex:0 numberOfVertices:DTF_eachVertexNum];
}
// middle
{
GLKMatrix4 mode = GLKMatrix4Identity;
mode = GLKMatrix4Translate(mode, 0.25, 0, 0);
glUniformMatrix4fv(self.bindObject->uniforms[DTF_uniform_model], 1, 0,mode.m);
[self.unitMiddle bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DTF_uniform_sam2D]];
[self.vertex enableVertexInVertexAttrib:DTF_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTF_aTexture numberOfCoordinates:2 attribOffset:sizeof(float)*2];
[self.vertex drawVertexWithMode:GL_TRIANGLE_FAN startVertexIndex:0 numberOfVertices:DTF_eachVertexNum];
}
self.glDrawMaskEndConfig();
//back
{
GLKMatrix4 mode = GLKMatrix4Identity;
mode = GLKMatrix4Translate(mode, 0.5, 0, 0);
glUniformMatrix4fv(self.bindObject->uniforms[DTF_uniform_model], 1, 0,mode.m);
[self.unitBack bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DTF_uniform_sam2D]];
[self.vertex enableVertexInVertexAttrib:DTF_aPos numberOfCoordinates:2 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DTF_aTexture numberOfCoordinates:2 attribOffset:sizeof(float)*2];
[self.vertex drawVertexWithMode:GL_TRIANGLE_FAN startVertexIndex:0 numberOfVertices:DTF_eachVertexNum];
}
}
@end
该测试代码和测试mask代码比较,只是更改了glDrawMaskBeginConfig
和glDrawMaskEndConfig
的测试位置
GL_ALWAYS
永远通过深度测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
};
}
测试结果
GL_NEVER
永远不通过深度测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
glDepthFunc(GL_NEVER);
};
}
测试结果
GL_LESS
在片段深度值小于缓冲的深度值时通过测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
glDepthFunc(GL_LESS);
};
}
GL_EQUAL
在片段深度值等于缓冲区的深度值时通过测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
glDepthFunc(GL_EQUAL);
};
}
GL_LEQUAL
在片段深度值小于等于缓冲区的深度值时通过测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
glDepthFunc(GL_LEQUAL);
};
}
GL_GREATER
在片段深度值大于缓冲区的深度值时通过测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
glDepthFunc(GL_GREATER);
};
}
GL_NOTEQUAL
在片段深度值不等于缓冲区的深度值时通过测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
glDepthFunc(GL_NOTEQUAL);
};
}
GL_GEQUAL
在片段深度值大于等于缓冲区的深度值时通过测试
-(void)initSubObject{
//生命周期三秒钟
[super initSubObject];
glEnable(GL_DEPTH_TEST);
self.glDrawConfig = ^{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};
self.glDrawMaskBeginConfig = ^{
glDepthFunc(GL_ALWAYS);
};
self.glDrawMaskEndConfig = ^{
glDepthFunc(GL_GEQUAL);
};
}
深度值精度
深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影平截头体的近平面(Near)和远平面(Far)之间的任何值。我们需要一种方式来将这些观察空间的z值变换到[0, 1]范围之间,其中的一种方式就是将它们线性变换到[0, 1]范围之间。下面这个(线性)方程将z值变换到了0.0到1.0之间的深度值:
深度值与z轴成正相关
这里的near和far值是我们之前提供给投影矩阵设置可视平截头体的那个 near 和 far 值
这个方程需要平截头体中的一个z值,并将它变换到了[0, 1]的范围中。z值和对应的深度值之间的关系可以在下图中看到:
然而,在实践中是几乎永远不会使用这样的线性深度缓冲(Linear Depth Buffer)的。要想有正确的投影性质,需要使用一个非线性的深度方程,它是与 1/z 成正比的。它做的就是在z值很小的时候提供非常高的精度,而在z值很远的时候提供更少的精度。
花时间想想这个:我们真的需要对1000单位远的深度值和只有1单位远的充满细节的物体使用相同的精度吗?线性方程并不会考虑这一点。
由于非线性方程与 1/z 成正比,在1.0和2.0之间的z值将会变换至1.0到0.5之间的深度值,这就是一个float提供给我们的一半精度了,这在z值很小的情况下提供了非常大的精度。在50.0和100.0之间的z值将会只占2%的float精度,这正是我们所需要的。这样的一个考虑了远近距离的方程是这样的:
其实就是线性关系的变种.更接近现实
如果你不知道这个方程是怎么回事也不用担心。重要的是要记住深度缓冲中的值在屏幕空间中不是线性的(在透视矩阵应用之前在观察空间中是线性的)。深度缓冲中0.5的值并不代表着物体的z值是位于平截头体的中间了,这个顶点的z值实际上非常接近近平面!你可以在下图中看到z值和最终的深度缓冲值之间的非线性关系:
可以看到,深度值很大一部分是由很小的z值所决定的,这给了近处的物体很大的深度精度。这个(从观察者的视角)变换z值的方程是嵌入在投影矩阵中的,所以当我们想将一个顶点坐标从观察空间至裁剪空间的时候这个非线性方程就被应用了。
深度缓冲的可视化
我们知道片段着色器中,内建gl_FragCoord向量的z值包含了那个特定片段的深度值。如果我们将这个深度值输出为颜色,我们可以显示场景中所有片段的深度值。我们可以根据片段的深度值返回一个颜色向量来完成这一工作:
precision mediump float;
varying vec2 vTexCoords;
uniform sampler2D uTexture;
void main()
{
vec4 textureColor = texture2D(uTexture, vTexCoords);
gl_FragColor = vec4(vec3(gl_FragCoord.z), 1.0);;
}
代码含义:
+ 生成了一个地面和两个正方体.
测试结果
这很清楚地展示了深度值的非线性性质。近处的物体比起远处的物体对深度值有着更大的影响。只需要移动几厘米就能让颜色从暗完全变白。
如何变成线性关系呢?
深度冲突
一个很常见的视觉错误会在两个平面或者三角形非常紧密地平行排列在一起时会发生,深度缓冲没有足够的精度来决定两个形状哪个在前面。结果就是这两个形状不断地在切换前后顺序,这会导致很奇怪的花纹。这个现象叫做深度冲突(Z-fighting),因为它看起来像是这两个形状在争夺(Fight)谁该处于顶端。
在我们一直使用的场景中,有几个地方的深度冲突还是非常明显的。箱子被放置在地板的同一高度上,这也就意味着箱子的底面和地板是共面的(Coplanar)。这两个面的深度值都是一样的,所以深度测试没有办法决定应该显示哪一个。
深度冲突是深度缓冲的一个常见问题,当物体在远处时效果会更明显(因为深度缓冲在z值比较大的时候有着更小的精度)。深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突、
防止深度冲突
第一个也是最重要的技巧是永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠。通过在两个物体之间设置一个用户无法注意到的偏移值,你可以完全避免这两个物体之间的深度冲突。在箱子和地板的例子中,我们可以将箱子沿着正y轴稍微移动一点。箱子位置的这点微小改变将不太可能被注意到,但它能够完全减少深度冲突的发生。然而,这需要对每个物体都手动调整,并且需要进行彻底的测试来保证场景中没有物体会产生深度冲突。
第二个技巧是尽可能将近平面设置远一些。在前面我们提到了精度在靠近近平面时是非常高的,所以如果我们将近平面远离观察者,我们将会对整个平截头体有着更大的精度。然而,将近平面设置太远将会导致近处的物体被裁剪掉,所以这通常需要实验和微调来决定最适合你的场景的近平面距离。
另外一个很好的技巧是牺牲一些性能,使用更高精度的深度缓冲。大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。所以,牺牲掉一些性能,你就能获得更高精度的深度测试,减少深度冲突。
我们上面讨论的三个技术是最普遍也是很容易实现的抗深度冲突技术了。还有一些更复杂的技术,但它们依然不能完全消除深度冲突。深度冲突是一个常见的问题,但如果你组合使用了上面列举出来的技术,你可能不会再需要处理深度冲突了。
测试代码 核心代码
#import "DepthTestViewController.h"
#import "DepthTestBindObject.h"
#import "CubeManager.h"
float 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 DepthTestViewController ()
@property (nonatomic ,strong) Vertex * vertex;
@property (nonatomic ,strong) Vertex * planeVertex ;
@property (nonatomic ,strong) TextureUnit * cubeUnit ;
@property (nonatomic ,strong) TextureUnit * floorUnit ;
@end
@implementation DepthTestViewController
-(void)initSubObject{
//生命周期三秒钟
glEnable(GL_DEPTH_TEST);
self.bindObject = [DepthTestBindObject 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]=planeVertices[i*5+j];
}
for (int j=0; j<2; j++) {
onevertex[j+3]=planeVertices[i*5+3+j];
}
[self.planeVertex setVertex:onevertex index:i];
}
[self.planeVertex bindBufferWithUsage:GL_STATIC_DRAW];
}
-(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)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(0,0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
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(self.bindObject->uniforms[DT_uniform_view], 1, 0,viewMatrix.m);
GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
GLKMatrix4 projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(85.0f),
aspectRatio,
1.0f,
20.0f);
glUniformMatrix4fv(self.bindObject->uniforms[DT_uniform_projection], 1, 0,projectionMatrix.m);
[self drawFloor];
[self drawCubeLocation:GLKVector3Make(-1.0f, 0.0f, -1.0f)];
[self drawCubeLocation:GLKVector3Make(0.0f, 0.0f, 1.0f)];
}
-(void)drawCubeLocation:(GLKVector3)location{
GLKMatrix4 mode = GLKMatrix4Identity;
mode = GLKMatrix4TranslateWithVector3(mode, location);
mode = GLKMatrix4Rotate(mode, 30*M_PI/180.0, 0, 1, 0);
glUniformMatrix4fv(self.bindObject->uniforms[DT_uniform_model], 1, 0,mode.m);
[self.cubeUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DT_uniform_Texture]];
[self.vertex enableVertexInVertexAttrib:DT_aPos numberOfCoordinates:3 attribOffset:0];
[self.vertex enableVertexInVertexAttrib:DT_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
[self.vertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:[CubeManager getTextureNormalVertexNum]];
}
-(void)drawFloor{
GLKMatrix4 mode = GLKMatrix4Identity;
glUniformMatrix4fv(self.bindObject->uniforms[DT_uniform_model], 1, 0,mode.m);
[self.floorUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:self.bindObject->uniforms[DT_uniform_Texture]];
[self.planeVertex enableVertexInVertexAttrib:DT_aPos numberOfCoordinates:3 attribOffset:0];
[self.planeVertex enableVertexInVertexAttrib:DT_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
[self.planeVertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:6];
}
@end