OpenGLES-05 立方體3D變換

開始這篇文章之前兴垦,請先了解3D變換的相關(guān)知識徙赢,下面資料寫得很好,請確保已經(jīng)閱讀過有關(guān)資料探越。
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變換知識)另外推薦下面資料狡赐,關(guān)于坐標(biāo)系統(tǒng)的,我覺得最好理解坐標(biāo)系統(tǒng)的資料钦幔,請都閱讀一遍
3.http://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/08%20Coordinate%20Systems/

OK枕屉,如果你繼續(xù)看下來的話,說明你已或多或少了解了3D變換的一些知識鲤氢。

請保證對投影矩陣搀擂,觀察矩陣西潘,模型矩陣已做了解

我們現(xiàn)在開始對《OpenGLES-04 繪制帶顏色的立方體》中的立方體進(jìn)行平移、旋轉(zhuǎn)哨颂、縮放這類具體的3D變換喷市,這位博主的教程寫得很好,若有時間咆蒿,推薦學(xué)習(xí)http://www.cnblogs.com/kesalin/archive/2012/12/07/3D_transform.html东抹,他的3D變換是View和OpenGLView交互,我們省去這些沃测,直接用OpenGLView與手勢交互缭黔,更快進(jìn)入軌道。

1.修改頂點(diǎn)著色器代碼蒂破,添加投影和模型矩陣:

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內(nèi)容只會在此類layer上描繪
    EAGLContext *_context;    //OpenGL渲染上下文
    GLuint _renderBuffer;     //
    GLuint _frameBuffer;      //

    GLuint _programHandle;
    GLuint _positionSlot; //頂點(diǎn)槽位
    GLuint _colorSlot;   //顏色槽位
    
    //新加矩陣相關(guān)
    GLKMatrix4 _projectionMatrix;
    GLKMatrix4 _modelViewMatrix;
    GLuint _projectionSlot;
    GLuint _modelViewSlot;
 
    //新加變換數(shù)值變量
    float TX,TY,TZ;   //平移
    float RX,RY,RZ;   //旋轉(zhuǎn)
    float S_XYZ;      //縮放
}

網(wǎng)上有很多關(guān)于矩陣的封裝馏谨,iOS系統(tǒng)庫也給我們封裝了,我們這里直接使用系統(tǒng)的GLKMatrix4附迷。

3.在setupProgram函數(shù)里獲取投影和模型矩陣的槽位惧互。

_positionSlot = glGetAttribLocation(_programHandle, "vPosition");
_colorSlot    = glGetAttribLocation(_programHandle, "vSourceColor");
    //新加
_modelViewSlot = glGetUniformLocation(_programHandle, "modelView");
_projectionSlot = glGetUniformLocation(_programHandle, "projection");

4.添加如下函數(shù),設(shè)置投影矩陣:

-(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);

參數(shù)1 fovyRadians:視角喇伯,要求輸入弧度喊儡,GLKMathDegreesToRadians幫助我們把角度值轉(zhuǎn)換為弧度。
參數(shù)2 aspect:算屏幕的寬高比稻据,如果不正確設(shè)置艾猜,可能顯示不出畫面。
參數(shù)3 nearZ:near 面可視深度
參數(shù)4 farZ:far 面可視深度
near和far共同決定了可視深度捻悯,都必須為正值匆赃,near一般設(shè)為一個比較小的數(shù),far必須大于near今缚。物體深度Z在near和far范圍之間才可見算柳。

glUniformMatrix4fv()的原型是

glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

它的參數(shù)分別為:下標(biāo)位置,矩陣數(shù)量姓言,是否進(jìn)行轉(zhuǎn)置瞬项,矩陣。
主要作用是調(diào)用glUniformMatrix4fv這個函數(shù)何荚,將矩陣傳遞到Shader中

5.添加如下函數(shù)滥壕,設(shè)置模型矩陣:

-(void)setupModelViewMatrix{
    _modelViewMatrix = GLKMatrix4Identity;   //初始矩陣   單位矩陣
    _modelViewMatrix = GLKMatrix4MakeTranslation(TX, TY, TZ); //平移
    _modelViewMatrix = GLKMatrix4RotateX(_modelViewMatrix, RX);  //旋轉(zhuǎn)
    _modelViewMatrix = GLKMatrix4RotateY(_modelViewMatrix, RY);
    _modelViewMatrix = GLKMatrix4RotateZ(_modelViewMatrix, RZ);
    _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix, S_XYZ, S_XYZ, S_XYZ);  //縮放
    glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, _modelViewMatrix.m);
}

對于這個模型矩陣各方法的理解就如函數(shù)名,沒什么好說的兽泣。請自行研究绎橘,還有許多別的方法。

6.給openGLView添加手勢
給我們的MyGLView中再添加3個變量

//新加手勢變量
UIPanGestureRecognizer *_panGesture;      //平移
UIPinchGestureRecognizer *_pinchGesture;  //縮放
UIRotationGestureRecognizer *_rotationGesture; //旋轉(zhuǎn)

然后在我們的initWithFrame方法中實(shí)例化這些變量并給初始的變換數(shù)值變量賦值:

-(instancetype)initWithFrame:(CGRect)frame{
    if (self==[super initWithFrame:frame]) {
        //賦值
        TX = 0; TY = 0; TZ = -6;
        RX = 0, RY = 0; RZ = 0;
        S_XYZ = 1;
        
        //實(shí)例化手勢
        _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是因?yàn)槟J(rèn)的觀察者位置在原點(diǎn),視線朝向 -Z 方向称鳞,我們設(shè)置遠(yuǎn)近平面為0.1-100涮较,則我們物體Z值在-100到-0.1之間才可見,也是TZ越大冈止,物體離我們越近狂票,物體也越大。

7.實(shí)現(xiàn)手勢方法熙暴,改變 變換值
添加如下4個函數(shù):

-(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添加兩個設(shè)置矩陣的函數(shù):

-(instancetype)initWithFrame:(CGRect)frame{
    if (self==[super initWithFrame:frame]) {
        //賦值
        TX = 0; TY = 0; TZ = -6;
        RX = 0, RY = 0; RZ = 0;
        S_XYZ = 1;
        
        //實(shí)例化手勢
        _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;
}

然后運(yùn)行闺属,做做操作,結(jié)果如下:

運(yùn)行結(jié)果.gif

gif中顯示圖形跟在模擬器中是不一樣的周霉,模擬器沒有那些雜七雜八的小框框掂器,可能是我那個gif軟件的問題,模擬器的運(yùn)行結(jié)果是這樣的:

靜態(tài)運(yùn)行結(jié)果.png

9.發(fā)現(xiàn)矩形存在的問題
肯定你發(fā)現(xiàn)了,咱們的矩形是有問題的,我們好像看穿了這個立方體茂蚓,立方體后面的面顯示在了前面,擋住了前面的面......意思就是這樣壳炎。
發(fā)生這種情況的原因是:
1).沒有做背面剔除,默認(rèn)情況下,OpenGL ES 是不進(jìn)行背面剔除的,也就是正對我們的面和背對我們的面都進(jìn)行了描繪孵睬,因此看起來就怪了。OpenGL ES 提供了 glFrontFace 這個函數(shù)來讓我們設(shè)置那那一面被當(dāng)做正面伶跷,默認(rèn)情況下逆時針方向的面被當(dāng)做正面(GL_CCW)掰读。我們可以調(diào)用 glCullFace 來明確指定我們想要剔除的面(GL_FRONT,GL_BACK, GL_FRONT_AND_BACK),默認(rèn)情況下是剔除 GL_BACK撩穿。為了讓剔除生效磷支,我們得使能之:glEnable(GL_CULL_FACE)谒撼。在這里食寡,我們只需要在合適的地方調(diào)用 glEnable(GL_CULL_FACE),其他的都采用默認(rèn)值就能滿足我們目前的需求廓潜。我們在render方法里添加

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

這時運(yùn)行結(jié)果就正常了:

正常結(jié)果.png

2).我們沒有開啟深度測試抵皱,openGL繪制時不知道哪個面深度高,哪個面深度低辩蛋,所以會出現(xiàn)這樣的結(jié)果呻畸,但要開啟深度測試的話,我們需要自己創(chuàng)建一個深度緩沖區(qū)來存儲物體的深度悼院。
關(guān)于深度測試請點(diǎn)這里:http://blog.csdn.net/zhongjling/article/details/7573055

10.使用深度緩存來解決立方體顯示問題
很多時候我們做背面剔除是滿足不了需求的(如繪制半透明的物體)伤为,這時候我們就得用到深度測試了。

1).我們在MyGLView中再添加一個變量

GLuint _depthBuffer;      //深度緩存

2).在函數(shù)setupRenderBuffer上面添加如下函數(shù):

-(void)setupDepthBuffer{
    glGenRenderbuffers(1, &_depthBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height);
}

并修改setupFrameBuffer函數(shù)如下(綁定深度緩存):

-(void)setupFrameBuffer{
    glGenFramebuffers(1, &_frameBuffer);   //生成和綁定frame buffer的API函數(shù)
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    //將renderbuffer跟framebuffer進(jìn)行綁定
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
    //將depthBuffer跟framebuffer進(jìn)行綁定
    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;
        
        //實(shí)例化手勢
        _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);  //添加

這樣的運(yùn)行結(jié)果跟背面剔除是一樣的(請忽略雜七雜八的塊塊兒)。

深度測試運(yùn)行結(jié)果.gif

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末位衩,一起剝皮案震驚了整個濱河市裆蒸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糖驴,老刑警劉巖僚祷,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贮缕,居然都是意外死亡辙谜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門跷睦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筷弦,“玉大人,你說我怎么就攤上這事抑诸±们伲” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵蜕乡,是天一觀的道長奸绷。 經(jīng)常有香客問我,道長层玲,這世上最難降的妖魔是什么号醉? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮辛块,結(jié)果婚禮上畔派,老公的妹妹穿的比我還像新娘。我一直安慰自己润绵,他們只是感情好线椰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尘盼,像睡著了一般憨愉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卿捎,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天配紫,我揣著相機(jī)與錄音,去河邊找鬼午阵。 笑死躺孝,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播植袍,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼伪很,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奋单?” 一聲冷哼從身側(cè)響起锉试,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎览濒,沒想到半個月后呆盖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贷笛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年应又,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乏苦。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡株扛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汇荐,到底是詐尸還是另有隱情洞就,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布掀淘,位于F島的核電站旬蟋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏革娄。R本人自食惡果不足惜倾贰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拦惋。 院中可真熱鬧匆浙,春花似錦、人聲如沸厕妖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叹放。三九已至饰恕,卻和暖如春挠羔,著一層夾襖步出監(jiān)牢的瞬間井仰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工破加, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俱恶,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像合是,于是被迫代替她去往敵國和親了罪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348