OpenGLES-05 立方体3D变换

作者: _清墨 | 来源:发表于2017-11-08 09:05 被阅读110次

    开始这篇文章之前,请先了解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;
    }
    

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

    运行结果.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方法里添加

    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_CULL_FACE);  //添加
    

    这时运行结果就正常了:

    正常结果.png

    2).我们没有开启深度测试,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-

    相关文章

      网友评论

        本文标题:OpenGLES-05 立方体3D变换

        本文链接:https://www.haomeiwen.com/subject/eyllmxtx.html