本案例是實現(xiàn)一個有紋理的立方體噪舀,并根據(jù)任意軸旋轉(zhuǎn)壹瘟,整體效果如下
-
未加光照
無光照效果圖
-
增加光照效果
有光照效果圖
增加光照的主要的思路如下
代碼的實現(xiàn)主要分為4部分:
- 準(zhǔn)備工作:這部分主要的相關(guān)庫的導(dǎo)入及屬性的創(chuàng)建(這里不做過多闡述)
- ViewDidLoad函數(shù):初始化OpenGL ES相關(guān)屬性命锄,加載頂點&紋理坐標(biāo)數(shù)據(jù)霸褒,以及設(shè)置定時器
- GLKViewDelegate函數(shù):視圖的繪制
- update函數(shù):定時器方法膛檀,計算旋轉(zhuǎn)角度并修改矩陣堆棧,重新渲染立方體斟赚,以實現(xiàn)立方體的旋轉(zhuǎn)
ViewDidLoad函數(shù)
這部分主要是一些初始化工作
- commonInit: OpenGL ES相關(guān)初始化
- setupVertex: 加載頂點&紋理坐標(biāo)數(shù)據(jù)
- addCADisplayLink: 添加定時器
commonInit函數(shù)
OpenGL ES初始化分為五部分:
- 初始化上下文 & 設(shè)置當(dāng)前上下文
- 創(chuàng)建GLKView對象着降,并設(shè)置context,加入view中
- 配置深度緩沖區(qū)
- 獲取紋理圖片 & 設(shè)置紋理參數(shù)
- 初始化effect拗军,并使用effect
注:代碼分為OC版本和Swift版本
- 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发侵、使用深度測試
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;
}
- Swift:其中有些變量使用了懶加載具體的代碼請見文末的鏈接
fileprivate func commonInit(){
// 1、初始化上下文 & 設(shè)置當(dāng)前上下文
guard let context = EAGLContext(api: .openGLES3) else{
return
}
EAGLContext.setCurrent(context)
glkView.context = context
// 讀取紋理圖片
let filePath = Bundle.main.path(forResource: "mouse", ofType: "jpg")
let image = UIImage(contentsOfFile: filePath!)
guard let textureInfo: GLKTextureInfo = try? GLKTextureLoader.texture(with: (image?.cgImage)!, options: [GLKTextureLoaderOriginBottomLeft:NSNumber.init(integerLiteral: 1)]) else{
return
}
// 使用effect
effect = GLKBaseEffect()
effect.texture2d0.name = textureInfo.name
effect.texture2d0.target = GLKTextureTarget(rawValue: textureInfo.target)!
}
setupVertex函數(shù)
這部分主要是設(shè)置頂點數(shù)據(jù)(頂點坐標(biāo) & 紋理坐標(biāo) & 法線)叔锐,并將這些數(shù)據(jù)從CPU拷貝至GPU
設(shè)置頂點數(shù)據(jù)
下圖是立方體的頂點坐標(biāo)與紋理坐標(biāo)圖示
其中6個面與紋理的映射關(guān)系如下
頂點數(shù)據(jù)使用結(jié)構(gòu)體定義
- OC版本
typedef struct {
GLKVector3 positionCoord; //頂點坐標(biāo)
GLKVector2 textureCoord; //紋理坐標(biāo)
GLKVector3 normal; //法線
} CCVertex;
//初始化--這里數(shù)據(jù)的初始化方式是c語言中的結(jié)構(gòu)體賦值
(CCVertex){{-0.5, 0.5, 0.5}, {0, 1}, {0, 0, 1}};
- Swift版本(暫時未包括法線)
struct CCVertex {
var positionCoord: GLKVector3
var textureCoord: GLKVector2
}
//初始化
CCVertex(positionCoord: GLKVector3(v: (-0.5, 0.5, 0.5)), textureCoord: GLKVector2(v: (0, 1)))
頂點數(shù)據(jù)的具體代碼見完整demo代碼挪鹏,這里不做過多說明
開辟緩存區(qū),copy頂點數(shù)據(jù)到GPU
將頂點數(shù)據(jù)從內(nèi)存(CPU)拷貝至顯存(GPU)中
頂點緩沖區(qū):簡稱VBO
頂點數(shù)組:簡稱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);
- Swift版本
//2掌腰、拷貝到頂點緩沖區(qū)
glGenBuffers(1, &vertexBuffer)
// 綁定頂點緩沖區(qū)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)
// coppy頂點數(shù)據(jù)
glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<CCVertex>.size*kVertexCount, vertices, GLenum(GL_STATIC_DRAW))
打開通道
attribute的開關(guān)在ios中是默認(rèn)關(guān)閉的狰住,需要使用代碼手動開啟张吉,同時通道需要打開三次(頂點齿梁,紋理,法線各需要打開一次)肮蛹,將頂點數(shù)據(jù)從顯存中讀取到GLKit的著色器中
- OC版本
其中的NULL是可以省略的勺择,但是加上代碼可讀性強
// 頂點數(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));
- Swift版本(暫未加光照)
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<CCVertex>.size), UnsafeMutableRawPointer(bitPattern: 0))
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
//這里加4的原因是因為蘋果對部分包含vector類型數(shù)據(jù)的結(jié)構(gòu)體加了一個padding,此處這個padding等于4個字節(jié)。CCVertex占24個字節(jié)伦忠,而不是5個float所占的20個字節(jié)
glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<CCVertex>.size), UnsafeMutableRawPointer(bitPattern: MemoryLayout<GLKVector3>.size+4))
addCADisplayLink函數(shù)
初始化定時器省核,并將定時器加入runloop中,用于立方體旋轉(zhuǎn)效果的實現(xiàn)
- OC版本
- (void)addCADisplayLink{
self.angle = 0;
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- Swift版本
fileprivate func addCADisplayLink(){
displayLink.add(to: RunLoop.main, forMode: .common)
}
update更新
CADisplayLink定時器的刷新的頻率與屏幕刷新頻率一致昆码,每次刷新都需要計算旋轉(zhuǎn)角度气忠,并應(yīng)用于立方體
- OC版本
- (void) update{
//計算旋轉(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];
}
- Swift版本
@objc fileprivate func update(){
// 計算旋轉(zhuǎn)角度
angle = (angle + 5).truncatingRemainder(dividingBy: 360)
// 修改矩陣堆棧
effect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(angle), 0.3, 1, -0.7)
// 重新渲染邻储,回調(diào)代理方法重新繪制
glkView.display()
}
GLKViewDelegate代理
由于GLKView是自定義的,所以需要在前面設(shè)置delegate旧噪,當(dāng)然也可以將控制器默認(rèn)的view的父類改為GLKView吨娜,不需要設(shè)置delegate
代理方法的主要目的是繪制視圖的內(nèi)容,并根據(jù)定時器的旋轉(zhuǎn)變換淘钟,重新渲染視圖
- OC版本
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
// 開啟深度測試
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);
}
- Swift版本
extension ViewController: GLKViewDelegate{
func glkView(_ view: GLKView, drawIn rect: CGRect) {
// 開啟深度測試
glEnable(GLenum(GL_DEPTH_TEST));
glClear(GLbitfield(GL_COLOR_BUFFER_BIT) | UInt32(GL_DEPTH_BUFFER_BIT))
//準(zhǔn)備繪制
effect.prepareToDraw()
//開始繪制
glDrawArrays(GLenum(GL_TRIANGLES), 0, GLsizei(kVertexCount))
}
}
注:swift與OC代碼的難點主要還是在于對指針的操作
完整的代碼見github - 09_GLKit_立方體+旋轉(zhuǎn)OC宦赠、09_GLKit立方體+旋轉(zhuǎn)_Swift(未包含光照效果),分別提供了OC和Swift版本