Shader編程學習筆記(十二)—— Cg語言入門4 -函數


1、引言

  這一篇我們倆瞭解下Cg語言中的函數,這裏涉及到的知識點包括以下內容:

  • 定製函數
  • 函數的前項聲明
  • 函數的傳參
  • Cg include
  • Cg中的內建函數

2、示例

  這我們將一一舉例說明!開始之前我們先搭建好場景,便於演示。創建一個場景,一個cube,一個材質球和一個頂點片元shader。然後該cube使用新建的材質,選擇剛纔闖將的shader,修改代碼如下:

Shader "lxt610/NewSurfaceShaderTest3" {
    SubShader 
	{
		Pass
		{
			CGPROGRAM
        
	#pragma fragment frag
	#pragma vertex vert
		
			void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
			{
				pos = float4(objPos,0,1);			
				
				col = float4(1,0,0,1);//紅色

				//col = pos;
			}

			void frag(inout float4 col:COLOR0)
			{				
			}
			ENDCG
		}
    }
}

  運行場景後顯示紅色。

2.1、函數

  函數的語法格式:

返回值類型 函數名稱([參數列表])
{
		//語句塊;
}

  下面是一個示例

Shader "lxt610/NewSurfaceShaderTest3" {
    SubShader 
	{
		Pass
		{
			CGPROGRAM
       
	#pragma fragment frag
	#pragma vertex vert
			void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
			{
				pos = float4(objPos,0,1);						
				col = float4(1,0,0,1);//紅色
			}

			void frag(inout float4 col:COLOR0)
			{				
				 func();
			}
				
			void func()
			{			
			}
			
			ENDCG
		}
    }
}

  寫完回到unity,編譯完畢發現異常了,提示函數未定義!Cg中需要把聲明的函數放在使用的位置之前,否則就異常了。這時只需要改爲:

void func()
{			
}
void frag(inout float4 col:COLOR0)
{				
	 func();
}		

  就可以了!然而有時候我們就想在後面聲明怎麼辦?

2.2、函數的前項聲明

  此時可以考慮函數的前項聲明。使用方式就是:在被調用的函數的位置前面聲明一個前置函數,其函數名返回值類型及參數列表和聲明的函數相同就可以了。eg:

Shader "lxt610/NewSurfaceShaderTest3" {
    SubShader 
	{
		Pass
		{
			CGPROGRAM
       
	#pragma fragment frag
	#pragma vertex vert
			void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
			{
				pos = float4(objPos,0,1);						
				col = float4(1,0,0,1);//紅色
			}
			void func();
			void frag(inout float4 col:COLOR0)
			{				
				 func();
			}
				
			void func()
			{		
			}
		
			ENDCG
		}
    }
}

  此時我們發現編譯通過!

2.3、函數的參數傳遞

  我們再次修改代碼

Shader "lxt610/NewSurfaceShaderTest3" {
    SubShader 
	{
		Pass
		{
			CGPROGRAM
       
	#pragma fragment frag
	#pragma vertex vert
			void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
			{
				pos = float4(objPos,0,1);						
				col = pos;
			}
			void func(float4 col);
			void frag(inout float4 col:COLOR0)
			{				
				func(col);
			}
				
			void func(float4 col)
			{
				 col = float4(0,1,0,1);
			}			
			ENDCG
		}
    }
}

  這裏我們發現顏色沒有變化爲想要的顏色,這時由於Cg中這裏的值進行了值Copy,並不是操作的原來的值!要想改變可以使用out關鍵字:

Shader "lxt610/NewSurfaceShaderTest3" {
    SubShader 
	{
		Pass
		{
			CGPROGRAM
       
	#pragma fragment frag
	#pragma vertex vert
			void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
			{
				pos = float4(objPos,0,1);						
				col = pos;
			}
			void func(out float4 col);
			void frag(inout float4 col:COLOR0)
			{				
				func(col);
			}
				
			void func(out float4 col)
			{
				 col = float4(0,1,0,1);
			}			
			ENDCG
		}
    }
}

  編譯通過後我們發現顏色變爲了綠色。我們可以知道這裏的out參數只是把數值傳遞進來,把值做了改變,並傳回去了。這個out類似於C#中的ref關鍵字。當我們不需要Copy一個變量,需要這個值得時候可以使用out參數
  當我們需要傳遞一個數組時,不可以像其他語言一樣,需要這樣寫:

float func2(float arr[3])
{
	float sum = 0;
	for(int i = 0; i < arr.Length; i++)
	{
		sum += arr[i];
	}

	return sum;
}

void frag(inout float4 col:COLOR0)
{				
	//func(col)
	float arr[2] = {0.5,0.5};
	col.x = func2(arr);
}

這裏需要注意的是:

  • 聲明的函數的參數列表中如果是數組時,我們需要指定這個函數的長度,我們同時在調用的時候也必須傳遞給這個函數相同長度的數組
  • 同時聲明數組時如果我們是ES3.0需要指定數組的長度,eg:float arr[2] = {0.5,0.5};
  • 我們注意到Cg中數組有一個關鍵字Length,用來表示數組的長度。

最終的代碼和效果如下:

Shader "lxt610/NewSurfaceShaderTest3" {
    SubShader 
	{
		Pass
		{
			CGPROGRAM
			
#pragma fragment frag
#pragma vertex vert

			void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
			{
				pos = float4(objPos,0,1);						
				col = pos;
			}

			void func(out float4 col);
			float func2(float arr[2])
			{
				float sum = 0;
				for(int i = 0; i < arr.Length; i++)
				{
					sum += arr[i];
				}

				return sum;
			}

			void frag(inout float4 col:COLOR0)
			{				
				//func(col)
				float arr[2] = {0.5,0.5};
				col.x = func2(arr);
			}
				
			void func(out float4 col)
			{
				 col = float4(0,1,0,1);
			}

			ENDCG
		}
    }
}

在這裏插入圖片描述
  同樣這裏的數組也是一個值Copye。因此在Cg中參數的傳遞是通過值Copy的形式來傳遞的,需要使用這個參數時可以使用out關鍵字。

2.4、Cg include

  當我們寫了很多shader之後,我們發現不同的shader在使用同樣的函數,有沒有什麼辦法解決這樣的問題呢?這裏就需要使用Cg當中的include指令。此時我們不妨打開Unity\Unity2017.4.27f1\Editor\Data\CGIncludes目錄,可以看到有很多.cginc文件,我們打開UnityCG.cginc,我們發現有很多函數和數據類型,我們可以使用include把這個文件包含進來,就可以使用這裏面的函數和數據類型了。
  那麼當我們自己寫shader的時候可不可以使用呢?當然也是可以的了。這裏我們創建一個.cginc文件命名爲lmy225.cginc,用來存儲自己的一些函數。爲了方便查找同時不和官方的cginc混淆這裏我專門創建了一個名爲lmy225的文件夾用來存儲自己的cginc文件,此時把lmy225.cginc放在這個文件夾中,這裏我們可以把:

float func2(float arr[2])
{
	float2 sum = 0;
	for(int  i = 0; i < arr.Length;i++)
	{
		sum += arr[i];
	}
	return sum;
}

void func(out float4 c)
{
	c = float4(0,1,0,1);
}

放在cginc中,然後回到shader刪除前項聲明,使用下面的include指令:

#include "路徑"

eg:

Shader "lxt610/NewSurfaceShaderTest3" {
    SubShader 
	{
		Pass
		{
			CGPROGRAM
			
#pragma fragment frag
#pragma vertex vert

			#include "lmy225/lmy225.cginc"

			void vert(in float2 objPos:POSITION,out float4 col:COLOR0,out float4 pos:POSITION)
			{
				pos = float4(objPos,0,1);						
				col = pos;
			}

			void frag(inout float4 col:COLOR0)
			{				
				//func(col)
				float arr[2] = {0.5,0.5};
				col.x = func2(arr);
			}

			ENDCG
		}
    }
}

  編譯運行後我們發現實現了相同的效果!

這裏include指令後面的路徑可以使用\或/,也可以混合使用!
當然這裏的.cginc後綴也可以是別的自己喜歡的後綴。只是改爲其他後綴之後,關鍵字沒有高亮提示了。

2.5、Cg中的內建函數

  在cg的內建函數中主要有以下四類:

  • 數學函數
  • 幾何函數
  • 紋理函數
  • 導數函數
      這裏我們給出Cg標準函數的文檔,需要的同學,可以點擊這裏下載。當然除了上面的還有debug函數,由於功能比較單一,只是把數值通過顏色語義來顯示出來!所以很少使用!

3、結束語


The End
  好了,今天的分享就到這裏,如有不足之處,還望大家及時指正,隨時歡迎探討交流!!!


喜歡的朋友們,請幫頂、點贊、評論!您的肯定是我寫作的不竭動力!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章