教程
OpenGLES入門教程1-Tutorial01-GLKit
OpenGLES入門教程2-Tutorial02-shader入門
OpenGLES入門教程3-Tutorial03-三維變換
OpenGLES入門教程4-Tutorial04-GLKit進階
OpenGLES進階教程1-Tutorial05-地球月亮
OpenGLES進階教程2-Tutorial06-光線
OpenGLES進階教程3-Tutorial07-粒子效果
OpenGLES進階教程4-Tutorial08-幀緩存
這一次是碰碰車。非常重要的一節(jié),是前面教程的一個應(yīng)用。
這一次的內(nèi)容較多,包括復(fù)雜頂點模型、第一和第三人稱視角變化、萬向節(jié)鎖、物理碰撞模擬、平滑動畫與高低通濾波器、模型封裝、材質(zhì)繪制。
這一次的教程會盡可能多詳細(xì)介紹。
效果展示

核心思路
通過加載頭文件的頂點數(shù)據(jù),得到復(fù)雜的頂點模型--車和場景。
先繪制場景,再單獨繪制每一輛車。
第三人稱視角固定為俯視角,第一人稱視角選取一輛車作為視點所在,視線為車的前進方向。
當(dāng)?shù)谝?、第三人稱視角切換的時候,通過濾波器來達到視線平滑過渡。
物理碰撞通過向量運算來模擬。
細(xì)節(jié)解析
1、模型加載
模型放在頭文件,car的數(shù)據(jù)放在bumperCar.h 包括bumperCarVerts頂點位置,bumperCarNormals頂點法線,總共有bumperCarNumVerts = 1164個頂點。
場景的數(shù)據(jù)放在bumperRink.h,格式和car一致,頂點較少,只有bumperRinkNumVerts = 180個頂點。
2、模型封裝
-
SceneMesh類是網(wǎng)格類,通過AGLKVertexAttribArrayBuffer管理頂點數(shù)據(jù),發(fā)送頂點數(shù)據(jù)到GPU,分配頂點數(shù)據(jù)內(nèi)存,繪制頂點數(shù)據(jù)。 -
SceneModel類是模型類,屬性axisAlignedBoundingBox放置了模型的最大最小邊界,屬性mesh是模型的網(wǎng)格類,管理頂點數(shù)據(jù)。 -
SceneCarModel類是car的模型類,包括car的頂點數(shù)據(jù)和模型的基本屬性,可以繪制car模型。 -
SceneRinkModel類是場景的模型類,包括場景的頂點數(shù)據(jù)和邊界等基本屬性,可以繪制場景。 -
SceneCar類是car的邏輯類,包括car的速度、位置、偏航角、半徑,還有濾波器函數(shù)、cars的碰撞處理、car與場景的碰撞處理、繪制car模型。
觀察SceneCar類的初始化函數(shù)
- (id)initWithModel:(SceneModel *)aModel
position:(GLKVector3)aPosition
velocity:(GLKVector3)aVelocity
color:(GLKVector4)aColor;
SceneCar需要SceneModel(模型類)、position(位置)、velocity(速度)、color(顏色)來初始化。
注意,這里的SceneCar并沒有依賴SceneCarModel,而是依賴抽象(基類)SceneModel,實現(xiàn)了解耦。可以新建一個SceneOtherCar繼承SceneModel,傳遞給SceneCar,不需要修改SceneCar的代碼就可以創(chuàng)建出一個新的car。
3、視角變化
視角通過函數(shù)GLKMatrix4MakeLookAt確定,參數(shù)如下:
GLKMatrix4 GLKMatrix4MakeLookAt(
float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ)
GLKMatrix4MakeLookAt() 函數(shù)會計算并返回一個model-view矩陣,這個矩陣會對齊從眼睛的位置到看向的位置之間的矢量與當(dāng)前視域的中心線。
GLKMatrix4MakeLookAt()的詳細(xì)解析會在接下來的教程--基于視錐體(平截體)的OpenGL ES性能優(yōu)化篇詳細(xì)介紹,目前可以按照參數(shù)來理解:假設(shè)有一個人,他的眼睛在前三個參數(shù)指定的位置,眼睛望向中間三個參數(shù)的位置,頭的朝向為最后三個參數(shù)的方向,up(0,1,0)為標(biāo)準(zhǔn)方向。
- 第一人稱視角如下。
self.eyePosition = GLKVector3Make(10.5, 5.0, 0.0);
self.lookAtPosition = GLKVector3Make(0.0, 0.5, 0.0);

- 第三人稱視角如下。
self.targetEyePosition = GLKVector3Make(viewerCar.position.x,
viewerCar.position.y + 0.45f,
viewerCar.position.z);
self.targetLookAtPosition = GLKVector3Add(_eyePosition, viewerCar.velocity);

細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)有eyePosition和targetEyePosition兩個position。
eyePosition表示的是當(dāng)前eye所在的位置,targetEyePosition表示的eye最終的目的。
之所以需要設(shè)置一個目標(biāo)的位置,是為了做視角的切換,通過高通濾波器函數(shù)SceneVector3FastLowPassFilter和低通濾波器函數(shù)SceneVector3SlowLowPassFilter,實現(xiàn)視角平滑過渡。
4、濾波器函數(shù)
- 高通濾波器函數(shù),50.0是一個可替換的較大的常數(shù)??梢阅M撞墻后震動的效果,因為50.0比較大,current值再增加后可能超過target。
GLfloat SceneScalarFastLowPassFilter(NSTimeInterval elapsed,
GLfloat target,
GLfloat current)
{
return current + (50.0 * elapsed * (target - current));
}
- 低通濾波器函數(shù),4.0是一個可替換的較小的常數(shù)。可以模擬視角切換過程的效果,因為4.0比較小,current會逐漸接近target。
GLfloat SceneScalarSlowLowPassFilter(NSTimeInterval elapsed,
GLfloat target,
GLfloat current)
{
return current + (4.0 * elapsed * (target - current));
}
5、萬向節(jié)鎖
| y
|<==-----------<==飛機
| -
| -
|
|______________________x
如上,假設(shè)地面的正北方向為x軸,正朝上為y軸?,F(xiàn)在左下角的原點處有一臺望遠(yuǎn)鏡,它通過(a, b)來望向天空中的飛機,a為與望遠(yuǎn)鏡與x軸正方向的夾角,我們用偏航角來解釋;b為望遠(yuǎn)鏡向上抬起后與地面的夾角,我們用高度角來解釋。
對于任意一臺坐標(biāo)為(a, b)的飛機,我們都可以通過先偏移a偏航角,再抬起b高度角,從而觀察到飛機。
現(xiàn)在假設(shè)有一臺坐標(biāo)為(0, 45)的飛機,朝望遠(yuǎn)鏡飛來。飛機在望遠(yuǎn)鏡的正北方向,偏航角為0,高度角為45。隨著飛機不斷接近望遠(yuǎn)鏡,偏航角不變,高度角不斷變大。
當(dāng)飛機飛到望遠(yuǎn)鏡的正上空的時候,坐標(biāo)為(0, 90)。
此時,如果飛機轉(zhuǎn)向朝東方向飛,偏航角由0直接變?yōu)?0,但是此時由于望遠(yuǎn)鏡正朝上(之前的坐標(biāo)為0,90),偏航角的改變沒有意義(丟失一個自由度)。
本教程還不需要用到四元數(shù)來解決這個問題,只是作為背景了解一下。Google上面有很多解釋,比我講的好很多。
6、物理碰撞模擬
- car與墻壁的碰撞
通過(SceneAxisAllignedBoundingBox)rinkBoundingBox可以得到墻壁的最大小邊界。根據(jù)car的radius屬性得到半徑,通過半徑radius+nextPosition與rinkBoundingBox判斷是否到達邊界。
如果到達邊界則把對應(yīng)軸的速度向量反向。 -
car之間的碰撞
假設(shè)兩輛車分別為self和other。
selfCar的速度為velocity、位置為position,otherCar的速度為otherVelocity、位置為otherPosition。
通過position和otherPosition,可以得到一條直線,car的碰撞就發(fā)生在這一條線上的方向上。
velocity在直線上的分量為tanOwnVelocity,otherVelocity在直線上的分量為tanOtherVelocity。
如上,碰撞完成后selfCar的速度為velocity - tanOwnVelocity,otherCar的速度為otherVelocity - tanOtherVelocity。
self.velocity = GLKVector3Subtract(ownVelocity, tanOwnVelocity);
currentCar.velocity = GLKVector3Subtract(otherVelocity, tanOtherVelocity);
7、材質(zhì)繪制
GLKBaseEffect有一個material屬性,用于材質(zhì)顏色的設(shè)置。
注意的是需要調(diào)用anEffect的prepareToDraw之后,再調(diào)用model的draw。
//設(shè)置材質(zhì)
anEffect.material.diffuseColor = self.color;
anEffect.material.ambientColor = self.color;
[anEffect prepareToDraw];
[model draw];
思考
場地、車都是分別繪制的,他們的位置是如何控制的?當(dāng)?shù)谝蝗朔Q視角切換后,為什么車的顯示會跟著變大?
這一部分作為思考題,就不給出答案了,在代碼里面有注釋。
總結(jié)
這一次的實現(xiàn)還是比較簡單,模型是放在頭文件,并沒有用modellist文件來管理。
我在源代碼的基礎(chǔ)上,簡化了不需要的代碼和增加了很多注釋??赐杲坛毯痛a的后,相信都能理解。
附上源碼
