學(xué)習(xí)OpenGL ES之教你造一面鏡子

本系列所有文章目錄

獲取示例代碼


我是悶騷的占位圖

前言

基于CubeMap的反射效果一文中,介紹到如何使用CubeMap讓物體反射環(huán)境的光,從而制造逼真的3D效果运悲。本文將介紹另一種反射效果的制作的烁,模擬真實(shí)平面鏡的反射末早。反射效果是實(shí)時(shí)的卵惦,而且可以反射任何3D模型歉备。下面是一張比較丑的效果圖痴柔,例子里面設(shè)置的燈光比較暗樱溉,導(dǎo)出gif后效果不好摸吠,最好還是下載例子自己運(yùn)行看的比較清楚。

原理

我將使用高中關(guān)于鏡面反射的物理知識(shí)來(lái)作為實(shí)現(xiàn)鏡面效果的理論基石避矢。下面是2D下的關(guān)于鏡面反射的一張圖。


鏡子上顯示的圖像囊榜,可以看做鏡像過(guò)去的另一個(gè)人所看到的的情景审胸。使用OpenGL的術(shù)語(yǔ)來(lái)說(shuō)就是把攝像機(jī)以鏡子所在的平面做鏡像,得到的鏡像攝像機(jī)所觀察到的世界卸勺,就是鏡面上應(yīng)該顯示的內(nèi)容歹嘹。基本原理雖然很簡(jiǎn)單孔庭,但實(shí)現(xiàn)過(guò)程中也會(huì)遇到諸多問(wèn)題尺上。比如如何把鏡像攝像機(jī)的渲染結(jié)果貼到鏡面上,鏡像攝像機(jī)被其他物體遮擋該如何處理圆到。

寫(xiě)代碼之前

本文代碼依然延續(xù)學(xué)習(xí)OpenGL ES的項(xiàng)目代碼怎抛,任何之前已經(jīng)介紹的代碼將不再介紹。所以你真的想看懂本文的話芽淡,至少對(duì)OpenGL和本系列Demo項(xiàng)目有基本的了解马绝。

封裝攝像機(jī)

之前的代碼中一直使用GLK的方法生成觀察矩陣,這次我對(duì)攝像機(jī)進(jìn)行了封裝挣菲,主要是為了更方便的進(jìn)行鏡像富稻。攝像機(jī)的類(lèi)是Camera。主要功能是生成攝像機(jī)和鏡像攝像機(jī)白胀。攝像機(jī)使用向前的向量forward椭赋,向上的向量up和位置position管理自身信息。鏡像時(shí)將這三個(gè)變量分別求解出鏡像值即可或杠。求解向量的鏡像主要使用了向量的反射公式哪怔,具體大家可以看代碼。這里就不詳細(xì)解釋了。

@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表示在法線上移動(dòng)的位移。

渲染鏡像攝像機(jī)內(nèi)容

想要把鏡像攝像機(jī)的內(nèi)容渲染到鏡面的平面上硼身,我們需要建立一個(gè)新的Framebuffer鉴未,并且綁定一個(gè)紋理到它的顏色附件中。這樣就可以把鏡像攝像機(jī)的內(nèi)容渲染到紋理了鸠姨。如果你看過(guò)渲染到紋理這一篇文章铜秆,下面的代碼你就會(huì)感覺(jué)很熟悉。

- (void)createTextureFramebuffer:(CGSize)framebufferSize {
    
    glGenFramebuffers(1, &mirrorFramebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, mirrorFramebuffer);
    
    // 生成顏色緩沖區(qū)的紋理對(duì)象并綁定到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);
    
    // 下面這段代碼不使用紋理作為深度緩沖區(qū)讶迁。
    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);
}

接著我們?cè)阡秩局鲌?chǎng)景之前连茧,把場(chǎng)景渲染到鏡像專(zhuān)用的Framebuffer中。為了渲染鏡像中觀察者看到的景象巍糯,我將當(dāng)前的觀察矩陣設(shè)置為鏡像攝像機(jī)mirrorCamera的觀察矩陣啸驯,并且設(shè)置了新的Viewport匹配當(dāng)前的Framebuffer大小,同時(shí)也設(shè)置了新的投影矩陣mirrorProjectionMatrix匹配新的Framebuffer的比例祟峦。至于GL_CLIP_DISTANCE0_APPLE裁剪平面相關(guān)的代碼罚斗,我們后面再介紹。

- (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宅楞,繪制一個(gè)四邊形针姿,目前并沒(méi)有實(shí)現(xiàn)任何獨(dú)特的代碼,主要用于后期將鏡面相關(guān)的邏輯移入其中⊙嵫茫現(xiàn)在將它看做一個(gè)普通的四邊形即可距淫,在渲染它時(shí),使用了特別編寫(xiě)的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);
}

使用頂點(diǎn)位置最終投影到屏幕的坐標(biāo)榕暇,計(jì)算UV,從鏡像攝像機(jī)渲染出的紋理上采樣喻杈。這個(gè)手法我們?cè)?a href="http://www.reibang.com/p/23b45ea6bf7b" target="_blank">投影紋理中有介紹到彤枢,相當(dāng)于把鏡像攝像機(jī)看到的內(nèi)容按照鏡像攝像機(jī)的VP矩陣投影到鏡面的平面上。
我們?cè)谥鲌?chǎng)景渲染時(shí)才渲染鏡面模型筒饰。并且開(kāi)啟了GL_CULL_FACE缴啡,因?yàn)樽尫疵嬖阡秩緯r(shí)使用另一個(gè)法線進(jìn)行鏡像計(jì)算比較繁瑣而且沒(méi)有必要。在渲染過(guò)程中傳入鏡像攝像機(jī)和鏡像投影的矩陣相乘結(jié)果mirrorPVMatrix龄砰,以及頂點(diǎn)著色器需要的projectionMatrixcameraMatrix盟猖,用來(lái)參與常規(guī)頂點(diǎn)著色流程讨衣。

- (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);
}

裁剪平面

在前面我們提到過(guò)一個(gè)問(wèn)題换棚,如果鏡像攝像機(jī)被遮擋應(yīng)該怎么辦式镐。glEnable(GL_CLIP_DISTANCE0_APPLE);就是解決方案。裁剪平面在OpenGL中是直接支持的固蚤,但在OpenGL ES中需要使用蘋(píng)果的擴(kuò)展娘汞,所以GL_CLIP_DISTANCE0_APPLE后面有個(gè)APPLE。我們將平面以Vector4的表達(dá)方式傳入Vertex Shader中夕玩,最終系統(tǒng)會(huì)將觀察點(diǎn)到平面之間的點(diǎn)都忽略掉你弦。這里我寫(xiě)死了0,0燎孟,1禽作,0這個(gè)平面,當(dāng)然你也可以動(dòng)態(tài)獲取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;
}

總結(jié)

本文使用了渲染到紋理兔仰,紋理投影茫负,裁剪平面等技術(shù)實(shí)現(xiàn)了鏡面效果。同時(shí)也涉及到了不少向量的計(jì)算乎赴,算是比較考驗(yàn)對(duì)OpenGL ES的熟練度忍法,讀者可以看完例子之后自己嘗試去實(shí)現(xiàn)這個(gè)效果,了解一下自己對(duì)OpenGL ES的熟練程度榕吼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缔赠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子友题,更是在濱河造成了極大的恐慌嗤堰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件度宦,死亡現(xiàn)場(chǎng)離奇詭異踢匣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)戈抄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)离唬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人划鸽,你說(shuō)我怎么就攤上這事输莺∑莅ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵嫂用,是天一觀的道長(zhǎng)型凳。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嘱函,這世上最難降的妖魔是什么甘畅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮往弓,結(jié)果婚禮上疏唾,老公的妹妹穿的比我還像新娘。我一直安慰自己函似,他們只是感情好槐脏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著撇寞,像睡著了一般顿天。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上重抖,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天露氮,我揣著相機(jī)與錄音,去河邊找鬼钟沛。 笑死畔规,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恨统。 我是一名探鬼主播叁扫,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼畜埋!你這毒婦竟也來(lái)了莫绣?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤悠鞍,失蹤者是張志新(化名)和其女友劉穎对室,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體咖祭,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掩宜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了么翰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牺汤。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖浩嫌,靈堂內(nèi)的尸體忽然破棺而出檐迟,到底是詐尸還是另有隱情补胚,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布追迟,位于F島的核電站溶其,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏怔匣。R本人自食惡果不足惜握联,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一桦沉、第九天 我趴在偏房一處隱蔽的房頂上張望每瞒。 院中可真熱鬧,春花似錦纯露、人聲如沸剿骨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浓利。三九已至,卻和暖如春钞速,著一層夾襖步出監(jiān)牢的瞬間贷掖,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工渴语, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苹威,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓驾凶,卻偏偏與公主長(zhǎng)得像牙甫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子调违,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容