您的位置:首页 > 移动开发 > IOS开发

GLES2 Graphic Engine Practice(五)iOS平台集成,SampleBrowser,GLES拾取

2013-09-17 23:05 197 查看
关于框架,这是最后一篇了。主要讲iOS平台的集成,以及最后成熟的框架的结构。
1. iOS平台集成
本来只想做完Windows和Android就结束了,不过正好自己笔记本丢了,换了个MacBook本本,就趁这个时机把iOS的框架搞定吧。之前Android上的基础设施基本都弄妥了,iOS的驱动部分差不多一样的处理。
第1)步,使用xCode建立一个iOS上的GLES应用程序。在iTunes或是Apple的developer网站上可以找到xCode的下载链接,需要注意的是自己的iMac(MacBook)的OS版本,如果版本在10.7以下的话,最好升级一下系统,不然装不了高版本的xCode,因为旧的xCode不大好找,对iOS开发的支持也不好。
我用的MacBook版本为10.7.5,xCode版本为4.6.2。在建立工程模板里选iOS->Application->OpenGLGame即可创建出一个简单不过包括所有GLES绘图功能的iOS应用程序。
第2)步,修改模板工程,嵌入框架启动与通信函数。
模板工程只绘制了一个立方体,但是基本OpnGLES2的初始化已经做好了。模板中需要修改的基本都在一个名为ViewController.m的objC的源文件里,OpenGLES相关的操作基本都集中在这里。我需要把之前开发完的图形引擎嫁接到这里。图形引擎是C++写的,objC我基本也不会,不过还好Apple提供了objC和C++混写的编译环境,我需要做的就只是把文件ViewController.m的后缀m改为mm,通知编译器这是objC和C++混写的。然后引用引擎的Application通信的函数所在的C++头文件,这里使用include命令就可以,而非import,主要有三个函数要嫁接过来,分别是
ZTIOSgles2App_setupGraphics,ZTIOSgles2App_renderFrame,ZTIOSgles2App_msgHandle,用来完成引擎和绘图数据初始化,数据更新于渲染,以及屏幕touch等事件的处理。这三个函数调用的代码基本都是平台无关的。平台相关的部分下一小节叙述。
第3)步,完成平台相关的基础设施。

首先是Input,思路和Android上的一样,捕捉Touch事件,网上查了一下,最后在ViewController.mm中实现了

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
这三个属于ViewController的接口。有一个蛮恼人的是绘图区大小为960x640的Retina屏,对应Input区取到的大小(TouchXY坐标范围)竟是480x320。困扰了一会儿加上了分别对应的处理。

然后Timer基本就用了和Android处理一样的代码,就是让iOS也使用sys/time.h中的时间查询api。

最后处理文件读取,目标是使用标准的C++库函数。在xCode中把资源文件加入工程(左侧的工程视图中右击工程,加入文件到…),在执行iOS模拟器或是上真机走的时候,xCode会把资源一起打入执行程序(就是特殊的文件夹)。这时使用文件名就可以找到此文件,使用std的Iostream或C中FILE相关函数即可打开文件。

顺便说一下,为了代码统一以及让Android平台开发效率提高,文件读取处多加了一层处理。原因为Android的资源会以压缩格式存在apk中无法直接读到。Android应用程序读取文件资源有几种办法:a)使用Java的api(需要JNI),b)zlib读取apk数据到内存,c)把文件数据预处理写到代码常量中(本人之前一直用的办法),d)把文件解压后写到系统文件目录下,前面三种都是把文件全载入内存中再处理,但是个人习惯的处理方式是边读文件边处理,而且iOS和Windows上都是这么处理,所以现在需要实现d)方法。具体就是使用assetManager读取apk里asset文件解压后写到Android的Application的data目录(/data/data/files/YOURAPP_PACKAGE_NAME/)下,然后再读取文件,这样文件IO也做到了平台统一了。PS:需要注意文件名区分大小写。

其他发布到iPhone(为此生活费里少了99$>_<)还遇到了个问题,载入png图片时Log打出无法辨认的chunk格式CgBI,无法读取,奇怪的是模拟器上运行不会有此问题。后来的调查结果不是pngLib的编码问题,而是xCode把图片导入真机上时会自己压缩一下,可能objC的iOS开发库有对应,我用纯C++加上pngLib所以出现这桩问题。最后在buildsettings把CompressPng
Files设为No就可以让png保持原格式顺利载入了。

然后还是为了开发的效率和管理上的合理性,整理一下代码目录。之前三个工作区出了平台Driver外引擎代码是重复的,有地方修改了需要手动代码同步,为了避免麻烦,希望的是硬盘上引擎代码只有一份,那么VisualStudio,eclipse和xCode默认的文件组织结构就需要修改了。VisualStudio和xCode上做的就是利用IDE把文件从项目中排除,在磁盘上移到合适的文件夹下再重新导进来。Android的话在变动了源文件所在文件夹后,在Android.mk的LOCAL_C_INCLUDES和LOCAL_SRC_FILES字段中都需要修改相对路径。下图是代码目录整理的示意图,改后感觉要好很多。



另外,再改一下框架的OverView吧,不才所以UML学的不好,凑合一下吧。



2. Sample browser
做samplebrowser不止为了好看,有个实用的目的是如果要换一个Demo开发和执行,可以不用重新编译与重新安装。这样Android上,iPhone上都可以使用单个App启动并切换多个演示程序。



自己拼凑了一个迷你UI组件,然后把之前写的Demo的预览图和描述画到浏览的ScrollPage上。
实现时把原本的App类拆开了,分离为App和AppContent两层,App负责AppContent的生命周期管理(即切换操作),AppContent的实例为各个Demo的逻辑和绘制。程序启动后App创建的第一个AppContent是Browser,之后由Browser进入某个具体Demo,Demo退出后回到Browser继续Demo的浏览。

3. GLES实现拾取
花些篇幅唠叨一下Sample说明,GLES的拾取实现。
OpenGL中实现拾取的话可以使用反馈feedback(selectionbuffer)实现,不过GLES中这个feature并未被采用。这里介绍一项通用的拾取技术。此技术分为两个步骤:构建射线与相交测试。
1)构建射线
射线的起点为相机位置,此射线经过屏幕鼠标(即手指触点)射向屏幕内无穷远。
下面是从工程源码里拿出的三行代码,计算相机的世界坐标和屏幕上Touch的世界坐标。

Vec3 far_point = Vec4(Vec4(ndcPtTouched.x, ndcPtTouched.y, -1, 1) * matMVP_Inv).XYZ();
Vec3 camera_pos = matMV_Inv.GetRowAsVec3(3);
Vec3 ray_dir = far_point - camera_pos;


ndcPtTouched.x,ndcPtTouched.y指的是normalized
device coordinate(规范设备坐标,即变成正方体后的视椎)下Touch的X坐标和Y坐标,取值范围都在[-1,1]之内。
matMVP_Inv是ModelViewProj组合矩阵的逆矩阵。第一行代码意思为在ModelViewProj转换后近平面上触点坐标为(ndcPtTouched.x,ndcPtTouched.y,
-1),通过ModelViewProj的逆矩阵把它转换回世界坐标。
同理matMV_Inv是ModelView矩阵的逆矩阵。第二行代码取matMV_Inv的第4行(GetRowAsVec3去第n行,索引从0开始对应矩阵第1行)。matMV_Inv第4行前三个元素即为相机的世界坐标(想象一下相机位置在相机坐标下总是(0,0,0),所以不知所以但是造就了这么个巧合)。
最后一行比较简单,有了起点,经过一点(触屏点),射线的方向就能确定了。



视锥示意图

2)相交测试

所要做的是射线和基础几何的相交测试。最常用几何的有三种:a)用于构建Mesh的基础,即三角形。b)球体,用于做包围球快速相交测试。c)长方体,即包围盒。
这里就把三角形相交测试代码列下,也是参照别人的代码来的。这个函数做两件事:第一,构建平面方程N*P+ D,把射线起点和无穷远点(很远很远的任意一点)代入查看是否在平面两边。第二,如果在两边说明可能会相交,求交点查看是否在三角形内部。

bool LineTriIntersect( Vec3 Tri[3], const Vec3 &lP0, const Vec3 &lP1, OUT Vec3 &intersectPt, OUT float &tu, OUT float &tv)
{
static Vec3 V1, V2, V3;
static float D;
static Vec3 N;
static float mTemp1, mTemp2;
static Vec3 S, Dr, tP, tQ;

Vec3 &P1 = Tri[0];
Vec3 &P2 = Tri[1];
Vec3 &P3 = Tri[2];

V1 = P2 - P1;
V2 = P3 - P2;
V3 = P3 - P1;

N = V1.CrossProduct(V2);

//D = -(N[0] * P1[0] + N[1] * P1[1] + N[2] * P1[2]); //triangle plane N*P + D = 0
D = -( N.Dot(P1) ); //triangle plane N*P + D = 0

//the two points of line on one side of plane?
mTemp1 = N.Dot(lP0) + D;
mTemp2 = N.Dot(lP1) + D;

if( ( !( mTemp1 >= 0 && mTemp2 < 0 ) ) && ( !( mTemp1 <= 0 && mTemp2 > 0 ) ) )
return false;

//Centroid coordinates of triangle:    t(u, v) = (1 - u - v)*v0 + u*v1 + v*v2
//u>=0, v>=0, u+v<=1
S = lP0 - P1;

Dr = lP1 - lP0;

tP = Dr.CrossProduct(V3);
tQ = S.CrossProduct(V1);

tu = tP.Dot(S) / tP.Dot(V1);
tv = tQ.Dot(Dr) / tP.Dot(V1);

//if (tu < 1.0f && tu > 0.0f && tv < 1.0f && tv > 0.0f  && (1.0f - tv - tu) < 1.0f && (1.0f - tv - tu) > 0.0f)
if (tu > 0.0f && tv > 0.0f && (tv + tu) < 1.0f)
{
intersectPt = Tri[0] * (1.0f - tv - tu) + Tri[1] * tu + Tri[2] * tv;
return true;
}
return false;
}


这里有一个英文的关于拾取实现的问答,可以略作参考

http://stackoverflow.com/questions/2093096/implementing-ray-picking
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: