登山第五梯:obj、mtl、紋理圖片——復雜的關系

一 簡介

紋理貼圖是人們對物體真實感的新需求之一。紋理映射的概念由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();
}
}

赞(0)