1、背景
需求:通过ARKit,让用户拍摄房间时显示挑选的家具或其它模型。
要求:需要感知房间的空间大小,让家具物体贴近现实。
2、功能实现
由于公司不是用通用的3D模型obj、dae或者苹果官方的scn文件。
之前对于3D建模知识完全不懂,所以只能摸索有没有更底层的方法。
后面看例子,发现可以用SCNGeometrySource和SCNGeometryElement实现。
代码如下:
typedef struct {
float x, y, z; // position
float nx, ny, nz; // normal
float s, t; // texture coordinates
} SourceVertex;
- (SCNGeometry *)geometryWithSourceVertex:(SourceVertex *)vertexcs faces:(int *)faces vertexNum:(int)vertexNum faceNum:(int)faceNum {
NSData *data = [NSData dataWithBytes:vertexcs length:sizeof(SourceVertex)*vertexNum];
SCNGeometrySource *vertexSource, *normalSource, *tcoordSource;
vertexSource = [SCNGeometrySource geometrySourceWithData:data
semantic:SCNGeometrySourceSemanticVertex
vectorCount:vertexNum
floatComponents:YES
componentsPerVector:3 // x, y, z
bytesPerComponent:sizeof(float)
dataOffset:offsetof(SourceVertex, x)
dataStride:sizeof(SourceVertex)];
normalSource = [SCNGeometrySource geometrySourceWithData:data
semantic:SCNGeometrySourceSemanticNormal
vectorCount:vertexNum
floatComponents:YES
componentsPerVector:3 // nx, ny, nz
bytesPerComponent:sizeof(float)
dataOffset:offsetof(SourceVertex, nx)
dataStride:sizeof(SourceVertex)];
tcoordSource = [SCNGeometrySource geometrySourceWithData:data
semantic:SCNGeometrySourceSemanticTexcoord
vectorCount:vertexNum
floatComponents:YES
componentsPerVector:2 // s, t
bytesPerComponent:sizeof(float)
dataOffset:offsetof(SourceVertex, s)
dataStride:sizeof(SourceVertex)];
NSData *eleData = [NSData dataWithBytes:faces length:faceNum*3*sizeof(int)];
SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:eleData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:faceNum bytesPerIndex:sizeof(int)];
SCNGeometry * geometry = [SCNGeometry geometryWithSources:@[vertexSource,normalSource,tcoordSource]
elements:@[element]];
return geometry;
}
3. 问题
加载完成后,纹理图片显示不正确,会有扭曲的情况。
由于例子比较少,网上的相关问题也少,搞到我花了很多时间去研究。开始以为是API方法问题,或者数据取值导致,但对照网上的加载显示六方形的方法,检查不出有问题。
后来只能尝试把模型转成obj文件格式,通过ModelIO去加载obj,测试后完全没问题,那只能从数据方面入手了。
不过尝试过,直接读obj的顶点、纹理、法向量和索引,加载出来的图形也是有问题。但通过ModelIO加载出来的数据,再用SCNGeometrySource加载也没问题。
这样比较清晰了,数据要加工处理过才行。
网上搜索到一个从obj加载数据,然后进行合面操作,网址是:https://blog.csdn.net/qinyuanpei/article/details/49991607 。加载出来显示没问题,但有性能问题,因为算法要对面索引
进行n*n的循环,当面索引数据大的时候,加载一个面要1分钟以上。
4. 解决方法
通过搜索加载obj文件数据的方案发现,纹理显示不正确,是因为顶点、纹理数据和面索引对应不上,后面想到直接根据面索引,重排顶点和纹理数据就可以解决这个问题了。
代码如下:
for (int i = 0; i < facesNum; i++) {
// 第一个顶点
int index = faces[i*9] - 1;
vertexcs[i*3].x = point[index*3];
vertexcs[i*3].y = point[index*3+1];
vertexcs[i*3].z = point[index*3+2];
// 纹理
index = faces[i*9+1] - 1;
vertexcs[i*3].s = tex[index*2];
vertexcs[i*3].t = tex[index*2+1];
// 法向量
index = faces[i*9+2] - 1;
vertexcs[i*3].nx = normal[index*3];
vertexcs[i*3].ny = normal[index*3+1];
vertexcs[i*3].nz = normal[index*3+2];
// 第二个顶点
index = faces[i*9+3] - 1;
vertexcs[i*3+1].x = point[index*3];
vertexcs[i*3+1].y = point[index*3+1];
vertexcs[i*3+1].z = point[index*3+2];
// 纹理
index = faces[i*9+4] - 1;
vertexcs[i*3+1].s = tex[index*2];
vertexcs[i*3+1].t = tex[index*2+1];
// 法向量
index = faces[i*9+5] - 1;
vertexcs[i*3+1].nx = normal[index*3];
vertexcs[i*3+1].ny = normal[index*3+1];
vertexcs[i*3+1].nz = normal[index*3+2];
// 第三个顶点
index = faces[i*9+6] - 1;
vertexcs[i*3+2].x = point[index*3];
vertexcs[i*3+2].y = point[index*3+1];
vertexcs[i*3+2].z = point[index*3+2];
// 纹理
index = faces[i*9+7] - 1;
vertexcs[i*3+2].s = tex[index*2];
vertexcs[i*3+2].t = tex[index*2+1];
// 法向量
index = faces[i*9+8] - 1;
vertexcs[i*3+2].nx = normal[index*3];
vertexcs[i*3+2].ny = normal[index*3+1];
vertexcs[i*3+2].nz = normal[index*3+2];
triangleFace[i*3] = i*3;
triangleFace[i*3+1] = i*3+1;
triangleFace[i*3+2] = i*3+2;
}