劉賢梅,葛昊天,趙 婭
(東北石油大學(xué) 計(jì)算機(jī)與信息技術(shù)學(xué)院,黑龍江 大慶 163318)
近年來不斷發(fā)展成熟的虛擬現(xiàn)實(shí)技術(shù)(virtual reality),利用計(jì)算機(jī)圖形學(xué)、三維建模、多媒體等多種技術(shù)手段[1],可以在計(jì)算機(jī)上實(shí)現(xiàn)逼真的三維場景的渲染,為模擬石油生產(chǎn)過程中的意外情況,提高工人的突發(fā)情況應(yīng)急處理能力,應(yīng)急仿真系統(tǒng)應(yīng)運(yùn)而生。在模擬石油泄漏事故的應(yīng)急仿真系統(tǒng)中,石油泄漏現(xiàn)象的流動(dòng)效果的模擬具有重大意義。傳統(tǒng)的模擬方式以粒子系統(tǒng)為主,但是使用粒子系統(tǒng)時(shí),隨著粒子數(shù)目的增加,需要的內(nèi)存增加,運(yùn)行速率也變得很慢。
隨著硬件設(shè)備的不斷發(fā)展,GPU(graphics processing unit,圖形處理單元)性能的大幅度提高,由于GPU的可編程著色器的存在,開發(fā)人員可以通過可編程著色器(Shader)[2],對(duì)頂點(diǎn)及像素進(jìn)行靈活的處理,模擬出更多逼真的效果。2006年上海師范大學(xué)蘇蘊(yùn)等人通過渲染流水線的可編程著色器的控制,完成對(duì)于室內(nèi)光的各種形式(點(diǎn)光源、平行光源等)的真實(shí)感模擬[3]。2015年,海軍航空工程學(xué)院的王彥等人通過可編程著色器完成大面積動(dòng)態(tài)海洋紅外視景仿真,提高了水流的真實(shí)感[4]。因此,針對(duì)石油泄漏的流動(dòng)效果,可以采用可編程著色器(Shader)進(jìn)行模擬,不僅能夠模擬出石油流動(dòng)的效果,還不需要消耗很多內(nèi)存,能夠一直保持一個(gè)較快的運(yùn)行速率。
Unity是一個(gè)專業(yè)的游戲引擎,而Unity的著色器(Unity Shader)是對(duì)著色器上層的一個(gè)抽象,它負(fù)責(zé)將輸入的Mesh(網(wǎng)格)以指定的方式與輸入的貼圖或者顏色等組合作用,輸出紋理[5]。文中首先探討了著色器和著色器語言,并對(duì)Unity Shader的文件結(jié)構(gòu)與分類進(jìn)行探討,然后通過Unity Shader控制底層GPU中的可編程著色器,采用紋理切換方法與基于噪聲的方法對(duì)輸入的貼圖進(jìn)行處理,模擬出石油流動(dòng)的效果。
Shader,中文翻譯為“著色器”,其本質(zhì)是作用于渲染流水線上的一段程序,能夠控制渲染流水線的特定階段進(jìn)行計(jì)算,以達(dá)到目標(biāo)效果?,F(xiàn)代可編程渲染流水線的最大特點(diǎn)在于GPU內(nèi)部硬件設(shè)計(jì)具有可編程性,可以通過程序控制GPU內(nèi)部渲染流水線。GPU中的可編程結(jié)構(gòu),包括頂點(diǎn)著色器和片元著色器,即可以針對(duì)頂點(diǎn)和像素進(jìn)行編程[6]。因此,可以在頂點(diǎn)和像素著色器中編寫具有不同功能的程序來處理輸入信息,使整個(gè)處理過程更具靈活性和可控性。
Shader的主流編程語言有HLSL、GLSL、CG。HLSL(high level Shader language)是微軟基于DirectX的語言,只能運(yùn)行在Windows平臺(tái)上[7]。GLSL(OpenGL Shading language),是用在OpenGL中著色編程的語言,也是一門跨平臺(tái)的著色器語言。Cg語言(C for graphics)是為GPU編程設(shè)計(jì)的高級(jí)著色器語言,由NVIDIA公司開發(fā)[8],可作為片段程序鑲嵌在Unity Shader程序中。
1.ShaderLab。
為了避免直接控制底層著色器,Unity提供一層抽象——Unity Shader,此外還提供了一種專門為Unity Shader服務(wù)的語言——ShaderLab。它是一種說明性的語言,使用了一些嵌套在花括號(hào)內(nèi)部的語義,這些語義包含了渲染所需的數(shù)據(jù),包括著色器所需的各種屬性[9]。而Unity會(huì)自動(dòng)將ShaderLab文件編譯成控制GPU中各個(gè)著色器的代碼。
2.ShaderLab文件的結(jié)構(gòu)。
一個(gè)ShaderLab的基礎(chǔ)文件結(jié)構(gòu)如下所示:
Shader”ShaderName”
{
Properties{
//屬性
}
SubShader{
//顯卡A使用的著色器
}
SubShader{
//顯卡B使用的著色器
}
......
Fallback”VertexList”
}
對(duì)各個(gè)部分的釋義如下:
(1)Properties。
Properties語義塊中包含了一系列屬性,并且這些屬性會(huì)出現(xiàn)在材質(zhì)面板中,定義方式通常如下:
_Name("Display Name",type)=defaultValue[{options}]
這些屬性的存在是為了能夠更好地調(diào)整材質(zhì)的屬性,方便在著色器中訪問它們?!癉isplay Name”是出現(xiàn)在材質(zhì)面板上的名字,type是屬性的類型,常用的屬性類型如表1所示,defaultValue是指定的默認(rèn)值。
表1 Properties語義塊支持的屬性類型
(2)SubShader。
一個(gè)Shader有多個(gè)SubShader。一個(gè)SubShader可理解為一個(gè)Shader的渲染方案。即SubShader是為了針對(duì)不同的渲染情況而編寫的。每個(gè)Shader至少1個(gè)SubShader。Unity會(huì)掃描所有的Unity Shader,然后選擇第一個(gè)能夠在目標(biāo)平臺(tái)上運(yùn)行的SubShader。SubShader語義塊中包含的定義如下:
SubShader
{
Pass{ }
}
一個(gè)SubShader是由Pass塊來執(zhí)行的。每個(gè)Pass定義了一次完整的渲染流程。在Pass內(nèi),定義了輸入輸出結(jié)構(gòu)體,在結(jié)構(gòu)體中進(jìn)行光照,法線與空間變換等的計(jì)算。
(3)FallBack。
在各個(gè)SubShader語義塊之后,會(huì)存在一個(gè)FallBack,它會(huì)規(guī)定最低級(jí)的Shader是哪一個(gè)。由于一段Shader有可能在不同等級(jí)的顯卡上運(yùn)行,而顯卡的能力也是參差不齊的。所以,F(xiàn)allBack相當(dāng)于留了一條“后路”,以保證若之前所有的SubShader都無法在當(dāng)前的顯卡上運(yùn)行,則運(yùn)行FallBack所指定的Shader[10]。
(1)表面著色器。
表面著色器(surface Shader)是Unity創(chuàng)造的一種著色器代碼類型。它在本質(zhì)上就是對(duì)頂點(diǎn)/片元著色器的封裝,省去了一些重復(fù)代碼編寫的工作量。也就是Unity在提供一個(gè)表面著色器之后,在背后仍然將它變?yōu)轫旤c(diǎn)片元著色器。但是相比于頂點(diǎn)/片元著色器,表面著色器更加簡潔,效率更高[11]。
(2)頂點(diǎn)/片元著色器。
相比于表面著色器,頂點(diǎn)/片元著色器(vertex/fragment Shader)更加復(fù)雜,但同時(shí)靈活性更高??梢愿鶕?jù)自己當(dāng)前的不同需求,編寫控制頂點(diǎn)著色器和片元著色器的代碼,對(duì)GPU渲染流水線中的可編程部分直接進(jìn)行靈活的控制。
(3)固定函數(shù)著色器。
上述的兩種Shader都使用了可編程管線,而對(duì)于一些比較老舊的,不支持可編程管線著色器的設(shè)備,則需要使用固定函數(shù)著色器(fixed function Shader)來完成渲染[12],這些著色器往往只能完成一些比較簡單的效果,目前很少使用。
首先根據(jù)需求,選擇一種Unity Shader形式,編寫Shader著色程序。然后在工程中新建材質(zhì)球,將上述寫好的著色器程序賦給材質(zhì)球,并給材質(zhì)球設(shè)置貼圖,作為著色器程序的輸入,最后將材質(zhì)球附在物體模型上,就得到了目標(biāo)效果[13]。
針對(duì)石油的泄漏現(xiàn)象,為模擬出石油流動(dòng)時(shí)石油的主體部分,在3dsMax中制作出一個(gè)邊緣光滑,形狀不規(guī)則的物體,作為模擬石油流動(dòng)的主體模型。石油主體模型在3dsMax中的截圖如圖1所示。
圖1 模型截圖
該方法實(shí)現(xiàn)石油流動(dòng)效果采用的是表面著色器(surface Shader),規(guī)定了兩個(gè)偏移速度x,y,速度乘以隨時(shí)間變化的時(shí)間變量得到偏移量。對(duì)圖片進(jìn)行偏移計(jì)算之后的點(diǎn)進(jìn)行采樣,獲得偏移點(diǎn)的顏色值作為返回值輸出,以達(dá)到石油流動(dòng)的效果。流程圖如圖2所示。
圖2 紋理切換流程
在Properties塊中,需要聲明貼圖與貼圖的x,y軸偏移速度。輸入的主紋理貼圖的格式是2D,而偏移速度為范圍在(0,100)之間的數(shù)值。
_WaterTex("水的紋理圖:",2D)="white" {}
_XSpeed("X軸方向的紋理滾動(dòng)速度",:
Range(-10,10))=1
_YSpeed("Y軸方向的紋理滾動(dòng)速度",:
Range(-10,10))=1
Properties塊結(jié)束后,需要在SubShader塊(渲染方案)中,嵌套CG代碼塊。首先,通過結(jié)構(gòu)體Input,將模型頂點(diǎn)的uv值作為surf函數(shù)的輸入。在表面函數(shù)surf中,對(duì)貼圖進(jìn)行計(jì)算。通過內(nèi)置的時(shí)間變量_Time乘以x軸,y軸方向的偏移速度,得到偏移量,通過x軸偏移量與y軸偏移量的組合,得到偏移量scrolledUV,并與頂點(diǎn)的uv值相加得到偏移量,通過tex2D函數(shù),對(duì)貼圖在偏移處進(jìn)行采樣,得到偏移處的顏色值。由于偏移量隨時(shí)間改變而改變,因此,采樣得出的顏色值也會(huì)隨時(shí)間改變,這樣就類似于貼圖在進(jìn)行運(yùn)動(dòng),以實(shí)現(xiàn)流動(dòng)的效果。
fixed xValue=_XSpeed*_Time;
fixed yValue=_YSpeed*_Time;
UV+=fixed2(xValue,yValue);
half4 c=tex2D(_WaterTex,UV);
o.Albedo=c.rgb;
圖3為使用紋理切換方法在RenderDoc中截取的八幀圖像。
圖3 RenderDoc中的截圖(1)
使用上述方法模擬在場景中發(fā)生漏油的效果如圖4所示。
圖4 紋理切換方法模擬效果圖
使用上述方法能夠有效模擬出石油流動(dòng)的效果,但是無法模擬出石油在光照下的反射與折射效果。因此,引入噪聲圖片,對(duì)噪聲進(jìn)行采樣得到的隨機(jī)值,來實(shí)現(xiàn)貼圖的動(dòng)態(tài)扭曲效果,以實(shí)現(xiàn)貼圖的反射折射效
果,提高模擬的真實(shí)感。
在圖形學(xué)中使用噪聲是為了把一些隨機(jī)變量引入到程序中,如火焰、地形、云朵的模擬等等都要使用隨機(jī)變量。在圖形學(xué)中稱這種噪聲為“白噪聲”(功率譜密度在整個(gè)頻域內(nèi)均勻分布的噪聲)[14]。噪聲的基礎(chǔ)來自于隨機(jī)數(shù),若屏幕上的每個(gè)像素點(diǎn)給一個(gè)0~1之間的隨機(jī)數(shù)來表示像素點(diǎn)的亮度[15],就能得到白噪聲紋理。因此可以通過對(duì)白噪聲圖片進(jìn)行采樣以得到隨機(jī)數(shù),完成對(duì)于石油反射折射效果的模擬。
該方法采用的是頂點(diǎn)/片元著色器。在該模擬中,對(duì)噪聲圖進(jìn)行采樣,其中對(duì)采樣點(diǎn)設(shè)置時(shí)間變量,隨著時(shí)間不斷發(fā)生偏移,它提供的R值就會(huì)不停的變化,將R值再與頂點(diǎn)數(shù)據(jù)相加作為采樣點(diǎn),對(duì)主貼圖進(jìn)行采樣,將得到的顏色作為輸出,那么圖片就會(huì)出現(xiàn)動(dòng)態(tài)的扭曲效果,就實(shí)現(xiàn)了想要的石油流動(dòng)效果。
流程圖如圖5所示。
圖5 噪聲模擬流程
在Pass通道嵌套的CG代碼段中,首先對(duì)頂點(diǎn)著色器進(jìn)行控制,在頂點(diǎn)階段主要完成兩個(gè)工作,包括把頂點(diǎn)坐標(biāo)從模型空間轉(zhuǎn)換到裁剪空間,再使用紋理的屬性(_MainTex_ST.xy,_MainTex_ST.zw)對(duì)頂點(diǎn)坐標(biāo)進(jìn)行計(jì)算,得到該階段計(jì)算之后的紋理坐標(biāo)。主要計(jì)算過程如下:
v2f vert(appdata v)
{ o.vertex=UnityObjectToClipPos(v.vertex);
o.uv=TRANSFORM_TEX(v.uv,_MainTex);
}
在片元著色器中,實(shí)現(xiàn)的是噪聲模擬方法的主體。首先設(shè)置時(shí)間變量乘以速度,得到偏移量,偏移量與頂點(diǎn)數(shù)據(jù)相加作為采樣點(diǎn),對(duì)噪聲圖片進(jìn)行采樣,得到rgb值,此時(shí),將R值提出,作為隨機(jī)數(shù),再與頂點(diǎn)數(shù)據(jù)相加,作為采樣點(diǎn)對(duì)主貼圖進(jìn)行采樣,得到對(duì)應(yīng)點(diǎn)的rgb值,作為返回值返回。圖片就會(huì)出現(xiàn)動(dòng)態(tài)的扭曲效果,就實(shí)現(xiàn)了想要的石油流動(dòng)效果。
fixed4 frag(v2f i):SV_Target{
fixed4 noise_col=tex2D(_NoiseTex,i.uv + fixed2(_Time.y*_XSpeed, 0));
fixed uOffset=noise_col.r;
fixed vOffset=noise_col.r;
fixed4 col=tex2D(_MainTex, i.uv+ _Intensity*fixed2(uOffset, vOffset));
UNITY_APPLY_FOG(i.fogCoord, col);}
圖6為使用上述方法模擬時(shí)在RenderDoc中截取的八幀圖像。
圖6 RenderDoc中的截圖(2)
使用上述方法模擬在場景中發(fā)生漏油的效果如圖7所示。
圖7 噪聲方法模擬效果圖
利用Unity Shader技術(shù),模擬出石油的流動(dòng)效果。首先對(duì)著色器進(jìn)行簡單介紹,然后針對(duì)Unity Shader,探討ShaderLab的結(jié)構(gòu)及其分類。通過ShaderLab的兩種形式—表面著色器和頂點(diǎn)/片元著色器,完成兩種石油流動(dòng)效果的模擬。一種使用紋理切換方式,引入時(shí)間變量對(duì)貼圖進(jìn)行采樣,實(shí)現(xiàn)貼圖運(yùn)動(dòng)效果;另一種對(duì)噪聲進(jìn)行采樣,得到隨機(jī)數(shù),通過該隨機(jī)值與頂點(diǎn)進(jìn)行組合進(jìn)行采樣,貼圖就會(huì)出現(xiàn)動(dòng)態(tài)的扭曲效果,完成對(duì)石油流動(dòng)效果的模擬。