OpenGLES-05 立方体3D变换

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358