开始这篇文章之前,请先了解3D变换的相关知识,下面资料写得很好,请确保已经阅读过有关资料。
1.http://www.cnblogs.com/kesalin/archive/2012/12/06/3D_math.html
2.http://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/07%20Transformations/
(1.2为3D变换知识)另外推荐下面资料,关于坐标系统的,我觉得最好理解坐标系统的资料,请都阅读一遍
3.http://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/08%20Coordinate%20Systems/
OK,如果你继续看下来的话,说明你已或多或少了解了3D变换的一些知识。
请保证对投影矩阵,观察矩阵,模型矩阵已做了解
我们现在开始对《OpenGLES-04 绘制带颜色的立方体》中的立方体进行平移、旋转、缩放这类具体的3D变换,这位博主的教程写得很好,若有时间,推荐学习http://www.cnblogs.com/kesalin/archive/2012/12/07/3D_transform.html,他的3D变换是View和OpenGLView交互,我们省去这些,直接用OpenGLView与手势交互,更快进入轨道。
1.修改顶点着色器代码,添加投影和模型矩阵:
uniform mat4 projection; //新加
uniform mat4 modelView; //新加
attribute vec4 vPosition;
attribute vec4 vSourceColor;
varying vec4 vDestinationColor;
void main(void)
{
gl_Position = projection * modelView * vPosition; //新加
vDestinationColor = vSourceColor;
}
2.修改MyGLView,添加如下变量
@interface MyGLView ()
{
CAEAGLLayer *_eaglLayer; //OpenGL内容只会在此类layer上描绘
EAGLContext *_context; //OpenGL渲染上下文
GLuint _renderBuffer; //
GLuint _frameBuffer; //
GLuint _programHandle;
GLuint _positionSlot; //顶点槽位
GLuint _colorSlot; //颜色槽位
//新加矩阵相关
GLKMatrix4 _projectionMatrix;
GLKMatrix4 _modelViewMatrix;
GLuint _projectionSlot;
GLuint _modelViewSlot;
//新加变换数值变量
float TX,TY,TZ; //平移
float RX,RY,RZ; //旋转
float S_XYZ; //缩放
}
网上有很多关于矩阵的封装,iOS系统库也给我们封装了,我们这里直接使用系统的GLKMatrix4。
3.在setupProgram函数里获取投影和模型矩阵的槽位。
_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
_colorSlot = glGetAttribLocation(_programHandle, "vSourceColor");
//新加
_modelViewSlot = glGetUniformLocation(_programHandle, "modelView");
_projectionSlot = glGetUniformLocation(_programHandle, "projection");
4.添加如下函数,设置投影矩阵:
-(void)setupProjectionMatrix{
float aspect = self.frame.size.width/self.frame.size.height;
_projectionMatrix = GLKMatrix4MakePerspective(45.0*M_PI/180.0, aspect, 0.1, 100);
glUniformMatrix4fv(_projectionSlot, 1, GL_FALSE, _projectionMatrix.m);
}
GLKMatrix4MakePerspective()原型是
GLKMatrix4MakePerspective(float fovyRadians, float aspect, float nearZ, float farZ);
参数1 fovyRadians:视角,要求输入弧度,GLKMathDegreesToRadians帮助我们把角度值转换为弧度。
参数2 aspect:算屏幕的宽高比,如果不正确设置,可能显示不出画面。
参数3 nearZ:near 面可视深度
参数4 farZ:far 面可视深度
near和far共同决定了可视深度,都必须为正值,near一般设为一个比较小的数,far必须大于near。物体深度Z在near和far范围之间才可见。
glUniformMatrix4fv()的原型是
glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
它的参数分别为:下标位置,矩阵数量,是否进行转置,矩阵。
主要作用是调用glUniformMatrix4fv这个函数,将矩阵传递到Shader中
5.添加如下函数,设置模型矩阵:
-(void)setupModelViewMatrix{
_modelViewMatrix = GLKMatrix4Identity; //初始矩阵 单位矩阵
_modelViewMatrix = GLKMatrix4MakeTranslation(TX, TY, TZ); //平移
_modelViewMatrix = GLKMatrix4RotateX(_modelViewMatrix, RX); //旋转
_modelViewMatrix = GLKMatrix4RotateY(_modelViewMatrix, RY);
_modelViewMatrix = GLKMatrix4RotateZ(_modelViewMatrix, RZ);
_modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix, S_XYZ, S_XYZ, S_XYZ); //缩放
glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, _modelViewMatrix.m);
}
对于这个模型矩阵各方法的理解就如函数名,没什么好说的。请自行研究,还有许多别的方法。
6.给openGLView添加手势
给我们的MyGLView中再添加3个变量
//新加手势变量
UIPanGestureRecognizer *_panGesture; //平移
UIPinchGestureRecognizer *_pinchGesture; //缩放
UIRotationGestureRecognizer *_rotationGesture; //旋转
然后在我们的initWithFrame方法中实例化这些变量并给初始的变换数值变量赋值:
-(instancetype)initWithFrame:(CGRect)frame{
if (self==[super initWithFrame:frame]) {
//赋值
TX = 0; TY = 0; TZ = -6;
RX = 0, RY = 0; RZ = 0;
S_XYZ = 1;
//实例化手势
_panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(viewTranslate:)];
[self addGestureRecognizer:_panGesture];
_pinchGesture = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(viewZoom:)];
[self addGestureRecognizer:_pinchGesture];
_rotationGesture = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(viewRotation:)];
[self addGestureRecognizer:_rotationGesture];
[self setupLayer];
[self setupContext];
[self setupRenderBuffer];
[self setupFrameBuffer];
[self setupProgram]; //配置program
[self render];
}
return self;
}
这里TZ=-6是因为默认的观察者位置在原点,视线朝向 -Z 方向,我们设置远近平面为0.1-100,则我们物体Z值在-100到-0.1之间才可见,也是TZ越大,物体离我们越近,物体也越大。
7.实现手势方法,改变 变换值
添加如下4个函数:
-(void)viewTranslate:(UIPanGestureRecognizer *)panGesture{
CGPoint transPoint = [panGesture translationInView:self];
float x = transPoint.x / self.frame.size.width;
float y = transPoint.y / self.frame.size.height;
TX += x;
TY -= y;
TZ += 0.0;
[self updateTransform];
[panGesture setTranslation:CGPointMake(0, 0) inView:self];
}
-(void)viewRotation:(UIRotationGestureRecognizer *)rotationGesture{
float rotate = rotationGesture.rotation;
RX += rotate/2.0;
RY += rotate/3.0;
RZ += rotate;
[self updateTransform];
rotationGesture.rotation = 0;
}
-(void)viewZoom:(UIPinchGestureRecognizer *)pinchGesture{
float scale = pinchGesture.scale;
S_XYZ *= scale;
[self updateTransform];
pinchGesture.scale = 1.0;
}
-(void)updateTransform{
[self setupProjectionMatrix];
[self setupModelViewMatrix];
[self render];
}
8.修改initWithFrame添加两个设置矩阵的函数:
-(instancetype)initWithFrame:(CGRect)frame{
if (self==[super initWithFrame:frame]) {
//赋值
TX = 0; TY = 0; TZ = -6;
RX = 0, RY = 0; RZ = 0;
S_XYZ = 1;
//实例化手势
_panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(viewTranslate:)];
[self addGestureRecognizer:_panGesture];
_pinchGesture = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(viewZoom:)];
[self addGestureRecognizer:_pinchGesture];
_rotationGesture = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(viewRotation:)];
[self addGestureRecognizer:_rotationGesture];
[self setupLayer];
[self setupContext];
[self setupRenderBuffer];
[self setupFrameBuffer];
[self setupProgram]; //配置program
//新加
[self setupProjectionMatrix];
[self setupModelViewMatrix];
[self render];
}
return self;
}
然后运行,做做操作,结果如下:
运行结果.gifgif中显示图形跟在模拟器中是不一样的,模拟器没有那些杂七杂八的小框框,可能是我那个gif软件的问题,模拟器的运行结果是这样的:
静态运行结果.png9.发现矩形存在的问题
肯定你发现了,咱们的矩形是有问题的,我们好像看穿了这个立方体,立方体后面的面显示在了前面,挡住了前面的面......意思就是这样。
发生这种情况的原因是:
1).没有做背面剔除,默认情况下,OpenGL ES 是不进行背面剔除的,也就是正对我们的面和背对我们的面都进行了描绘,因此看起来就怪了。OpenGL ES 提供了 glFrontFace 这个函数来让我们设置那那一面被当做正面,默认情况下逆时针方向的面被当做正面(GL_CCW)。我们可以调用 glCullFace 来明确指定我们想要剔除的面(GL_FRONT,GL_BACK, GL_FRONT_AND_BACK),默认情况下是剔除 GL_BACK。为了让剔除生效,我们得使能之:glEnable(GL_CULL_FACE)。在这里,我们只需要在合适的地方调用 glEnable(GL_CULL_FACE),其他的都采用默认值就能满足我们目前的需求。我们在render方法里添加
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_CULL_FACE); //添加
这时运行结果就正常了:
正常结果.png2).我们没有开启深度测试,openGL绘制时不知道哪个面深度高,哪个面深度低,所以会出现这样的结果,但要开启深度测试的话,我们需要自己创建一个深度缓冲区来存储物体的深度。
关于深度测试请点这里:http://blog.csdn.net/zhongjling/article/details/7573055
10.使用深度缓存来解决立方体显示问题
很多时候我们做背面剔除是满足不了需求的(如绘制半透明的物体),这时候我们就得用到深度测试了。
1).我们在MyGLView中再添加一个变量
GLuint _depthBuffer; //深度缓存
2).在函数setupRenderBuffer上面添加如下函数:
-(void)setupDepthBuffer{
glGenRenderbuffers(1, &_depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height);
}
并修改setupFrameBuffer函数如下(绑定深度缓存):
-(void)setupFrameBuffer{
glGenFramebuffers(1, &_frameBuffer); //生成和绑定frame buffer的API函数
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
//将renderbuffer跟framebuffer进行绑定
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
//将depthBuffer跟framebuffer进行绑定
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
}
3).修改initWithFrame方法如下
-(instancetype)initWithFrame:(CGRect)frame{
if (self==[super initWithFrame:frame]) {
//赋值
TX = 0; TY = 0; TZ = -6;
RX = 0, RY = 0; RZ = 0;
S_XYZ = 1;
//实例化手势
_panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(viewTranslate:)];
[self addGestureRecognizer:_panGesture];
_pinchGesture = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(viewZoom:)];
[self addGestureRecognizer:_pinchGesture];
_rotationGesture = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(viewRotation:)];
[self addGestureRecognizer:_rotationGesture];
[self setupLayer];
[self setupContext];
[self setupDepthBuffer]; //新加
[self setupRenderBuffer];
[self setupFrameBuffer];
[self setupProgram]; //配置program
[self setupProjectionMatrix];
[self setupModelViewMatrix];
[self render];
}
return self;
}
4).在render方法里添加glEnable(GL_DEPTH_TEST)来开启深度测试,同样,有了深度缓存,我们就需要清理它:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); //添加
这样的运行结果跟背面剔除是一样的(请忽略杂七杂八的块块儿)。
深度测试运行结果.gif所有教程代码在此 : https://github.com/qingmomo/iOS-OpenGLES-
网友评论