學習OpenGL ES模擬真實平面鏡的反射制造逼真3D效果
前言
在基于CubeMap的反射效果一文中,介紹到如何使用CubeMap讓物體反射環境的光,從而制造逼真的3D效果。本文將介紹另一種反射效果的制作,模擬真實平面鏡的反射。反射效果是實時的,而且可以反射任何3D模型。下面是一張比較丑的效果圖,例子里面設置的燈光比較暗,導出gif后效果不好,最好還是下載例子自己運行看的比較清楚。
原理
我將使用高中關于鏡面反射的物理知識來作為實現鏡面效果的理論基石。下面是2D下的關于鏡面反射的一張圖。
鏡子上顯示的圖像,可以看做鏡像過去的另一個人所看到的的情景。使用OpenGL的術語來說就是把攝像機以鏡子所在的平面做鏡像,得到的鏡像攝像機所觀察到的世界,就是鏡面上應該顯示的內容。基本原理雖然很簡單,但實現過程中也會遇到諸多問題。比如如何把鏡像攝像機的渲染結果貼到鏡面上,鏡像攝像機被其他物體遮擋該如何處理。
寫代碼之前
本文代碼依然延續學習OpenGL ES的項目代碼,任何之前已經介紹的代碼將不再介紹。所以你真的想看懂本文的話,至少對OpenGL和本系列Demo項目有基本的了解。
封裝攝像機
之前的代碼中一直使用GLK的方法生成觀察矩陣,這次我對攝像機進行了封裝,主要是為了更方便的進行鏡像。攝像機的類是Camera。主要功能是生成攝像機和鏡像攝像機。攝像機使用向前的向量forward,向上的向量up和位置position管理自身信息。鏡像時將這三個變量分別求解出鏡像值即可。求解向量的鏡像主要使用了向量的反射公式,具體大家可以看代碼。這里就不詳細解釋了。
@interface?Camera?:?NSObject@property?(assign,?nonatomic)?GLKVector3?forward;@property?(assign,?nonatomic)?GLKVector3?up;@property?(assign,?nonatomic)?GLKVector3?position;-?(void)setupCameraWithEye:(GLKVector3)eye?lookAt:(GLKVector3)lookAt?up:(GLKVector3)up;-?(void)mirrorTo:(Camera?*)targetCamera?plane:(GLKVector4)plane;-?(GLKMatrix4)cameraMatrix;@end
在鏡像方法- (void)mirrorTo:(Camera *)targetCamera plane:(GLKVector4)plane;中,使用GLKVector4表示平面,x,y,z表示法線,w表示在法線上移動的位移。
渲染鏡像攝像機內容
想要把鏡像攝像機的內容渲染到鏡面的平面上,我們需要建立一個新的Framebuffer,并且綁定一個紋理到它的顏色附件中。這樣就可以把鏡像攝像機的內容渲染到紋理了。如果你看過渲染到紋理這一篇文章,下面的代碼你就會感覺很熟悉。
-?(void)createTextureFramebuffer:(CGSize)framebufferSize?{????????glGenFramebuffers(1,?&mirrorFramebuffer);????glBindFramebuffer(GL_FRAMEBUFFER,?mirrorFramebuffer);????????//?生成顏色緩沖區的紋理對象并綁定到framebuffer上????glGenTextures(1,?&mirrorTexture);????glBindTexture(GL_TEXTURE_2D,?mirrorTexture);????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,?mirrorTexture,?0);????????//?下面這段代碼不使用紋理作為深度緩沖區。????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);????GLenum?status?=?glCheckFramebufferStatus(GL_FRAMEBUFFER);????if?(status?!=?GL_FRAMEBUFFER_COMPLETE)?{????????//?framebuffer生成失敗????}????glBindFramebuffer(GL_FRAMEBUFFER,?0);}
接著我們在渲染主場景之前,把場景渲染到鏡像專用的Framebuffer中。為了渲染鏡像中觀察者看到的景象,我將當前的觀察矩陣設置為鏡像攝像機mirrorCamera的觀察矩陣,并且設置了新的Viewport匹配當前的Framebuffer大小,同時也設置了新的投影矩陣mirrorProjectionMatrix匹配新的Framebuffer的比例。至于GL_CLIP_DISTANCE0_APPLE裁剪平面相關的代碼,我們后面再介紹。
-?(void)glkView:(GLKView?*)view?drawInRect:(CGRect)rect?{????self.projectionMatrix?=?self.mirrorProjectionMatrix;????self.cameraMatrix?=?[self.mirrorCamera?cameraMatrix];????glBindFramebuffer(GL_FRAMEBUFFER,?mirrorFramebuffer);????glViewport(0,?0,?1024,?1024);????glClearColor(0.7,?0.7,?0.9,?1);????glClear(GL_COLOR_BUFFER_BIT?|?GL_DEPTH_BUFFER_BIT);????self.clipplaneEnable?=?YES;????self.clipplane?=?GLKVector4Make(0,?0,?1,?0);????glEnable(GL_CLIP_DISTANCE0_APPLE);????[self?drawObjects];????????glDisable(GL_CLIP_DISTANCE0_APPLE);????self.clipplaneEnable?=?NO;????self.projectionMatrix?=?self.viewProjectionMatrix;????self.cameraMatrix?=?[self.mainCamera?cameraMatrix];????[view?bindDrawable];????glClearColor(0.7,?0.7,?0.7,?1);????glClear(GL_COLOR_BUFFER_BIT?|?GL_DEPTH_BUFFER_BIT);????[self?drawObjects];????[self?drawMirror];}
Mirror模型的渲染
Mirror繼承于Plane,繪制一個四邊形,目前并沒有實現任何獨特的代碼,主要用于后期將鏡面相關的邏輯移入其中。現在將它看做一個普通的四邊形即可,在渲染它時,使用了特別編寫的Shader frag_mirror.glsl。
precision?highp?float;varying?vec2?fragUV;varying?vec3?fragPosition;uniform?mat4?mirrorPVMatrix;uniform?mat4?modelMatrix;uniform?sampler2D?diffuseMap;void?main(void)?{????vec4?positionInWordCoord?=?mirrorPVMatrix?*?modelMatrix?*?vec4(fragPosition,?1.0);????positionInWordCoord?=?positionInWordCoord?/?positionInWordCoord.w;????positionInWordCoord?=?(positionInWordCoord?+?1.0)?*?0.5;????gl_FragColor?=?texture2D(diffuseMap,?positionInWordCoord.st);}
使用頂點位置最終投影到屏幕的坐標,計算UV,從鏡像攝像機渲染出的紋理上采樣。這個手法我們在投影紋理中有介紹到,相當于把鏡像攝像機看到的內容按照鏡像攝像機的VP矩陣投影到鏡面的平面上。
我們在主場景渲染時才渲染鏡面模型。并且開啟了GL_CULL_FACE,因為讓反面在渲染時使用另一個法線進行鏡像計算比較繁瑣而且沒有必要。在渲染過程中傳入鏡像攝像機和鏡像投影的矩陣相乘結果mirrorPVMatrix,以及頂點著色器需要的projectionMatrix和cameraMatrix,用來參與常規頂點著色流程。
-?(void)drawMirror?{????glEnable(GL_CULL_FACE);????[self.mirror.context?active];????[self.mirror.context?setUniformMatrix4fv:@“projectionMatrix”?value:self.projectionMatrix];????[self.mirror.context?setUniformMatrix4fv:@“mirrorPVMatrix”?value:?GLKMatrix4Multiply(self.mirrorProjectionMatrix,?[self.mirrorCamera?cameraMatrix])];????[self.mirror.context?setUniformMatrix4fv:@“cameraMatrix”?value:?self.cameraMatrix];????[self.mirror?draw:self.mirror.context];????glDisable(GL_CULL_FACE);}
裁剪平面
在前面我們提到過一個問題,如果鏡像攝像機被遮擋應該怎么辦。glEnable(GL_CLIP_DISTANCE0_APPLE);就是解決方案。裁剪平面在OpenGL中是直接支持的,但在OpenGL ES中需要使用蘋果的擴展,所以GL_CLIP_DISTANCE0_APPLE后面有個APPLE。我們將平面以Vector4的表達方式傳入Vertex Shader中,最終系統會將觀察點到平面之間的點都忽略掉。這里我寫死了0,0,1,0這個平面,當然你也可以動態獲取mirror模型的平面法線,使用normalMatrix和0,0,1,0相乘。
self.clipplaneEnable?=?YES;self.clipplane?=?GLKVector4Make(0,?0,?1,?0);glEnable(GL_CLIP_DISTANCE0_APPLE);
在Vertex Shader中需要添加如下代碼。
if?(clipplaneEnabled)?{????gl_ClipDistance[0]?=?dot((modelMatrix?*?position).xyz,?clipplane.xyz)?+?clipplane.w;}
總結
本文使用了渲染到紋理,紋理投影,裁剪平面等技術實現了鏡面效果。同時也涉及到了不少向量的計算,算是比較考驗對OpenGL ES的熟練度,讀者可以看完例子之后自己嘗試去實現這個效果,了解一下自己對OpenGL ES的熟練程度。
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%
下載地址
學習OpenGL ES模擬真實平面鏡的反射制造逼真3D效果下載
相關電子資料下載
- 使用OpenGL ES構架和GPU的離屏幀合成器/分解器功能實現 1730
- ARM Mali OpenGL ES軟件開發工具包 3236
- 采用QT+OpenGL ES方案進行開發硬件平臺 13252
- 英特爾OpenGL ES API中的新功能介紹 2952
- Android OpenGL ES用于英特爾圖形性能分析 2634
- Android上的高級OpenGL ES介紹 2622
- Android OpenGL ES開發:投影和相機視圖創建和應用 1090
- 英特爾在OpenGL ES3.1中的高級特性 2574
- 為何說OpenGL ES上使用Vulkan不適合作為SDK 7203
- 簡單解析OpenGL ES為緩存提供數據的7個步驟 5154