開始這篇文章之前兴垦,請先了解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é)果如下:
gif中顯示圖形跟在模擬器中是不一樣的周霉,模擬器沒有那些雜七雜八的小框框掂器,可能是我那個gif軟件的問題,模擬器的運(yùn)行結(jié)果是這樣的:
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é)果就正常了:
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é)果跟背面剔除是一樣的(請忽略雜七雜八的塊塊兒)。
所有教程代碼在此 : https://github.com/qingmomo/iOS-OpenGLES-