一 简介
纹理贴图是人们对物体真实感的新需求之一。纹理映射的概念由Catmull提出,用来表示以像素坐标(u,v)表示的纹理空间和以参数坐标(x,y,z)表示的三维空间之间的映射关系,随后,Blinn对纹理映射的概念进行了改进,使得纹理映射结果更加自然。随后Bier等提出了两步映射法,实验过程中引入中介曲面并将其作为中间映射媒介,从而构建从三维模型到纹理图像的映射关系。传统的纹理贴图大多是通过计算机来渲染添加到三维模型中,并不是物体在真实世界中所呈现的样子。
随后,所研究的纹理贴图主要是使用拍摄得到的彩色照片作为纹理图像,并通过重建点云与纹理图像的映射关系,将纹理信息赋予给三维点云,从而构建出具有真实纹理的三维数据。
二 文件结构
2.1 obj
OBJ文件是Wavefront公司为它的一套基于工作站的3D建模和动画软件"Advanced Visualizer"开发的一种文件格式,可以通过Maya、CloudCompare等软件打开。OBJ文件可以是文本格式,因此,可以通过写字板打开进行查看和编辑。
OBJ格式支持多边形、直线、表面和自由形态曲线。多边形和直线可以通过顶点进行描述,曲线支持B样条、贝塞尔曲线以及泰勒方程表征的曲线。
OBJ是一种3D模型文件,其不包含材质特性和纹理图片路径,且其通常主要支持多边形模型。
以下为一个简单的正方体模型的样例:
#The units used in this file are meters.
v -1 -1 -1
v 1 -1 -1
v 1 1 -1
v -1 1 -1
v -1 -1 1
v 1 -1 1
v 1 1 1
v -1 1 1
f 1 2 3 4
f 1 2 6 5
f 2 3 7 6
f 3 4 8 7
f 4 1 5 8
f 5 6 7 8
其中,v代表顶点坐标(x,y,z),f代表面片四边形,后面的数字代表坐标索引,注意,obj文件的索引从1开始。
结果展示:
这个时候,我们发现这个正方体无法区分面片,这个时候我们可以给其加上法向量,添加法向量有两种方式:
(1)给顶点添加法向量
#The units used in this file are meters.
v -1 -1 -1
v 1 -1 -1
v 1 1 -1
v -1 1 -1
v -1 -1 1
v 1 -1 1
v 1 1 1
v -1 1 1
vn -0.33405748 -0.66648537 0.66648537
vn 0.81489873 -0.40984145 0.40984145
vn 0.33405748 0.66648537 0.66648537
vn -0.81489873 0.40984145 0.40984145
vn -0.66648537 -0.33405748 0.66648537
vn 0.40984145 -0.81489873 0.40984145
vn 0.66648537 0.33405748 0.66648537
vn -0.40984145 0.81489873 0.40984145
f 1//1 2//2 3//3 4//4
f 1//1 2//2 6//6 5//5
f 2//2 3//3 7//7 6//6
f 3//3 4//4 8//8 7//7
f 4//4 1//1 5//5 8//8
f 5//5 6//6 7//7 8//8
其中,vn表示各顶点的法向量,而f 1//1 2//2 3//3 4//4中的1//1表示顶点索引/(纹理坐标索引)/法向量索引,由于无纹理坐标,故中间可不填。
结果展示:
(2)给面片添加法向量
#The units used in this file are meters.
v -1 -1 -1
v 1 -1 -1
v 1 1 -1
v -1 1 -1
v -1 -1 1
v 1 -1 1
v 1 1 1
v -1 1 1
vn 0 0 -1
vn 0 -1 0
vn 1 0 0
vn 0 1 0
vn -1 0 0
vn 0 0 1
f 1//1 2//1 3//3 4//1
f 1//2 2//2 6//2 5//2
f 2//3 3//3 7//3 6//3
f 3//4 4//4 8//4 7//4
f 4//5 1//5 5//5 8//5
f 5//6 6//6 7//6 8//6
由上可发现,法向量个数与面片个数一致,而同一面片的法向量一致。
结果展示:
这个时候,我们就可以看到棱角分明了。
目前仅仅是创建好了3D模型,如何进行模型贴图,便需要往下看了。
2.2 mtl
mtl 文件(Material Library File)是材质库文件,与obj文件配合,把纹理颜色渲染到obj模型上。
以下用一个简单例子进行说明:
obj文件:
#The units used in this file are meters.
#applied to each of its faces.
mtllib show.mtl
v -1 -1 -1
v 1 -1 -1
v 1 1 -1
v -1 1 -1
v -1 -1 1
v 1 -1 1
v 1 1 1
v -1 1 1
vn 0 0 -1
vn 0 -1 0
vn 1 0 0
vn 0 1 0
vn -1 0 0
vn 0 0 1
usemtl my_mtl_01
g pCube1
f 1//1 2//1 3//3 4//1
f 1//2 2//2 6//2 5//2
f 2//3 3//3 7//3 6//3
f 3//4 4//4 8//4 7//4
f 4//5 1//5 5//5 8//5
f 5//6 6//6 7//6 8//6
其中增加了mtl文件路径,如mtllib show.mtl表明使用show.mtl材质库文件,而后面的usemtl my_mtl_01,表示使用材质库文件show.mtl中的材质组my_mtl_01。
show.mtl文件:
newmtl my_mtl_01
Ka 1 1 1
Kd 1 1 1
d 1
Ns 0
illum 1
其中,newmtl表示定义新的材质组,后面为材质组名称;
Ka为环境反射,如Ka 1 1 1表示用r=1,g=1,b=1的颜色值作为环境光,三个参数的取值范围为[0,1];
Kd为漫反射,Kd 1 1 1同样表示用r=1,g=1,b=1的颜色值。
d为渐隐指数,可理解为透明度,默认为1,表示不透明。取值范围为[0,1];
Ns为反射指数,取值范围为[0,1000];
illum为照明度,取值范围为[0.10]。
上面文件的展示:
接着把Kd改为红色
newmtl my_mtl_01
Ka 1 1 1
Kd 1 0 0
d 1
Ns 0
illum 1
然后把透明度d改为0.5
newmtl my_mtl_01
Ka 1 1 1
Kd 1 0 0
d 0.5
Ns 0
illum 1
2.3 纹理图片
纹理图片输入一张传感器采集的RGB图像即可,文件周骓可为jpg、png。
三 效果展示
show.obj:
#The units used in this file are meters.
#applied to each of its faces.
mtllib show.mtl
v -1 -1 -1
v 1 -1 -1
v 1 1 -1
v -1 1 -1
v -1 -1 1
v 1 -1 1
v 1 1 1
v -1 1 1
vt 0 0
vt 0 1
vt 1 0
vt 1 1
vn 0 0 -1
vn 0 -1 0
vn 1 0 0
vn 0 1 0
vn -1 0 0
vn 0 0 1
usemtl my_mtl_01
g pCube1
f 1/1/1 2/2/1 3/4/3 4/3/1
f 1/1/2 2/2/2 6/4/2 5/3/2
f 2/1/3 3/2/3 7/4/3 6/3/3
f 3/1/4 4/2/4 8/4/4 7/3/4
f 4/1/5 1/2/5 5/4/5 8/3/5
f 5/1/6 6/2/6 7/4/6 8/3/6
其中,vt为纹理坐标,通常取值范围为[0,1];
show.mtl:
newmtl my_mtl_01
Ka 1 1 1
Kd 1 1 1
d 1
Ns 0
illum 1
map_Kd mosquito.jpg
其中,map_Kd mosquito.jpg表示指定纹理文件路径。
mosquito.jpg:
模型纹理贴图结果:
四 代码展示
以下为obj文件的保存函数:
//此处输入的点云坐标携带法向量,且UV坐标与顶点坐标一一对应
void saveObj(const pcl::PointCloud<pcl::PointNormal>& pCloud,
const std::vector<texturePos>& pUV,
const pcl::PolygonMesh& triangles,
const Eigen::Vector3d& offset,
const std::string& outFolder,
const std::string& objFile,
const std::string& mtlFile,
const std::string& imgFile)
{
{
std::ofstream outfile(outFolder+"\\"+objFile);
outfile.setf(std::ios::fixed, std::ios::floatfield);
outfile.precision(6);
outfile << "mtllib " << mtlFile << ".mtl" << std::endl;
//header
outfile << "#The units used in this file are meters." << std::endl;
//输出顶点v
for (auto& pt : pCloud)
{
outfile << "v" << " " << pt.x + offset[0] << " " << pt.y + offset[1] << " " << pt.z + offset[2] << std::endl;
}
//输出顶点纹理坐标vt
for (auto& pt : pUV)
{
outfile << "vt" << " " << pt.u << " " << pt.v << std::endl;
}
outfile << "usemtl " << mtlFile << std::endl;
//输出顶点法向量vn
for (auto& pt : pCloud)
{
outfile << "vn" << " " << pt.normal_x << " " << pt.normal_y << " " << pt.normal_z << std::endl;
}
//输出面f
outfile << "g pCube1" << std::endl;
for (auto& f : triangles.polygons)
{
//outfile << "f" << " " << f.vertices[0] + 1 << " " << f.vertices[1] + 1 << " " << f.vertices[2] + 1 << std::endl;
outfile << "f" << " " << f.vertices[0] + 1 << "/" << f.vertices[0] + 1 << "/"<< f.vertices[1] + 1 <<" "
<< f.vertices[1] + 1 << "/" << f.vertices[1] + 1 <<"/"<< f.vertices[1] + 1 << " "
<< f.vertices[2] + 1 << "/" << f.vertices[2] + 1 <<"/"<< f.vertices[1] + 1 << std::endl;
}
outfile.close();
}
{
std::string tempFile =outFolder+"\\"+ mtlFile + ".mtl";
std::ofstream outfile(tempFile);
outfile.setf(std::ios::fixed, std::ios::floatfield);
outfile.precision(6);
outfile << "newmtl my_mtl" << std::endl
<< "Ka 1 1 1" << std::endl
<< "Kd 1 1 1" << std::endl
<< "d 1" << std::endl
<< "Ns 0" << std::endl
<< "illum 1" << std::endl
<< "map_Kd " << imgFile << std::endl;
outfile.close();
}
}