程 飛
在計(jì)算機(jī)圖形學(xué)中,三維物體表面的紋理是增加真實(shí)感、模擬真實(shí)世界形體的有效手段.當(dāng)前主要采用的表面紋理方法有:①紋理貼圖[1].將一張圖片按映射方法投射到形體表面.常用的商業(yè)軟件(如3dmax)一般采用該方法.該方法速度快,但是在紋理連接處會(huì)有明顯的不連續(xù)現(xiàn)象,不符合形體的物理實(shí)際(如圖1所示).②紋理合成[2].紋理合成最早應(yīng)用于二維圖形,即給出一個(gè)小尺寸的紋理圖,進(jìn)而合成大尺寸的紋理圖(如圖2所示).紋理合成方法可以進(jìn)一步推廣到三維形體.紋理合成主要基于markov隨機(jī)場(chǎng)算法,速度慢、需要采用一定的加速算法.實(shí)驗(yàn)表明,紋理合成對(duì)于圖片的紋理特征有要求,對(duì)于一些特殊的樣圖(如規(guī)則樣圖)不能產(chǎn)生需要的繪制效果.以圖2為例,圖2(a)為樣圖,圖2(b)為合成圖,圖2(a)為規(guī)則的磚墻圖案,圖2(b)中磚塊出現(xiàn)了明顯的混亂和錯(cuò)位.
圖1 紋理貼圖
圖2 紋理合成
在大規(guī)模游戲場(chǎng)景繪制中,需要快速繪制具有真實(shí)感三維形體.采用上述兩種方法繪制三維表面紋理難以滿足真實(shí)感和實(shí)時(shí)性的要求.因而,本文提出一種基于紋理場(chǎng)[3]的三維紋理繪制方法.主要思想為:①由數(shù)學(xué)方法或者樣圖構(gòu)建紋理場(chǎng),三維紋理場(chǎng)以函數(shù)
color(x,y,z)=F(x,y,z)的形式給出,場(chǎng)中每一坐標(biāo)點(diǎn)對(duì)應(yīng)一個(gè)顏色值.將物體置于紋理場(chǎng)中,由所給公式計(jì)算表面網(wǎng)格點(diǎn)的顏色值.計(jì)算機(jī)圖形學(xué)中,三維形體一般由點(diǎn)云模型給出,點(diǎn)云模型通過一定的算法形成三角網(wǎng)格,從而表示形體的外輪廓.對(duì)于三角網(wǎng)格涂色,可以得到三維物體的表面紋理.為簡化問題,這里僅以圖形學(xué)常用的Bunny模型為例,對(duì)于規(guī)則紋理場(chǎng)和不規(guī)則紋理場(chǎng)中三維形體表面紋理的繪制方法予以討論.本文實(shí)例均以底層圖形庫OpenGL為工具開發(fā).具體算法描述如下:
step1.構(gòu)建紋理場(chǎng).
step2.在OpenGL中,導(dǎo)入Bunny表面點(diǎn)的點(diǎn)云模型.
step3.利用導(dǎo)入的點(diǎn)云數(shù)據(jù),按一定的算法得到三角線框模型.
step4.對(duì)于每一個(gè)輪廓點(diǎn)計(jì)算其在紋理場(chǎng)中的對(duì)應(yīng)的顏色.
step5.繪制每個(gè)三角面片,進(jìn)而繪制出整個(gè)圖形.
這里僅以黑白格紋理為例,描述具體算法.
構(gòu)建紋理場(chǎng).將黑白格紋理場(chǎng)理解為多個(gè)邊長為1的黑色與白色正方體間隔疊加而成.如圖3(b)所示.這里紋理場(chǎng)大小設(shè)置為20*20*20;將其放置在第一卦限,場(chǎng)中任意一點(diǎn)坐標(biāo)為(x,y,z),對(duì)于x,y,z三個(gè)數(shù)下取整得到(x0,y0,z0),考慮x0,y0,z0的奇偶性,如果(x0,y0,z0)組合為(偶偶奇)(奇奇奇)(奇偶偶)(偶奇偶),則以該點(diǎn)為起點(diǎn),向三個(gè)坐標(biāo)軸正方向所作邊長為1的正方體所確定的空間的顏色為黑色,反之為白色.
導(dǎo)入Bunny點(diǎn)云模型(ply格式),這里采用meshlab軟件將其轉(zhuǎn)化為obj格式,則形體由三角面片表示,每個(gè)面片包括三個(gè)頂點(diǎn).建立數(shù)組index[],以標(biāo)記不同面片.
建立光照等模型后,將形體放置在紋理場(chǎng)中,即將形體上的各個(gè)表面輪廓點(diǎn)映射到紋理場(chǎng)中.以Bunny為例,其表面點(diǎn)三維坐標(biāo)的取值范圍為[-0.2<x<0,0<y<0.2,-0.2<z<0],需要對(duì)于表面點(diǎn)數(shù)據(jù)進(jìn)行規(guī)格化,并根據(jù)實(shí)際繪圖要求確定縮放比例.這里將其變換到[0<x<20,0<y<20,0<z<20].關(guān)鍵代碼如下:
glBegin(GL_TRIANGLES); //開始繪制物體(三角形)
{for(int j=0;j<P_Object->Num_Faces;j++)//遍歷所有的面,開始繪制
{for(int index=0;index<1;index++) //遍歷面上的頂點(diǎn)
{int vertIndex=P_Object->P_Faces[j].vertIndex[index];//取得每個(gè)面頂點(diǎn)的索引值
int cccc=100;
int xx=int(floor((P_Object->P_Verts[vertIndex].x+0.2)*cccc));//規(guī)格化以及縮放
int yy=int(floor(P_Object->P_Verts[vertIndex].y*cccc));
int zz=int(floor((P_Object->P_Verts[vertIndex].z+0.2)*cccc));
if(((xx%2==1)&&(yy%2==1)&&(zz%2==0))||((xx%2==0)&&(yy%2==0)&&(zz%2==0))||((xx%2==1)&&(yy%2==0)&&(zz%2==1))|| ((xx%2==0)&&(yy%2==1)&&(zz%2==1)) ) //映射紋理場(chǎng)
glColor3f(1,1,1);//位于黑格位置
elseglColor3f(0,0,0);}//位于白格位置
for(index=0;index<3;index++) //遍歷面上的頂點(diǎn){int vertIndex=P_Object->P_Faces[j].vertIndex[index];//取得每個(gè)面頂點(diǎn)的索引值
glNormal3f (P_Object->P_Normals [vertIndex].x, P_Object->P_Normals[vertIndex].y,P_Object->P_Normals[vertIndex].z);//設(shè)置法向量
glVertex3f(P_Object->P_Verts[vertIndex].x,P_Object->P_Verts[vertIndex].y,P_Object->P_Verts[vertIndex].z);//繪制頂點(diǎn)
}glEnd();}
圖3 規(guī)則紋理場(chǎng)繪制效果
繪制效果如圖3(c)所示,觀察圖形可見,圖形相當(dāng)于從紋理場(chǎng)中蝕刻得到,不會(huì)出現(xiàn)類似圖1的視覺上紋理不連續(xù)現(xiàn)象.
這里以大理石紋理為例予以說明.
圖4 不規(guī)則紋理場(chǎng)繪制效果
首先給出樣圖,如圖4(a)所示.將其導(dǎo)入為紋理.關(guān)鍵代碼如下:
void BuildTexture(char*szPathName)//獲取圖片,*szPathName為圖片地址
{…………………………
lWidthPixels=MulDiv(lWidth,GetDeviceCaps(hdcTemp,LOGPIXELSX),2540);//取得IPicture寬度(轉(zhuǎn)換為Pixels格式)
lHeightPixels =MulDiv(lHeight,GetDeviceCaps(hdcTemp,LOGPIXELSY),2540);//取得IPicture高度(轉(zhuǎn)換為Pixels格式)hbmpTemp=CreateDIBSection(hdcTemp,&bi,DIB_RGB_COLORS,(void**)&pBits,0,0); //在位圖上繪制IPicture
pPicture->Render(hdcTemp,0,0,lWidthPixels,lHeightPixels,0,lHeight,lWidth,-lHeight,0);tupianchang=lWidthPixels;//圖片長tupiankuan=lHeightPixels;//圖片寬
for(long i=0;i<lWidthPixels;i++)//循環(huán)遍歷所有的像素
{for(long j=0;j< lHeightPixels;j++)
{xiangsu[i][j][0]=int(pPixel[0]);//將圖片的R值存入xiangsu[i][j][0]
xiangsu[i][j][1]=int(pPixel[1]);//將圖片的 G 值存入 xiangsu[i][j][1]
xiangsu[i][j][2]=int(pPixel[2]);}//將圖片的B值存入xiangsu[i][j][2]
建立紋理場(chǎng).現(xiàn)實(shí)世界中,一定大小的大理石切塊的某一方向的各個(gè)切片圖案基本相同,如圖4(b)所示.可以將紋理場(chǎng)理解為圖片紋理沿一定方向的延伸.圖像的像素點(diǎn)按照一定的映射關(guān)系映射到三維空間,得到紋理場(chǎng).關(guān)鍵代碼如下:
void huiwenlichang()
{for(int i=1;i< tupianchang-1 ;i++) //循環(huán)遍歷所有的像素
{for(int j=1;j<tupiankuan-1;j++)
{glColor3ub(xiangsu[i][j][0],xiangsu[i][j][1],xiangsu[i][j][2]);
glTranslatef(i*0.0002,j*0.0002,0);DrawCube(0.0002,0.0002,0.15);}}}
形體表面的三維紋理計(jì)算.將形體放置在紋理場(chǎng)中,首先計(jì)算各個(gè)表面輪廓點(diǎn)在紋理場(chǎng)中的對(duì)應(yīng)位置,找到該點(diǎn)的紋理場(chǎng)數(shù)據(jù),作為圖形繪制的顏色值.本例中,導(dǎo)入的圖片大小為768*1 024,考慮Bunny模型的實(shí)際大小,以int(floor((P)/0.2*tupianchang))的形式進(jìn)行縮放,建立形體和紋理場(chǎng)的對(duì)應(yīng)關(guān)系.然后再以三角面片的形式繪制圖形.
glBegin(GL_TRIANGLES); //繪制三角面片
for(int j=0;j<P_Object->Num_Faces;j++)//遍歷所有的面,
{for(int index=0;index<3;index++) //遍歷面上的頂點(diǎn)
{int vertIndex=P_Object->P_Faces[j].vertIndex[index];//取得面頂點(diǎn)的索引值
x=P_Object->P_Verts[vertIndex].x;
y=P_Object->P_Verts[vertIndex].y;
z=P_Object->P_Verts[vertIndex].z;
int x0=int(floor((x+0.1)/0.2*tupianchang));
int y0=int(floor((y)/0.2*tupiankuan));
glColor3ub(xiangsu[x0][y0][0],xiangsu[x0][y0][1],xiangsu[x0][y0][2]);
glNormal3f (P_Object->P_Normals [vertIndex].x, P_Object->P_Normals[vertIndex].y,P_Object->P_Normals[vertIndex].z);//設(shè)置法向量
glVertex3f(P_Object->P_Verts[vertIndex].x,P_Object->P_Verts[vertIndex].y,
P_Object->P_Verts[vertIndex].z); //繪制頂點(diǎn)
}
}
glEnd();
//結(jié)束繪制
繪制效果如圖4(c)所示.
本質(zhì)上,紋理映射算法需要將樣圖上的點(diǎn)的紋理坐標(biāo)(s,t)與空間點(diǎn)(x,y,z)建立對(duì)應(yīng)關(guān)系.映射關(guān)系是紋理映射算法的關(guān)鍵.紋理映射可以應(yīng)用于以下三種情況:①形體表面輪廓由明確數(shù)學(xué)表達(dá)式給出(主要是二次曲面,如圓柱、圓錐等規(guī)則形體),其表面點(diǎn)和點(diǎn)的紋理坐標(biāo),都易于計(jì)算,可以用for循環(huán)快速實(shí)現(xiàn)紋理繪制.②對(duì)于樣條類曲面(如Nurbs以及Bezier曲面),OpenGL應(yīng)用內(nèi)置evaluator自動(dòng)生成紋理[1],紋理對(duì)應(yīng)關(guān)系依賴曲面的型值點(diǎn)的數(shù)目,紋理尺寸無法控制.③對(duì)于3ds格式表示的形體.3ds格式是本身附加紋理坐標(biāo)的(在3dmax中以u(píng)v坐標(biāo)的形式表示),不需要用戶建立映射關(guān)系.給定樣圖以后,OpenGL可以自動(dòng)生成三維表面紋理,同樣對(duì)于紋理的尺寸無法控制.該方法對(duì)于不具有(u,v)紋理坐標(biāo)非3ds格式的模型難以實(shí)現(xiàn),如本文所用Bunny模型(obj格式).且上面三種方法在紋理結(jié)合處都難以取得較好的繪制效果.而紋理合成算法對(duì)于隨機(jī)圖形有較好的效果,對(duì)于規(guī)則圖形會(huì)產(chǎn)生嚴(yán)重形變,如圖2所示.而且其實(shí)時(shí)性差,難以適用于游戲場(chǎng)景的繪制.
比較而言,紋理場(chǎng)算法具有較好的適應(yīng)性.①對(duì)于obj、3ds、md2等各種格式的三維圖形都可以處理,各種格式的模型導(dǎo)入OpenGL中,都可以取得其表面點(diǎn)的三維坐標(biāo).②紋理場(chǎng)的尺寸可控.對(duì)于規(guī)則紋理場(chǎng),紋理空間由數(shù)學(xué)形式給出,可以調(diào)整參數(shù)改變紋理尺寸.以圖3(b)為例,該空間包含20*20*20個(gè)黑白立方體,可以進(jìn)一步細(xì)分為40*40*40個(gè)立方體.對(duì)于不規(guī)則紋理場(chǎng),采用不同分辨率的圖形作為樣圖,就可以實(shí)現(xiàn)不同分辨率的紋理.③紋理的方向可控.調(diào)整紋理場(chǎng)的方向,紋理場(chǎng)中形體就會(huì)得到不同的紋理.圖5顯示了規(guī)則紋理場(chǎng)中模擬木紋不同方向的Bunny表面紋理.紋理場(chǎng)以多個(gè)同心圓模擬木材紋理場(chǎng).圖5(a)紋理中心位于底部,圖5(b)紋理中心穿過頭部.三種紋理算法的比較如表1所示.
圖5 規(guī)則紋理場(chǎng)中模擬木紋不同方向的Bunny表面紋理
表1 三種紋理算法的比較
實(shí)驗(yàn)表明,OpenGL框架內(nèi)基于函數(shù)生成紋理場(chǎng)的算法比較基于樣圖的算法具有更高的處理速度.本文使用的Bunny具有34 838個(gè)頂點(diǎn),69 452個(gè)面.圖6是基于樣圖的Bunny紋理,樣圖大小1 024*726,在Pent T4200處理器的繪制時(shí)間為8.7 s;而圖5的繪制時(shí)間為2.7 s.究其原因,樣圖的導(dǎo)入需要一定的時(shí)間和空間開銷,其構(gòu)建紋理場(chǎng)的時(shí)間遠(yuǎn)大于基于函數(shù)的紋理場(chǎng)的算法所需要的時(shí)間.觀察圖5和圖6,二者繪制效果并無明顯區(qū)別.
圖6 基于樣圖的Bunny表面紋理
紋理場(chǎng)算法從數(shù)學(xué)函數(shù)或者樣圖出發(fā),構(gòu)建紋理場(chǎng),從紋理場(chǎng)中蝕刻形體,避免了紋理貼圖的接縫及視覺不連續(xù)問題.與紋理合成算法相比較,繪圖具有實(shí)時(shí)性,還可以避免紋理合成中的錯(cuò)位問題.該算法能滿足游戲場(chǎng)景的繪圖需要,是一種構(gòu)建真實(shí)感的三維表面紋理的有效方法.
通化師范學(xué)院學(xué)報(bào)2019年10期