獲取示例代碼
本文將介紹渲染到紋理技術(shù)遥诉。之前的例子都是將3D物體渲染到屏幕上须床,在iOS中GLKView為我們做好了渲染到屏幕的所有準(zhǔn)備工作铐料,我們只需要調(diào)用Open GL ES的繪制方法就可以輕松的渲染到屏幕。那么我們接下來了解一下GLKView為我們做了哪些準(zhǔn)備工作豺旬。
FrameBuffer
FrameBuffer是OpenGL ES中重要基礎(chǔ)組件之一余赢,經(jīng)常被縮寫成FBO(FrameBufferObject),它用來承載GPU計算出來的數(shù)據(jù)哈垢,包括顏色(Color),深度(Depth)扛拨,遮罩(Stencil)耘分。FrameBuffer包括3個緩沖區(qū),顏色緩沖區(qū),深度緩沖區(qū)求泰,遮罩緩沖區(qū)央渣,每個緩沖區(qū)就是一塊內(nèi)存,存儲著對應(yīng)的像素數(shù)據(jù)渴频。比如顏色緩沖區(qū)芽丹,一般像素格式都是RGBA,一共4個字節(jié)卜朗,如果是一個大小1024乘以1024的FrameBuffer拔第,那么顏色緩沖區(qū)所占的內(nèi)存就是1024x1024x4個字節(jié)。深度和遮罩緩沖區(qū)也有自己的格式场钉。GLKView默認(rèn)為我們創(chuàng)建了一個FrameBuffer蚊俺,并且綁定了剛才說的3個緩沖區(qū)到這個FrameBuffer上。我們所有繪制的操作逛万,最終都被寫入到這個FrameBuffer的緩沖區(qū)中泳猬。這個FrameBuffer里的顏色緩沖區(qū)的數(shù)據(jù)最終會被呈現(xiàn)在GLKView上。
手動創(chuàng)建FrameBuffer
既然GLKView可以幫我們創(chuàng)建FrameBuffer宇植,那么我們自己是不是也可以手動創(chuàng)建FrameBuffer呢得封?自然是可以的。創(chuàng)建FrameBuffer分為下面幾個步驟指郁。
- 創(chuàng)建FrameBuffer對象忙上,并綁定到GL_FRAMEBUFFER上,等待后續(xù)處理坡氯。
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
- 生成顏色緩沖區(qū)并附加到FrameBuffer上晨横。這里我們使用一個紋理對象作為顏色緩沖區(qū),這意味著所有繪制到FrameBuffer的顏色數(shù)據(jù)都會存儲到
framebufferColorTexture
中箫柳。更酷的是我們可以把這個紋理framebufferColorTexture
當(dāng)做貼圖使用手形,比如用作漫反射貼圖(diffuseMap)。
glGenTextures(1, &framebufferColorTexture);
glBindTexture(GL_TEXTURE_2D, framebufferColorTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, framebufferSize.width, framebufferSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, framebufferColorTexture, 0);
- 生成深度緩沖區(qū)并附加到framebuffer上悯恍。這里我們同樣使用了紋理對象作為緩沖區(qū)库糠。
glGenTextures(1, &framebufferDepthTexture);
glBindTexture(GL_TEXTURE_2D, framebufferDepthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, framebufferSize.width, framebufferSize.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT
, GL_TEXTURE_2D, framebufferDepthTexture, 0);
本例使用的是OpenGL ES2 API,如果使用OpenGL ES3涮毫,GL_DEPTH_COMPONENT需要修改成GL_DEPTH_COMPONENT_OES瞬欧。
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT_OES, framebufferSize.width, framebufferSize.height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
,修改第一個罢防,第二個不需要修改艘虎。
如果你不需要使用紋理作為深度緩沖區(qū),可以使用下面的寫法替代咒吐。創(chuàng)建一個RenderBuffer野建,而不是Texture属划。
GLuint depthBufferID;
glGenRenderbuffers(1, &depthBufferID);
glBindRenderbuffer(GL_RENDERBUFFER, depthBufferID);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, framebufferSize.width, framebufferSize.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferID);
本文不會使用遮罩(Stencil)緩沖區(qū),所以這里不做詳細(xì)介紹候生。
- 檢查FrameBuffer的創(chuàng)建狀態(tài)同眯。
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
// framebuffer生成失敗
}
如果狀態(tài)不是GL_FRAMEBUFFER_COMPLETE
,就說明創(chuàng)建有問題唯鸭⌒胛希可以根據(jù)status
排查問題。
使用FrameBuffer
接下來我們就要在創(chuàng)建好的FrameBuffer上繪制物體了目溉。步驟很簡單明肮,綁定FrameBuffer,設(shè)置Viewport停做,繪制晤愧。
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, self.framebufferSize.width, self.framebufferSize.height);
glClearColor(0.8, 0.8, 0.8, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
self.projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90), self.framebufferSize.width / self.framebufferSize.height, 0.1, 1000.0);
self.cameraMatrix = GLKMatrix4MakeLookAt(0, 1, sin(self.elapsedTime) * 5.0 + 9.0, 0, 0, 0, 0, 1, 0);
[self drawObjects];
Viewport是繪制區(qū)域的大小,我們把Viewport設(shè)置為FrameBuffer的尺寸蛉腌,這樣繪制區(qū)域就可以撐滿整個FrameBuffer了官份。我還設(shè)置了新的clearColor,新的投影矩陣烙丛,觀察矩陣舅巷,這樣就可以在FrameBuffer中呈現(xiàn)出不一樣的繪制效果。[self drawObjects];
里簡單的封裝了之前3D物體的繪制河咽。
- (void)drawObjects {
[self.objects enumerateObjectsUsingBlock:^(GLObject *obj, NSUInteger idx, BOOL *stop) {
[obj.context active];
[obj.context setUniform1f:@"elapsedTime" value:(GLfloat)self.elapsedTime];
[obj.context setUniformMatrix4fv:@"projectionMatrix" value:self.projectionMatrix];
[obj.context setUniformMatrix4fv:@"cameraMatrix" value:self.cameraMatrix];
[obj.context setUniform3fv:@"eyePosition" value:self.eyePosition];
[obj.context setUniform3fv:@"light.position" value:self.light.position];
[obj.context setUniform3fv:@"light.color" value:self.light.color];
[obj.context setUniform1f:@"light.indensity" value:self.light.indensity];
[obj.context setUniform1f:@"light.ambientIndensity" value:self.light.ambientIndensity];
[obj.context setUniform3fv:@"material.diffuseColor" value:self.material.diffuseColor];
[obj.context setUniform3fv:@"material.ambientColor" value:self.material.ambientColor];
[obj.context setUniform3fv:@"material.specularColor" value:self.material.specularColor];
[obj.context setUniform1f:@"material.smoothness" value:self.material.smoothness];
[obj.context setUniform1i:@"useNormalMap" value:self.useNormalMap];
[obj draw:obj.context];
}];
}
使用FrameBuffer的顏色緩沖區(qū)紋理
經(jīng)過上面的步驟钠右,我們成功的在FrameBuffer上進行了繪制。此時顏色緩沖區(qū)紋理已經(jīng)存儲了剛剛的繪制結(jié)果忘蟹。接下來我們將這個紋理顯示在一個平面上飒房。Plane
類繪制了一個1x1面朝z軸正方向的平面,并且接受一個diffuseMap作為漫反射貼圖媚值。我為它單獨寫了一個非常簡單的Fragment Shader (frag_framebuffer_plane.glsl)狠毯,沒有光照處理,直接顯示diffuseMap的像素褥芒。
precision highp float;
varying vec2 fragUV;
uniform sampler2D diffuseMap;
void main(void) {
gl_FragColor = texture2D(diffuseMap, fragUV);
}
下面的代碼將繪制3D物體和一個用來顯示FrameBuffer顏色緩沖區(qū)紋理的平面嚼松。
[view bindDrawable];
glClearColor(0.7, 0.7, 0.7, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
float aspect = self.view.frame.size.width / self.view.frame.size.height;
self.projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90), aspect, 0.1, 1000.0);
self.cameraMatrix = GLKMatrix4MakeLookAt(0, 1, 6.5, 0, 0, 0, 0, 1, 0);
[self drawObjects];
[self drawPlane];
[view bindDrawable];
將GLKView生成的FrameBuffer綁定到GL_FRAMEBUFFER
,并設(shè)置好Viewport锰扶,這樣后面繪制的內(nèi)容將呈現(xiàn)在GLKView上献酗。接著重新設(shè)置投影矩陣和觀察矩陣。繪制3D物體[self drawObjects];
坷牛。繪制平面[self drawPlane];
罕偎。值得一提的是,繪制平面時京闰,我使用了正交投影矩陣颜及,這樣平面就不會有透視效果痴怨,這正是我想要的。
- (void)drawPlane {
[self.displayFramebufferPlane.context active];
[self.displayFramebufferPlane.context setUniformMatrix4fv:@"projectionMatrix" value:self.planeProjectionMatrix];
[self.displayFramebufferPlane.context setUniformMatrix4fv:@"cameraMatrix" value:GLKMatrix4Identity];
[self.displayFramebufferPlane draw:self.displayFramebufferPlane.context];
}
planeProjectionMatrix
就是平面的正交投影矩陣器予,在createPlane
中被設(shè)置,Plane
使用了新的GLContext捐迫,由vertex.glsl
和frag_framebuffer_plane.glsl
組成乾翔。framebufferColorTexture
顏色緩沖區(qū)紋理在Plane
初始化時被傳遞進去。這里的紋理對象使用的GLuint類型的施戴,相當(dāng)于GLKTextureInfo的name屬性反浓,是OpenGL ES原始的紋理對象。
- (void)createPlane {
NSString *vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"vertex" ofType:@".glsl"];
NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"frag_framebuffer_plane" ofType:@".glsl"];
GLContext *displayFramebufferPlaneContext = [GLContext contextWithVertexShaderPath:vertexShaderPath fragmentShaderPath:fragmentShaderPath];
self.displayFramebufferPlane = [[Plane alloc] initWithGLContext:displayFramebufferPlaneContext texture:framebufferColorTexture];
self.displayFramebufferPlane.modelMatrix = GLKMatrix4Identity;
self.planeProjectionMatrix = GLKMatrix4MakeOrtho(-2.5, 0.5, -4.5, 0.5, -100, 100);
}
至于Plane
的實現(xiàn)代碼很簡單赞哗,讀者可以自行clone代碼查看雷则。
最終效果
右上角顯示的是使用了顏色緩沖區(qū)紋理作為漫反射貼圖的平面。
渲染到紋理能做什么肪笋?
最直接的用法就是同時顯示兩個視角的觀察結(jié)果月劈,比如賽車游戲能在右上角同時查看車后的情況。還有就是鏡面效果藤乙,水面效果猜揪,深度緩沖區(qū)貼圖可以做陰影效果(顏色緩沖區(qū)也能做,不過效果還是深度緩沖區(qū)好)坛梁,所以理解好渲染到紋理對后面的特效制作還是很有幫助的而姐。
寫在最后
OpenGL ES進階篇到此就結(jié)束啦,希望通過這部分的文章划咐,大家能夠?qū)penGL ES有更深的理解拴念。下一部分高級篇會以介紹一些OpenGL ES特效的原理和實現(xiàn)為主,比如陰影效果褐缠,紋理投影效果政鼠,水面效果,粒子效果送丰,物理引擎等等缔俄,敬請期待。