本案例是實(shí)現(xiàn)一個(gè)有紋理的立方體,并根據(jù)任意軸旋轉(zhuǎn),整體效果如下
-
未加光照
image -
增加光照效果
image
增加光照的主要的思路如下
代碼的實(shí)現(xiàn)主要分為4部分:
- 準(zhǔn)備工作:這部分主要的相關(guān)庫(kù)的導(dǎo)入及屬性的創(chuàng)建(這里不做過多闡述)
- ViewDidLoad函數(shù):初始化OpenGL ES相關(guān)屬性怀挠,加載頂點(diǎn)&紋理坐標(biāo)數(shù)據(jù)巴比,以及設(shè)置定時(shí)器
- GLKViewDelegate函數(shù):視圖的繪制
- update函數(shù):定時(shí)器方法即横,計(jì)算旋轉(zhuǎn)角度并修改矩陣堆棧,重新渲染立方體苹享,以實(shí)現(xiàn)立方體的旋轉(zhuǎn)
ViewDidLoad函數(shù)
這部分主要是一些初始化工作
- commonInit: OpenGL ES相關(guān)初始化
- setupVertex: 加載頂點(diǎn)&紋理坐標(biāo)數(shù)據(jù)
- addCADisplayLink: 添加定時(shí)器
commonInit函數(shù)
OpenGL ES初始化分為五部分:
- 初始化上下文 & 設(shè)置當(dāng)前上下文
- 創(chuàng)建GLKView對(duì)象,并設(shè)置context挣菲,加入view中
- 配置深度緩沖區(qū)
- 獲取紋理圖片 & 設(shè)置紋理參數(shù)
- 初始化effect富稻,并使用effect
- OC
- (void) commonInit{
// 1掷邦、創(chuàng)建context
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:context];
// 2、創(chuàng)建GLKView并設(shè)置代理
CGRect frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width );
self.glkView = [[GLKView alloc] initWithFrame:frame context:context];
self.glkView.backgroundColor = [UIColor clearColor];
self.glkView.delegate = self;
// 3椭赋、使用深度測(cè)試
self.glkView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
// 4抚岗、將glkView加入到view上
[self.view addSubview:self.glkView];
// 5、獲取紋理圖片
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"mouse" ofType:@"jpg"];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
// 6哪怔、設(shè)置紋理參數(shù)(紋理倒置翻轉(zhuǎn)的策略)
NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft: @(YES)};
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:[image CGImage] options:options error:NULL];
// 7宣蔚、使用effect
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.texture2d0.name = textureInfo.name;
self.baseEffect.texture2d0.target = textureInfo.target;
}
setupVertex函數(shù)
這部分主要是設(shè)置頂點(diǎn)數(shù)據(jù)(頂點(diǎn)坐標(biāo) & 紋理坐標(biāo) & 法線),并將這些數(shù)據(jù)從CPU拷貝至GPU
設(shè)置頂點(diǎn)數(shù)據(jù)
下圖是立方體的頂點(diǎn)坐標(biāo)與紋理坐標(biāo)圖示
其中6個(gè)面與紋理的映射關(guān)系如下
頂點(diǎn)數(shù)據(jù)使用結(jié)構(gòu)體定義
- OC版本
typedef struct {
GLKVector3 positionCoord; //頂點(diǎn)坐標(biāo)
GLKVector2 textureCoord; //紋理坐標(biāo)
GLKVector3 normal; //法線
} CCVertex;
//初始化--這里數(shù)據(jù)的初始化方式是c語(yǔ)言中的結(jié)構(gòu)體賦值
(CCVertex){{-0.5, 0.5, 0.5}, {0, 1}, {0, 0, 1}};
頂點(diǎn)數(shù)據(jù)的具體代碼見完整demo代碼认境,這里不做過多說明
開辟緩存區(qū)胚委,copy頂點(diǎn)數(shù)據(jù)到GPU
將頂點(diǎn)數(shù)據(jù)從內(nèi)存(CPU)拷貝至顯存(GPU)中
頂點(diǎn)緩沖區(qū):簡(jiǎn)稱VBO
頂點(diǎn)數(shù)組:簡(jiǎn)稱VAO
在glBufferData
中確認(rèn)了緩存區(qū)的大小
- OC版本
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(CCVertex)*kCoordCount, self.vertices, GL_STATIC_DRAW);
打開通道
attribute的開關(guān)在ios中是默認(rèn)關(guān)閉的,需要使用代碼手動(dòng)開啟叉信,同時(shí)通道需要打開三次(頂點(diǎn)亩冬,紋理,法線各需要打開一次)硼身,將頂點(diǎn)數(shù)據(jù)從顯存中讀取到GLKit的著色器中
- OC版本
其中的NULL是可以省略的硅急,但是加上代碼可讀性強(qiáng)
// 頂點(diǎn)數(shù)據(jù)
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(CCVertex), NULL+offsetof(CCVertex, positionCoord));
// 紋理數(shù)據(jù)
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(CCVertex), NULL+offsetof(CCVertex, textureCoord));
// 光照
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, sizeof(CCVertex), NULL+offsetof(CCVertex, normal));
addCADisplayLink函數(shù)
初始化定時(shí)器,并將定時(shí)器加入runloop中佳遂,用于立方體旋轉(zhuǎn)效果的實(shí)現(xiàn)
- OC版本
- (void)addCADisplayLink{
self.angle = 0;
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
update更新
CADisplayLink定時(shí)器的刷新的頻率與屏幕刷新頻率一致营袜,每次刷新都需要計(jì)算旋轉(zhuǎn)角度,并應(yīng)用于立方體
- OC版本
- (void) update{
//計(jì)算旋轉(zhuǎn)度數(shù)
self.angle = (self.angle +5) % 360;
// 修改baseEffect.transform.modelviewMatrix
self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(self.angle), 0.3, 1, 0.7);
// 重新渲染
[self.glkView display];
}
GLKViewDelegate代理
由于GLKView是自定義的丑罪,所以需要在前面設(shè)置delegate荚板,當(dāng)然也可以將控制器默認(rèn)的view的父類改為GLKView,不需要設(shè)置delegate
代理方法的主要目的是繪制視圖的內(nèi)容吩屹,并根據(jù)定時(shí)器的旋轉(zhuǎn)變換跪另,重新渲染視圖
- OC版本
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
// 開啟深度測(cè)試
glEnable(GL_DEPTH_TEST);
// 清除緩存區(qū)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 準(zhǔn)備繪制
[self.baseEffect prepareToDraw];
// 繪圖(數(shù)組繪制)
glDrawArrays(GL_TRIANGLES, 0, kCoordCount);
}