日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Unity GeometryShader(从一个线框渲染的例子开始)

發布時間:2023/12/10 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Unity GeometryShader(从一个线框渲染的例子开始) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  GeometryShader這個概念,已經出現很久了,但由于性能不佳,所以使用的并不多。甚至移動平臺根本就不支持。移動平臺的硬件更新速度也是越來越快,GS的應用普及應該不會太遠。就現階段而言,GS來做一些輔助效果也是有一定用武之地的。就像本文要提到的這個線框渲染的效果(如下圖)。在Unity編輯模式中,偶爾有時候希望能有這種效果, 我在AssetStore里找到了一個叫UCLA Wireframe Shader的資源,里面有Shader源碼。發現它是利用GS來實現的,本文就以它的源碼為例來說明一下它是如何利用GeometryShader來實現這種線框渲染效果(WireFrame)的。

?

概念解釋


?

在具體看代碼之前,需要先對幾何著色器階段有個初步了解,大概需要知道需要下面幾兩個概念:

1.圖元(graphics primitive)

  幾乎所有的圖形渲染入門書籍里,都要提到這個概念,我們知道,所有的幾何模型都是有點,線,三角形等基本單元組成的(這里以三角形為例),每個圖元又是由若干個頂點構成。在渲染管線的開始,GPU處理的是每一個頂點,但是GPU是知道每一個頂點是屬于哪個三角形的。所有頂點經過頂點著色器處理后輸出的結果會經過一個圖元裝配(Primitive Assembly)的階段,這個階段就是把這些處理后的頂點組裝成成一個個三角形。為什么這么做呢?因為之后的無論是光柵化和頂點信息插值過程,以及視椎體的裁剪,都是以圖元為單位進行的(如果你對這個過程不是非常了解,可以查查資料,或者去看一下劉鵬翻譯的《計算機圖形學—基于3D圖形開發技術》),經過上述的這些階段后再到達我們熟悉的片元著色階段,也就離最終渲染結果不遠了。

2.幾何著色器(Geometry Shader)

  對于VS,FS我們都比較熟悉,那GS出現在哪呢?從下面這種圖中我們可以看到GS是位于VS和FS之間的。并且是虛線連接,即是可選的。GeometryShader所接收的實際是對VS輸出的圖元進行添加,刪除,或修改,然后輸出新的圖元信息。再之后的流程就和之前的一樣了。

進行線框渲染,一個比較困擾的地方就是我們不知道一個頂點是屬于哪一個圖元的。但是有了GS的參與之后,這一切就迎刃而解了。后面解釋代碼時會具體說。

代碼解釋


?

1 Shader "UCLA Game Lab/Wireframe/Single-Sided" 2 { 3 Properties 4 { 5 _Color ("Line Color", Color) = (1,1,1,1) 6 _MainTex ("Main Texture", 2D) = "white" {} 7 _Thickness ("Thickness", Float) = 1 8 } 9 10 SubShader 11 { 12 Pass 13 { 14 Tags { "RenderType"="Transparent" "Queue"="Transparent" } 15 16 Blend SrcAlpha OneMinusSrcAlpha 17 ZWrite Off 18 LOD 200 19 20 CGPROGRAM 21 #pragma target 5.0 22 #include "UnityCG.cginc" 23 #include "UCLA GameLab Wireframe Functions.cginc" 24 #pragma vertex vert 25 #pragma fragment frag 26 #pragma geometry geom 27 28 // Vertex Shader 29 UCLAGL_v2g vert(appdata_base v) 30 { 31 return UCLAGL_vert(v); 32 } 33 34 // Geometry Shader 35 [maxvertexcount(3)] 36 void geom(triangle UCLAGL_v2g p[3], inout TriangleStream<UCLAGL_g2f> triStream) 37 { 38 UCLAGL_geom( p, triStream); 39 } 40 41 // Fragment Shader 42 float4 frag(UCLAGL_g2f input) : COLOR 43 { 44 return UCLAGL_frag(input); 45 } 46 47 ENDCG 48 } 49 } 50 }

可以看到這個文件里調用了很多定義在"UCLA GameLab Wireframe Functions.cginc"這個文件中的函數。后面用到時候再看。

?

第26行:指定了用于幾何著色器執行的函數。與vs,fs一樣。另外21行中指定了要是用的ShaderModel(SM定義了著色器代碼的一些規范和能力),這里必須是4.0以上的版本。

29~32行:vs的代碼沒有什么特殊的,下面貼出的UCLAGL_vert和UCLAGL_v2g的代碼也沒啥不一樣的。唯一值得提一下就是UCLAGL_v2g中pos后面的語意是POSITION而非SV_POSITION,我試了一下這樣沒啥問題。可以看出vs的代碼和以前的意義,進行完頂點處理之后,GPU會進行圖元裝備,接下來就進入到GS階段了。

1 // DATA STRUCTURES // 2 // Vertex to Geometry 3 struct UCLAGL_v2g 4 { 5 float4 pos : POSITION; // vertex position 6 float2 uv : TEXCOORD0; // vertex uv coordinate 7 }; 1 // Vertex Shader 2 UCLAGL_v2g UCLAGL_vert(appdata_base v) 3 { 4 UCLAGL_v2g output; 5 output.pos = mul(UNITY_MATRIX_MVP, v.vertex); 6 output.uv = TRANSFORM_TEX (v.texcoord, _MainTex);//v.texcoord; 7 8 return output; 9 }

  35~39行:這段就是GS的執行函數了,其中第35行[maxvertexcount(3)]是用來限制GS輸出的最大頂點數,這里必須理解清楚,前面說過GS可以對輸入的圖元進行刪除,添加,修改,也就是進來一個圖元,可能輸出0~n個圖元,不論圖元是以何種形式組織的,它都是由頂點構成的,這個maxvertexcount就是用來限定這個頂點數量的,記住它只是限定最大數量,也就是你提供小于等于這個數量的頂點就可以。

  36行:估計你第一次看到這行代碼的時候應該和我一樣感到奇怪。這怎么還有點模板的意思,還有剛才的那個[maxvertexcount],這個語法看上去有點C#的Attribute的意思啊。平常寫的UnityShader不是說CG語言(C for Graphics),這可和C不太一樣啊。其實Unity的Shader代碼是基于自己的ShaderLab結構的,他用的CG也并不是和Nvidia的CG一模一樣。我查了下OpenGL和官方CG的GS用法,大家的意思都是差不多,但是語法細節上是不一樣的。無論怎么樣最終Unity會負責對ShaderLab進行編譯,轉化成對于平臺的GLSL或者HLSL語言。

  回到這個函數聲明,第一個參數triangle UCLAGL_v2g p[3],GS接收的是圖元,那這個圖元是以什么樣的形式傳遞進來的呢?就是以頂點結構數組的形式,比如一個三角形圖元由三個頂點構成,那么數組大小就是3,相應的點和線就是1和2。這個參數最前面的triangle就是用來表示這個的,還有兩個就是point和line。要記住這個標識符必須和后面數組大小相配。還有一點就是你填寫的圖元標識符類型和Unity原始模型資源的頂點組織方式不要求一定匹配,比如Unity默認組織模型資源是三角圖元的,你在這里可以用point接收,但這樣的結果就是本來這個圖元有三個頂點,但是你只能接收到第一個了。

? ? ? 第二個參數inout TriangleStream<UCLAGL_g2f> triStream,這里的inout就和C#的inout是一樣的,CG本身就有這個關鍵字。而TriangleStream決定了輸出的圖元是三角形圖元。對應的還有LineStream和PointStream。UCLAGL_g2f的內容如下:

1 // Geometry to UCLAGL_fragment 2 struct UCLAGL_g2f 3 { 4 float4 pos : POSITION; // fragment position 5 float2 uv : TEXCOORD0; // fragment uv coordinate 6 float3 dist : TEXCOORD1; // distance to each edge of the triangle 7 };

  這個結構定義了構成GS輸出圖元的頂點結構。這里有個dist,后面再解釋。可以想象,GS就是把第一個參數的信息拿過來經過處理后把結果填充到第二個參數中去。需要額外說明一下,這里的Stream類型和上面的maxvertexcount是有一些關聯的,上面代碼輸出圖元類型是三角形,構成三角形最少需要三個頂點,如果我們最終在GS中像triStream提供了小于三個頂點,則GS將放棄這個片元,當然你也可以提供多余三個頂點,但是超過了maxvertexcount的部分也會被拋棄掉。總之他們兩個是有聯系的,這里只是提一下,使用時候要注意。

現在來看一下UCLAGL_geom函數,也是這個Shader的核心部分。

1 // Geometry Shader 2 [maxvertexcount(3)] 3 void UCLAGL_geom(triangle UCLAGL_v2g p[3], inout TriangleStream<UCLAGL_g2f> triStream) 4 { 5 //points in screen space 6 float2 p0 = _ScreenParams.xy * p[0].pos.xy / p[0].pos.w; 7 float2 p1 = _ScreenParams.xy * p[1].pos.xy / p[1].pos.w; 8 float2 p2 = _ScreenParams.xy * p[2].pos.xy / p[2].pos.w; 9 10 //edge vectors 11 float2 v0 = p2 - p1; 12 float2 v1 = p2 - p0; 13 float2 v2 = p1 - p0; 14 15 //area of the triangle 16 float area = abs(v1.x*v2.y - v1.y * v2.x); 17 18 //values based on distance to the edges 19 float dist0 = area / length(v0); 20 float dist1 = area / length(v1); 21 float dist2 = area / length(v2); 22 23 UCLAGL_g2f pIn; 24 25 //add the first point 26 pIn.pos = p[0].pos; 27 pIn.uv = p[0].uv; 28 pIn.dist = float3(dist0,0,0); 29 triStream.Append(pIn); 30 31 //add the second point 32 pIn.pos = p[1].pos; 33 pIn.uv = p[1].uv; 34 pIn.dist = float3(0,dist1,0); 35 triStream.Append(pIn); 36 37 //add the third point 38 pIn.pos = p[2].pos; 39 pIn.uv = p[2].uv; 40 pIn.dist = float3(0,0,dist2); 41 triStream.Append(pIn); 42 }

  先說一下開頭處,p0,p1,p2處的計算,因為GS接收的頂點信息都是VS處理過的,在VS中頂點輸出的pos信息已經是其在投影空間下的坐標了。現在p[x].pos.xy/p[x].ps.w實際就是手動進行透視除法,得到視口坐標。_ScreenParams.xy是一個Unity為我們提供的內置變量,表示屏幕的分辨率。那么這兩者相乘就得到了p[x]在屏幕空間的坐標(單位像素)。這里分別對當前三角圖元的三個頂點進行了計算。

?  再分別講頂點兩兩相減,得到三角形的三個邊向量。之后利用叉積的幾何意義得到三角形的面積,這里實際是平行四邊形面積,注意這里用的是v1和v2,思考一下為什么。得到面積后,利用四邊形面積公式(底邊長X高)來得到當前頂點的對角邊的距離。

?  計算出這三個距離之后就可以進行輸出了。前面我們提到UCLAGL_g2f結構中有一個dist,現在可以解釋一下了。他是個float3類型,他的xyz分量分別代表了當前頂點到達三角圖元三邊的距離,你可能會奇怪為什么要用float3,明明一個頂點到其中兩條邊的距離都是0,之后另外一條邊才不是0。要知道我們GS最后輸出的依然是圖元,還沒有進行光柵化插值。最終我們要進行渲染的是片元,這些片元可不一定是正好在圖元的三個頂點上。所以用float3是為了能夠正確的插值,將來光柵化時候能得到片元距離圖元三邊的距離。進行輸出時候先定義了一個UCLAGL_g2f類型的pIn變量。可以看到后面賦值就沒什么說的,注意dist的賦值。每當構造完一個UCLAGL_g2f變量以后,就調用Append方法把它添加到輸出結構中。

?  可見這里的GS并沒有刪除或者添加圖元,它只是對輸入圖元進行修改再輸出。進來的是三角片元的三個頂點,出去的還是三角片元三個頂點。但是經過GS處理,現在每個頂點都知道他距離他所在的三角圖元三條邊的距離了。正式這個值讓我們在fs中能完成線框渲染的效果。?

  第42~45行:fs我們主要看UCLAGL_frag這個函數,代碼如下:

1 // Fragment Shader 2 float4 UCLAGL_frag(UCLAGL_g2f input) : COLOR 3 { 4 //find the smallest distance 5 float val = min( input.dist.x, min( input.dist.y, input.dist.z)); 6 7 //calculate power to 2 to thin the line 8 val = exp2( -1/_Thickness * val * val ); 9 10 //blend between the lines and the negative space to give illusion of anti aliasing 11 float4 targetColor = _Color * tex2D( _MainTex, input.uv); 12 float4 transCol = _Color * tex2D( _MainTex, input.uv); 13 transCol.a = 0; 14 return val * targetColor+ ( 1 - val ) * transCol; 15 }

  要知道現在input參數里的所有信息,都是經過插值了,它代表的是片元,而不是頂點了。先選出該片元到其所在三角圖元三邊的最短距離val.看最后的幾行代碼,可以知道是利用一個混合因子來進行blend,來達到距離圖元邊越近的片元,可見程度越高。那么混合因子值的計算就決定了后面這個融合步驟的好壞。如果混合因子選取的不好,最終線框渲染的效果就不好。

  最后來看看混合因子的計算:val = exp2( -1/_Thickness * val * val );這行代碼可以從最外層開始理解,exp2是CG的內置函數,代表2位底的指數函數。你去看看指數函數的圖像就知道,當x小于0的時候y值是在0~1區間內的。而且不會為負。而我們的混合因子也是要在0~1區間的。上式括號中的-1就決定了exp2的參數一定為負,因為_Thickness是大于等于0,val的平方也是大于等于0的。那為什么要用val的平方的。看一下y=x^2的函數圖像。val是距離,他是大于等于0的,那么隨著x的增大,y的增大幅度越來越大(y的導函數是個上升函數),也就是說隨著val的增大val*val的取值跨度越來越大。你會問,這又有什么用呢?你現在把兩個函數圖像結合起來,先不看_Thickness,得到下圖3.

  從圖像上看出當x在0~2范圍內,函數曲線急劇下降。并在之后無限趨近于0。這樣計算后的混合因子大概只在片元距離三角圖元邊線0~2個像素的距離內,可見性才明顯一些,這樣就能畫出比較明顯的邊線效果。?之所以又加上了_Thickness,可以看到把曲線的最終結果除以一個正數,這個數越大,那么就會越明顯的減緩圖像三曲線的下降效果,也就是圖像不會太快的趨近于0,那么新的混合因子在表現的時候邊線顯得更寬了。

總結


?

  這個方法只是視覺上達到了WireFrame的效果,實際上還是以三角圖元的方式來渲染的,只是利用透明度的混合來達到效果。如果真正的想要按照線框模式來渲染,應該修改GS讓他輸出的圖元是LineStream。也就完全不需要像這種方法計算這么多中間變量了。如果感興趣,不妨寫寫試試。只是想透過這個例子來說明一下Unity中GeometryShader的基本原理和語法。

  貌似Unity的GS好像還只能在DX11API上用,OpenGL上還不行。OpenGL ES目前還不支持GeometryShader。

  利用GS確實能做出很多以前做不出或者很難做出的效果,其中一個關鍵點就是頂點能知道它所在圖元的一些信息,這是以前的VS-FS結構做不到的。

  

  ?尊重他人智慧成果,若要轉載,請注明作者esfog,原文地址http://www.cnblogs.com/Esfog/p/UnityGeometryShader_WireFrame.html?

?

轉載于:https://www.cnblogs.com/Esfog/p/UnityGeometryShader_WireFrame.html

總結

以上是生活随笔為你收集整理的Unity GeometryShader(从一个线框渲染的例子开始)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。