前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenGLES-05 立方体3D变换

OpenGLES-05 立方体3D变换

作者头像
清墨
发布2018-05-07 15:53:27
1.1K0
发布2018-05-07 15:53:27
举报
文章被收录于专栏:清墨_iOS分享

开始这篇文章之前,请先了解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变换的一些知识。

代码语言:javascript
复制
请保证对投影矩阵,观察矩阵,模型矩阵已做了解

我们现在开始对《OpenGLES-04 绘制带颜色的立方体》中的立方体进行平移、旋转、缩放这类具体的3D变换,这位博主的教程写得很好,若有时间,推荐学习http://www.cnblogs.com/kesalin/archive/2012/12/07/3D_transform.html,他的3D变换是View和OpenGLView交互,我们省去这些,直接用OpenGLView与手势交互,更快进入轨道。

1.修改顶点着色器代码,添加投影和模型矩阵:

代码语言:javascript
复制
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,添加如下变量

代码语言:javascript
复制
@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函数里获取投影和模型矩阵的槽位。

代码语言:javascript
复制
_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
_colorSlot    = glGetAttribLocation(_programHandle, "vSourceColor");
    //新加
_modelViewSlot = glGetUniformLocation(_programHandle, "modelView");
_projectionSlot = glGetUniformLocation(_programHandle, "projection");

4.添加如下函数,设置投影矩阵:

代码语言:javascript
复制
-(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()原型是

代码语言:javascript
复制
 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()的原型是

代码语言:javascript
复制
glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

它的参数分别为:下标位置,矩阵数量,是否进行转置,矩阵。
主要作用是调用glUniformMatrix4fv这个函数,将矩阵传递到Shader中

5.添加如下函数,设置模型矩阵:

代码语言:javascript
复制
-(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个变量

代码语言:javascript
复制
//新加手势变量
UIPanGestureRecognizer *_panGesture;      //平移
UIPinchGestureRecognizer *_pinchGesture;  //缩放
UIRotationGestureRecognizer *_rotationGesture; //旋转

然后在我们的initWithFrame方法中实例化这些变量并给初始的变换数值变量赋值:

代码语言:javascript
复制
-(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个函数:

代码语言:javascript
复制
-(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添加两个设置矩阵的函数:

代码语言:javascript
复制
-(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;
}

然后运行,做做操作,结果如下:

运行结果.gif

gif中显示图形跟在模拟器中是不一样的,模拟器没有那些杂七杂八的小框框,可能是我那个gif软件的问题,模拟器的运行结果是这样的:

静态运行结果.png

9.发现矩形存在的问题 肯定你发现了,咱们的矩形是有问题的,我们好像看穿了这个立方体,立方体后面的面显示在了前面,挡住了前面的面......意思就是这样。 发生这种情况的原因是: 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方法里添加

代码语言:javascript
复制
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_CULL_FACE);  //添加

这时运行结果就正常了:

正常结果.png

2).我们没有开启深度测试,openGL绘制时不知道哪个面深度高,哪个面深度低,所以会出现这样的结果,但要开启深度测试的话,我们需要自己创建一个深度缓冲区来存储物体的深度。 关于深度测试请点这里:http://blog.csdn.net/zhongjling/article/details/7573055

10.使用深度缓存来解决立方体显示问题 很多时候我们做背面剔除是满足不了需求的(如绘制半透明的物体),这时候我们就得用到深度测试了。

1).我们在MyGLView中再添加一个变量

代码语言:javascript
复制
GLuint _depthBuffer;      //深度缓存

2).在函数setupRenderBuffer上面添加如下函数:

代码语言:javascript
复制
-(void)setupDepthBuffer{
    glGenRenderbuffers(1, &_depthBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height);
}

并修改setupFrameBuffer函数如下(绑定深度缓存):

代码语言:javascript
复制
-(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方法如下

代码语言:javascript
复制
-(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)来开启深度测试,同样,有了深度缓存,我们就需要清理它:

代码语言:javascript
复制
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);  //添加

这样的运行结果跟背面剔除是一样的(请忽略杂七杂八的块块儿)。

深度测试运行结果.gif

所有教程代码在此 : https://github.com/qingmomo/iOS-OpenGLES-

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017.11.08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档