??隨著前有Apple在iOS11中提供了ARKit类浪,后有Google推出的ARCore载城,顯然掀起了一股AR熱潮(都是一堆廢話,說白了就是公司要求做)费就。由于高通的Vuforia已經(jīng)存在較長(zhǎng)時(shí)間了诉瓦,相對(duì)于EasyAR或者百度AR更為成熟一點(diǎn)兒,所以它成了第一個(gè)技術(shù)選擇力细。EasyAR和百度的AR——DuMix AR后面再依次去學(xué)習(xí)睬澡。好吧,先開始來學(xué)習(xí)Vuforia吧眠蚂!
該文章同步發(fā)布在我的博客.
集成步驟
由于這些步驟相對(duì)來說比較基礎(chǔ)煞聪,我就直接羅列出來。
1逝慧、開發(fā)者官網(wǎng)https://developer.vuforia.com昔脯;
2、下載ios-sdk和ios-sample笛臣,并按照官方文檔要求將ios-sample放入到ios-sdk的sample文件夾下
-
3云稚、在vuforia的開發(fā)者官網(wǎng)上的License Manager和Target Manager,添加License-key和database
License-Key -
4沈堡、將上一步添加的License-key静陈,放在ios-sample中的代碼文件SampleApplicationSession.mm中的方法
Vuforia::setInitParameters(mVuforiaInitFlags,"");
中。具體步驟見官網(wǎng)踱蛀。Vuforia::setInitParameters(mVuforiaInitFlags,"AT16FIX/////AAAAGVieZ/kg1UkghTnYAz5zXWs8+y5JjeF/NJRcjgVDoCSvsrSt+lWzFMcIVBbQ2YSFRF+6J0GceHoaz8NctXib3cndJEacXmR+1FyO5FhalO7sC4hE9d1/x72qTNDhkPs4rF04JulMYT876Grsnmg9C61oyaDVwBfSpzNZ7gx3NADkkV5q4NQs4ghZwVCdMhj6LVt1YTJcwiuULtDTEgpFZZeW/nC8yiC53hpUFOVxhH++ILx1T65jpY8yDn6ct++3mgVeVotg/5tWXYb5FYqBtJiwU/LJJxhJYqUWyy4pd9dHUJBQojuAE8FoW1DmjokrpDWgjOMMp3am4GjNT04hCg+o0Z3SByYx6VIqfSR9fsXw");
-
5窿给、將第3步中Target Manager創(chuàng)建的database,傳入相關(guān)的圖片文件率拒。也可以不傳崩泡,這步可選;怎樣使用設(shè)備數(shù)據(jù)猬膨。我們?cè)?a target="_blank" rel="nofollow">官方Developer創(chuàng)建角撞,下載對(duì)應(yīng)的Database呛伴,在添加圖片時(shí),對(duì)應(yīng)的星星數(shù)越高表明識(shí)別度越高
Target-Manager
解壓并將其引入到工程中:
引入工程
這一步可以不做谒所,因?yàn)檫@只是在給后面打基礎(chǔ)而已热康,如果只是運(yùn)行demo的話是不需要做這一步的。如果做了這一步劣领,在掃描你對(duì)應(yīng)的圖片的時(shí)候是沒有任何效果的姐军,具體的操作后面。 6尖淘、編譯運(yùn)行奕锌,由于需要使用到傳感器,所以必須使用真機(jī)來運(yùn)行村生。關(guān)于真機(jī)運(yùn)行的相關(guān)事項(xiàng)查看apple developer
源碼閱讀順序
源碼說明:
Voforia SDK版本:vuforia-sdk-ios-6-5-19
iOS Samples版本:vuforia-samples-core-ios-6-5-20
如果不想看源碼相關(guān)可直接跳過這部分惊暴,直接跟著“收尾”做自定義的tracker和模型(替換Teapot茶壺模型為自己的)
為何要閱讀源碼?因?yàn)樵?strong>Voforia的官方文檔中我沒有找到我自己想要的信息趁桃。所以我們需要通過閱讀源碼辽话,來找到怎樣才能去修改貼在目標(biāo)圖像中虛擬模型。以sample中ImageTargetsViewController
為例來解讀卫病!
首先查看ImageTargetsViewController.h
文件油啤,我們先不看成員變量。先來看屬性
屬性 | 屬性類型 | 初步作用 |
---|---|---|
eaglView | ImageTargetsEAGLView* |
初步認(rèn)定為一個(gè)展示視圖 |
tapGestureRecognizer | UITapGestureRecognizer* |
一個(gè)點(diǎn)擊手勢(shì) |
vapp | SampleApplicationSession* |
初步認(rèn)定為一個(gè)會(huì)話層(類似于ISO網(wǎng)絡(luò)七層模型中忽肛,在TCP可以歸于應(yīng)用層村砂,也就是說想偷懶可以直接將其代碼放入控制器中。個(gè)人理解) |
showingMenu | BOOL |
一個(gè)flag |
從上表中出現(xiàn)的屬性屹逛,我們先來分析一下屬性eaglView
和vapp
础废。
SampleApplicationSession類
在ImageTargetsViewController
控制器類中,和下面會(huì)講到的ImageTargetsEAGLView
都有SampleApplicationSession
類型的屬性罕模,所以我們有必要先來看看該類评腺。同樣的先看頭文件,因?yàn)轭^文件能夠讓我們對(duì)于該類有大體的認(rèn)識(shí)淑掌,而不拘于類具體的實(shí)現(xiàn)細(xì)節(jié)蒿讥。
??粗略來看,提供了一個(gè)初始化方法抛腕;一個(gè)初始化AR的方法芋绸;四個(gè)對(duì)AR的操作方法(它們不是我們需要的重點(diǎn),等到需要的時(shí)候再來仔細(xì)閱讀)担敌;以及一個(gè)對(duì)Camera的方法:
- (id)initWithDelegate:(id<SampleApplicationControl>) delegate;
- (void) initAR:(int) VuforiaInitFlags orientation:(UIInterfaceOrientation) ARViewOrientation;
- (bool) startAR:(Vuforia::CameraDevice::CAMERA_DIRECTION) camera error:(NSError **)error;
- (bool) pauseAR:(NSError **)error;
- (bool) resumeAR:(NSError **)error;
- (bool) stopAR:(NSError **)error;
- (bool) stopCamera:(NSError **)error;
上述的initAR方法是通過異步實(shí)現(xiàn)的摔敛,當(dāng)其AR初始化完成之后會(huì)調(diào)用方法下面會(huì)提到的代理方法onInitARDone
。順藤摸瓜全封,我們來看看該代理马昙,那么該代理所需要處理的事務(wù)有哪些呢桃犬?這里先將SampleApplicationControl
的所有方法先列出來:
@required
- (void) onInitARDone:(NSError *)error;
- (bool) doInitTrackers;
- (bool) doLoadTrackersData;
- (bool) doStartTrackers;
- (bool) doStopTrackers;
- (bool) doUnloadTrackersData;
- (bool) doDeinitTrackers;
- (void)configureVideoBackgroundWithViewWidth:(float)viewWidth andHeight:(float)viewHeight;
@optional
- (void) onVuforiaUpdate: (Vuforia::State *) state;
該代理方法中大多是涉及到的是tracker。通過從初始化方法開始查看方法調(diào)用行楞,得出了一個(gè)程序執(zhí)行流程圖攒暇,
我們主動(dòng)調(diào)用
initAR
方法,其結(jié)果會(huì)由回調(diào)方法onInitARDone
反應(yīng)給開發(fā)者子房。開發(fā)者可以用通過調(diào)用doInitTrackers
來控制是否需要去加載tracker數(shù)據(jù)形用,如果可以加載數(shù)據(jù)則通過調(diào)用回調(diào)方法doLoadTrackersData
來獲取數(shù)據(jù)。關(guān)于該類中其他幾個(gè)方法startAR , pauseAR, resumeAR, stopAR
由調(diào)用人員主動(dòng)調(diào)用池颈,調(diào)用這些方法會(huì)觸發(fā)對(duì)應(yīng)的方法回調(diào)尾序。現(xiàn)在我們需要把目光轉(zhuǎn)向
ImageTargetsEAGLView
類,并去具體的看一下里面的相關(guān)細(xì)節(jié)躯砰。
SampleAppRenderer類
這個(gè)類主要是做渲染相關(guān)的工作,其源碼大多數(shù)為OpenGL携丁。所以對(duì)于該類我只做具體的作用分析琢歇,而不去解釋具體的源代碼(因?yàn)槲乙膊欢缬行枰脑捗渭孕猩罹堪衫蠲#??。這里先將各個(gè)方法的作用羅列出來:
方法名 | 方法作用 |
---|---|
initWithSampleAppRendererControl | 類初始化方法 |
initRendering | 渲染相關(guān)的初始化 |
setNearPlane:farPlane: | 配置投影矩陣數(shù)據(jù) |
renderFrameVuforia | 由Vuforia調(diào)用肥橙,渲染數(shù)據(jù)幀到屏幕 |
renderVideoBackground | 后臺(tái)渲染視頻 |
configureVideoBackgroundWithViewWidth:andHeight: | 視頻相關(guān)的配置 |
updateRenderingPrimitives | 更新渲染數(shù)據(jù) |
下面具體分析:老規(guī)矩魄宏,同樣先看頭文件,我們根據(jù)頭文件暴露出來的方法一層一層往里剝存筏。該類存在一個(gè)協(xié)議SampleAppRendererControl
宠互,和一個(gè)初始化方法initWithSampleAppRendererControl
。使用這個(gè)方法需要傳入一個(gè)遵守SampleAppRendererControl
協(xié)議的類實(shí)例椭坚,第二個(gè)參數(shù)來決定VR/AR的模式予跌,以及三個(gè)用于決定投影矩陣的參數(shù)。除了在初始化方法設(shè)置投影矩陣的參數(shù)善茎,該類提供了一個(gè)public方法setNearPlane:farPlane:
券册。進(jìn)入到.mm文件中查看該初始化方法可以看出,只是對(duì)類內(nèi)部私有屬性進(jìn)行相關(guān)的賦值操作以及對(duì)硬件設(shè)備進(jìn)行相關(guān)的設(shè)置吧垂涯。
??現(xiàn)在來看看方法initRendering
烁焙,這個(gè)方法里面主要是做了一些OpenGLES的東西,我們只需要知道里面做了一些和具體業(yè)務(wù)邏輯無關(guān)的東西就行了耕赘。
??接下來看renderFrameVuforia
的作用是什么骄蝇?源代碼中說的很清楚:使用OpenGL繪制當(dāng)前幀,當(dāng)需要將當(dāng)前幀渲染到屏幕上時(shí)鞠苟,Vuforia會(huì)定期的在后臺(tái)線程調(diào)用該方法乞榨。同樣和業(yè)務(wù)邏輯無關(guān)秽之,源碼不細(xì)看。同樣方法renderVideoBackground
也是使用OpenGL來做吃既,我們只需要從該方法的名字得知其用途(后臺(tái)渲染視頻)即可考榨。
??configureVideoBackgroundWithViewWidth:andHeight:
方法從名字就可以知道其作用。updateRenderingPrimitives
方法的作用是:當(dāng)屏幕尺寸發(fā)生改變或者是設(shè)備朝向改變之后鹦倚,調(diào)用該方法來更新渲染原始數(shù)據(jù)河质。
??最后需要介紹一下該類很重要的的一個(gè)協(xié)議方法:renderFrameWithState
,該方法被用于獲取渲染相關(guān)的數(shù)據(jù)震叙。通過對(duì).mm文件可知掀鹅,每渲染一次都會(huì)調(diào)用該方法一次。
ImageTargetsEAGLView類
該類的頭文件所暴露出來的初始化方法- (id)initWithFrame:(CGRect)frame appSession:(SampleApplicationSession *) app
媒楼,我們以該方法入手來分析乐尊。第一個(gè)參數(shù)為當(dāng)前視圖的大小設(shè)置,第二個(gè)參數(shù)為前面我們講到過的一個(gè)類實(shí)例划址。頭文件中余下的方法還有:
- (void)finishOpenGLESCommands;
- (void)freeOpenGLESResources;
- (void) setOffTargetTrackingMode:(BOOL) enabled;
- (void) configureVideoBackgroundWithViewWidth:(float)viewWidth andHeight:(float)viewHeight;
- (void) updateRenderingPrimitives;
方法finishOpenGLESCommands , freeOpenGLESResources
分別對(duì)應(yīng)著結(jié)束OpenGL和釋放OpenGL的資源扔嵌。configureVideoBackgroundWithViewWidth:andHeight: , updateRenderingPrimitives和類SampleAppRenderer公開的方法名一樣,這里我猜測(cè)它們作用是一樣的夺颤。setOffTargetTrackingMode:
方法作用目前還不是很清晰痢缎,需要去.mm文件中詳查。
??現(xiàn)在進(jìn)入實(shí)現(xiàn)文件中世澜,源碼中提到了關(guān)于OpenGL線程安全的問題独旷。iOS上的OpenGL ES是線程不安全的,在程序中Vuforia使用下面的方法來保證線程(OpenGL 上下文)安全:
- a寥裂、在主線程中創(chuàng)建OpenGL ES上下文嵌洼。
- b、Vuforia相機(jī)開始時(shí)抚恒,將其位于我們自己EAGLView視圖上咱台,并開啟renderer線程。
- c俭驮、Vuforia會(huì)在renderer線程上回溺,定期調(diào)用我們的renderFrameVuforia(SampleAppRenderer類提到)方法。當(dāng)?shù)谝淮握{(diào)用該方法的時(shí)候混萝,
defaultFramebuffer
并不存在遗遵,調(diào)用createFramebuffer方法來創(chuàng)建它。createFramebuffer由主線程調(diào)用逸嘀,而與此同時(shí)renderer線程會(huì)被阻塞车要。因此確保OpenGL ES上下文不會(huì)被并行使用
在initWithFrame:appSession:的實(shí)現(xiàn)方法中會(huì)進(jìn)行session,OpenGL的context和Renderer的賦值崭倘,初始化和綁定工作翼岁。而方法configureVideoBackgroundWithViewWidth:andHeight: , updateRenderingPrimitives在其實(shí)現(xiàn)方法中的確只是簡(jiǎn)單的調(diào)用了一下SampleAppRenderer 類的實(shí)例方法类垫。
??現(xiàn)在主要來看看方法setOffTargetTrackingMode :
,它的實(shí)現(xiàn)很簡(jiǎn)單只是對(duì)其私有成員變量NO琅坡。但是卻在協(xié)議方法renderFrameWithState
中大量的使用悉患。該方法大部分是OpenGL相關(guān)的工作,我沒有深究下去榆俺,只整理出來一個(gè)工作流程圖:
目前來看ImageTargetsEAGLView類的主要作用在于保證OpenGL在iOS中達(dá)到線程安全售躁,創(chuàng)建buffer和對(duì)buffer的管理,提供了對(duì)OpenGL的控制茴晋,而實(shí)際的渲染則由SampleAppRenderer來實(shí)現(xiàn)陪捷。
ImageTargetsViewController類
現(xiàn)在將目光回到ImageTargetsViewController
類上面來。由于是一個(gè)控制器類诺擅,所以我直接從.mm文件中著手市袖。根據(jù)ViewController的加載順序來看具體的邏輯,首先查找loadView
方法烁涌,如果沒有則查找viewDidLoad
凌盯。源碼中,loadView方法主要?jiǎng)?chuàng)建了vapp烹玉,eaglView以及對(duì)vapp初始化了AR相關(guān)的事務(wù)(其他視圖和手勢(shì)等先忽略,只關(guān)心屬性vapp,eaglView相關(guān)的邏輯)阐滩,將ViewController的View設(shè)置為eaglView二打。
??在loadView中將vapp的代理設(shè)置為控制器自身,此時(shí)通過上面介紹__ SampleApplicationSession__時(shí)對(duì)應(yīng)的程序執(zhí)行流程掂榔,將目光放在對(duì)應(yīng)的部分協(xié)議方法上面继效。
@protocol SampleApplicationControl
- (void) onInitARDone:(NSError *)error;
- (bool) doInitTrackers;
- (bool) doLoadTrackersData;
- (bool) doStartTrackers;
@end
從圖-1可以看出是由doInitTrackers
的返回值來判斷是否需要去加載tracker數(shù)據(jù)(doLoadTrachersData),最后在onInitARDone方法流程結(jié)束装获。通過這個(gè)就確定了我們的源碼查看順序:
/// doInitTrackers --> doLoadTrackersData --> onInitARDone
那么在ImageTargetsViewController類中瑞信,其流程圖如下:
自此我們的源碼閱讀就告一段落,最后我們將要去實(shí)現(xiàn)開始提到的目的穴豫!
收尾
讀到這里凡简,自定義的數(shù)據(jù)集的切入點(diǎn)在方法doLoadTrackersData
方法中,并且要doInitTrackers方法返回YES精肃。如果沒有執(zhí)行“安裝步驟”中的第5步的話秤涩,現(xiàn)在可以去做了!做完之后添加如下代碼到工程中:
///ImageTargetsViewController.mm -> doLoadTrackersData
dataSetCustom = [self loadObjectTrackerDataSet:@"WillDB_Device.xml"];/// 這個(gè)dataset為你自己的名字
if (dataSetCustom == NULL) {
return NO;
}
if (! [self activateDataSet:dataSetCustom]) {
return NO;
}
運(yùn)行程序司抱,掃描對(duì)應(yīng)的圖片發(fā)現(xiàn)是能夠成功掃描對(duì)應(yīng)的圖片的筐眷。但是系統(tǒng)的圖片能出來一個(gè)“茶壺”,而我們自己的圖片上面什么也沒有呢习柠?
在這里我不想又去使用這個(gè)煩人的“茶壺”O(jiān)penGL模型了匀谣,我選擇的是一個(gè)皮卡丘的原型(在文末我會(huì)將改造過的demo傳到Github可以去那里下載這個(gè)原型)照棋。
模型obj到opengl數(shù)據(jù)的轉(zhuǎn)換
就目前我知道的來說,在Xcode中無法使用.obj的模型數(shù)據(jù)的武翎。我在網(wǎng)上找到了一個(gè)工具obj2opengl烈炭,具體的使用方法見這里,我還是大體來說一下使用步驟:
將下載好的文件放到特定的文件夾中后频,然后把對(duì)應(yīng)的obj文件和它放在一起梳庆,使用終端進(jìn)入obj2opengl.pl文件所在文件夾之后,輸入如下命令:
./obj2opengl.pl yourobjfilename
成功后卑惜,它會(huì)生成一個(gè)頭文件膏执,這就是通過obj文件生成的紋理坐標(biāo)代碼,在該頭文件中有3個(gè)數(shù)組露久,這三個(gè)數(shù)組分別對(duì)應(yīng)著xxxVerts [], xxxNormals [], xxxTexCoords []更米,和一個(gè)xxxxNumVerts(xxx為你的obj文件名字),具體使用說明毫痕。
模型替換
通過前面的源碼閱讀征峦,我們知道ImageTargetsViewController類是用來加載tracker數(shù)據(jù)的,SampleAppRenderer類是做渲染相關(guān)的數(shù)據(jù)消请,SampleApplicationSession類是使用tracker數(shù)據(jù)并控制AR栏笆,最后只剩下一個(gè)ImageTargetsEAGLView類。在該類中會(huì)做如下操作:
1臊泰、在textureFilenames數(shù)組中蛉加,添加一個(gè)新的紋理。這個(gè)自己選擇一個(gè)紋理圖片缸逃,我是隨便選的针饥,所以看起來會(huì)很丑。
-
2需频、在ImageTargetsEAGLView類的頭文件中添加一個(gè)私有成員變量pikachuModel丁眼。用它來代替例子中的buildingModel。并在SampleApplication3DModel.h文件中添加方法
pikachu_ReWrite
昭殉,并在SampleApplication3DModel.m文件中添加如下代碼:- (void)pikachu_ReWrite{ #if kUse3DModel == 1 _numVertices = XY_PikachuMNumVerts; _vertices = XY_PikachuMVerts; _normals = XY_PikachuMNormals; _texCoords = XY_PikachuMTexCoords; #endif }
-
3苞七、在ImageTargetsEAGLView.mm的方法
loadBuildingsModel
中添加如下代碼:pikachuModel = [[SampleApplication3DModel alloc] init]; [pikachuModel pikachu_ReWrite];
4、將ImageTargetsEAGLView.mm文件中所有的buildingModel替換為pikachuModel饲化,最后調(diào)節(jié)一下變量kObjectScaleOffTargetTracking的值莽鸭,這個(gè)值調(diào)節(jié)由自己決定。
上述的修改靈感大多是來自頭文件Teapot.h吃靠,但是我們使用obj2opengl時(shí)生成的文件中并沒有Teapot.h中teapotIndices對(duì)應(yīng)的數(shù)組硫眨。相反多了一個(gè)無符號(hào)的整形變量xxxNumVerts
,所以除了上訴的方法以外還有另外一種方法,具體的代碼修改如下:
/// ImageTargetsEAGLView.mm -> renderFrameWithState方法中
glVertexPointer(3, GL_FLOAT, 0, XY_PikachuMVerts);
glNormalPointer(GL_FLOAT, 0, XY_PikachuMNormals);
glTexCoordPointer(2, GL_FLOAT, 0, XY_PikachuMTexCoords);
glDrawArrays(GL_TRIANGLES, 0, XY_PikachuMNumVerts);
在使用這個(gè)方法時(shí)其(用obj2opengl生成的頭文件的數(shù)組中的)數(shù)值比例是需要修改的礁阁,而且還需要對(duì)模型進(jìn)行翻轉(zhuǎn)巧号。這個(gè)方法具體見How do I replace the Teapot和Replace the teapot model。
在修改源碼時(shí)主要就是在修改方法renderFrameWithState
姥闭,它在介紹ImageTargetsEAGLView類時(shí)在文件的最開頭就有提到丹鸿,它是在每捕捉到一次tracker之后就會(huì)運(yùn)行一次。
到這里一個(gè)很基本的Vuforia集成棚品,源碼的解讀以及自定義tracker和模型就算完成了靠欢,最后附上Demo地址。