基于Unity Shader石油泄漏现象模拟的研究
2020-07-15刘贤梅葛昊天
刘贤梅,葛昊天,赵 娅
(东北石油大学 计算机与信息技术学院,黑龙江 大庆 163318)
0 引 言
近年来不断发展成熟的虚拟现实技术(virtual reality),利用计算机图形学、三维建模、多媒体等多种技术手段[1],可以在计算机上实现逼真的三维场景的渲染,为模拟石油生产过程中的意外情况,提高工人的突发情况应急处理能力,应急仿真系统应运而生。在模拟石油泄漏事故的应急仿真系统中,石油泄漏现象的流动效果的模拟具有重大意义。传统的模拟方式以粒子系统为主,但是使用粒子系统时,随着粒子数目的增加,需要的内存增加,运行速率也变得很慢。
随着硬件设备的不断发展,GPU(graphics processing unit,图形处理单元)性能的大幅度提高,由于GPU的可编程着色器的存在,开发人员可以通过可编程着色器(Shader)[2],对顶点及像素进行灵活的处理,模拟出更多逼真的效果。2006年上海师范大学苏蕴等人通过渲染流水线的可编程着色器的控制,完成对于室内光的各种形式(点光源、平行光源等)的真实感模拟[3]。2015年,海军航空工程学院的王彦等人通过可编程着色器完成大面积动态海洋红外视景仿真,提高了水流的真实感[4]。因此,针对石油泄漏的流动效果,可以采用可编程着色器(Shader)进行模拟,不仅能够模拟出石油流动的效果,还不需要消耗很多内存,能够一直保持一个较快的运行速率。
Unity是一个专业的游戏引擎,而Unity的着色器(Unity Shader)是对着色器上层的一个抽象,它负责将输入的Mesh(网格)以指定的方式与输入的贴图或者颜色等组合作用,输出纹理[5]。文中首先探讨了着色器和着色器语言,并对Unity Shader的文件结构与分类进行探讨,然后通过Unity Shader控制底层GPU中的可编程着色器,采用纹理切换方法与基于噪声的方法对输入的贴图进行处理,模拟出石油流动的效果。
1 着色器
1.1 着色器与着色器语言
Shader,中文翻译为“着色器”,其本质是作用于渲染流水线上的一段程序,能够控制渲染流水线的特定阶段进行计算,以达到目标效果。现代可编程渲染流水线的最大特点在于GPU内部硬件设计具有可编程性,可以通过程序控制GPU内部渲染流水线。GPU中的可编程结构,包括顶点着色器和片元着色器,即可以针对顶点和像素进行编程[6]。因此,可以在顶点和像素着色器中编写具有不同功能的程序来处理输入信息,使整个处理过程更具灵活性和可控性。
Shader的主流编程语言有HLSL、GLSL、CG。HLSL(high level Shader language)是微软基于DirectX的语言,只能运行在Windows平台上[7]。GLSL(OpenGL Shading language),是用在OpenGL中着色编程的语言,也是一门跨平台的着色器语言。Cg语言(C for graphics)是为GPU编程设计的高级着色器语言,由NVIDIA公司开发[8],可作为片段程序镶嵌在Unity Shader程序中。
1.2 Unity Shader
1.ShaderLab。
为了避免直接控制底层着色器,Unity提供一层抽象——Unity Shader,此外还提供了一种专门为Unity Shader服务的语言——ShaderLab。它是一种说明性的语言,使用了一些嵌套在花括号内部的语义,这些语义包含了渲染所需的数据,包括着色器所需的各种属性[9]。而Unity会自动将ShaderLab文件编译成控制GPU中各个着色器的代码。
2.ShaderLab文件的结构。
一个ShaderLab的基础文件结构如下所示:
Shader”ShaderName”
{
Properties{
//属性
}
SubShader{
//显卡A使用的着色器
}
SubShader{
//显卡B使用的着色器
}
......
Fallback”VertexList”
}
对各个部分的释义如下:
(1)Properties。
Properties语义块中包含了一系列属性,并且这些属性会出现在材质面板中,定义方式通常如下:
_Name("Display Name",type)=defaultValue[{options}]
这些属性的存在是为了能够更好地调整材质的属性,方便在着色器中访问它们。“Display Name”是出现在材质面板上的名字,type是属性的类型,常用的属性类型如表1所示,defaultValue是指定的默认值。
表1 Properties语义块支持的属性类型
(2)SubShader。
一个Shader有多个SubShader。一个SubShader可理解为一个Shader的渲染方案。即SubShader是为了针对不同的渲染情况而编写的。每个Shader至少1个SubShader。Unity会扫描所有的Unity Shader,然后选择第一个能够在目标平台上运行的SubShader。SubShader语义块中包含的定义如下:
SubShader
{
Pass{ }
}
一个SubShader是由Pass块来执行的。每个Pass定义了一次完整的渲染流程。在Pass内,定义了输入输出结构体,在结构体中进行光照,法线与空间变换等的计算。
(3)FallBack。
在各个SubShader语义块之后,会存在一个FallBack,它会规定最低级的Shader是哪一个。由于一段Shader有可能在不同等级的显卡上运行,而显卡的能力也是参差不齐的。所以,FallBack相当于留了一条“后路”,以保证若之前所有的SubShader都无法在当前的显卡上运行,则运行FallBack所指定的Shader[10]。
1.3 Unity Shader的形式
(1)表面着色器。
表面着色器(surface Shader)是Unity创造的一种着色器代码类型。它在本质上就是对顶点/片元着色器的封装,省去了一些重复代码编写的工作量。也就是Unity在提供一个表面着色器之后,在背后仍然将它变为顶点片元着色器。但是相比于顶点/片元着色器,表面着色器更加简洁,效率更高[11]。
(2)顶点/片元着色器。
相比于表面着色器,顶点/片元着色器(vertex/fragment Shader)更加复杂,但同时灵活性更高。可以根据自己当前的不同需求,编写控制顶点着色器和片元着色器的代码,对GPU渲染流水线中的可编程部分直接进行灵活的控制。
(3)固定函数着色器。
上述的两种Shader都使用了可编程管线,而对于一些比较老旧的,不支持可编程管线着色器的设备,则需要使用固定函数着色器(fixed function Shader)来完成渲染[12],这些着色器往往只能完成一些比较简单的效果,目前很少使用。
1.4 Unity Shader的使用方法
首先根据需求,选择一种Unity Shader形式,编写Shader着色程序。然后在工程中新建材质球,将上述写好的着色器程序赋给材质球,并给材质球设置贴图,作为着色器程序的输入,最后将材质球附在物体模型上,就得到了目标效果[13]。
2 石油流动效果的实现
针对石油的泄漏现象,为模拟出石油流动时石油的主体部分,在3dsMax中制作出一个边缘光滑,形状不规则的物体,作为模拟石油流动的主体模型。石油主体模型在3dsMax中的截图如图1所示。
图1 模型截图
2.1 基于纹理切换的石油流动效果设计与实现
该方法实现石油流动效果采用的是表面着色器(surface Shader),规定了两个偏移速度x,y,速度乘以随时间变化的时间变量得到偏移量。对图片进行偏移计算之后的点进行采样,获得偏移点的颜色值作为返回值输出,以达到石油流动的效果。流程图如图2所示。
图2 纹理切换流程
在Properties块中,需要声明贴图与贴图的x,y轴偏移速度。输入的主纹理贴图的格式是2D,而偏移速度为范围在(0,100)之间的数值。
_WaterTex("水的纹理图:",2D)="white" {}
_XSpeed("X轴方向的纹理滚动速度",:
Range(-10,10))=1
_YSpeed("Y轴方向的纹理滚动速度",:
Range(-10,10))=1
Properties块结束后,需要在SubShader块(渲染方案)中,嵌套CG代码块。首先,通过结构体Input,将模型顶点的uv值作为surf函数的输入。在表面函数surf中,对贴图进行计算。通过内置的时间变量_Time乘以x轴,y轴方向的偏移速度,得到偏移量,通过x轴偏移量与y轴偏移量的组合,得到偏移量scrolledUV,并与顶点的uv值相加得到偏移量,通过tex2D函数,对贴图在偏移处进行采样,得到偏移处的颜色值。由于偏移量随时间改变而改变,因此,采样得出的颜色值也会随时间改变,这样就类似于贴图在进行运动,以实现流动的效果。
fixed xValue=_XSpeed*_Time;
fixed yValue=_YSpeed*_Time;
UV+=fixed2(xValue,yValue);
half4 c=tex2D(_WaterTex,UV);
o.Albedo=c.rgb;
2.2 基于纹理切换的石油流动效果展示
图3为使用纹理切换方法在RenderDoc中截取的八帧图像。
图3 RenderDoc中的截图(1)
使用上述方法模拟在场景中发生漏油的效果如图4所示。
图4 纹理切换方法模拟效果图
2.3 基于噪声的石油流动效果的设计与实现
使用上述方法能够有效模拟出石油流动的效果,但是无法模拟出石油在光照下的反射与折射效果。因此,引入噪声图片,对噪声进行采样得到的随机值,来实现贴图的动态扭曲效果,以实现贴图的反射折射效
果,提高模拟的真实感。
在图形学中使用噪声是为了把一些随机变量引入到程序中,如火焰、地形、云朵的模拟等等都要使用随机变量。在图形学中称这种噪声为“白噪声”(功率谱密度在整个频域内均匀分布的噪声)[14]。噪声的基础来自于随机数,若屏幕上的每个像素点给一个0~1之间的随机数来表示像素点的亮度[15],就能得到白噪声纹理。因此可以通过对白噪声图片进行采样以得到随机数,完成对于石油反射折射效果的模拟。
该方法采用的是顶点/片元着色器。在该模拟中,对噪声图进行采样,其中对采样点设置时间变量,随着时间不断发生偏移,它提供的R值就会不停的变化,将R值再与顶点数据相加作为采样点,对主贴图进行采样,将得到的颜色作为输出,那么图片就会出现动态的扭曲效果,就实现了想要的石油流动效果。
流程图如图5所示。
图5 噪声模拟流程
在Pass通道嵌套的CG代码段中,首先对顶点着色器进行控制,在顶点阶段主要完成两个工作,包括把顶点坐标从模型空间转换到裁剪空间,再使用纹理的属性(_MainTex_ST.xy,_MainTex_ST.zw)对顶点坐标进行计算,得到该阶段计算之后的纹理坐标。主要计算过程如下:
v2f vert(appdata v)
{ o.vertex=UnityObjectToClipPos(v.vertex);
o.uv=TRANSFORM_TEX(v.uv,_MainTex);
}
在片元着色器中,实现的是噪声模拟方法的主体。首先设置时间变量乘以速度,得到偏移量,偏移量与顶点数据相加作为采样点,对噪声图片进行采样,得到rgb值,此时,将R值提出,作为随机数,再与顶点数据相加,作为采样点对主贴图进行采样,得到对应点的rgb值,作为返回值返回。图片就会出现动态的扭曲效果,就实现了想要的石油流动效果。
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);}
2.4 基于噪声的石油流动效果展示
图6为使用上述方法模拟时在RenderDoc中截取的八帧图像。
图6 RenderDoc中的截图(2)
使用上述方法模拟在场景中发生漏油的效果如图7所示。
图7 噪声方法模拟效果图
3 结束语
利用Unity Shader技术,模拟出石油的流动效果。首先对着色器进行简单介绍,然后针对Unity Shader,探讨ShaderLab的结构及其分类。通过ShaderLab的两种形式—表面着色器和顶点/片元着色器,完成两种石油流动效果的模拟。一种使用纹理切换方式,引入时间变量对贴图进行采样,实现贴图运动效果;另一种对噪声进行采样,得到随机数,通过该随机值与顶点进行组合进行采样,贴图就会出现动态的扭曲效果,完成对石油流动效果的模拟。