开源时代程序员首页投稿(暂停使用,暂停投稿)

OpenGLES-05 立方体3D变换

2017-11-08  本文已影响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-

上一篇下一篇

猜你喜欢

热点阅读