在Unity中為頂點/片段着色器添加陰影


譯文

  大部分情況下當我們使用Unity的表面着色器的時候,就已經擁有陰影了。但是有時候因為某些原因你不想使用表面着色器,而是想創建你自己的頂點/片段着色器。這樣做最大的好處是一切皆由你控制,但是這同時也是它的缺點:你將不得不自己處理Unity在表面着色器里為你處理好的很多東西。其中一件事就是多重光照和陰影。

  幸運的是,Unity提供了解決的辦法!怎么做?文檔中關於這一點所言甚少。我也和很多處在類似處境的人一樣對於如何讓頂點/片段着色器擁有陰影感到困惑。我Google了一些相關資料,雖然得到的信息不能解決我的問題,卻也給了我不少指引。我還查看了一個編譯后的表面着色器,試圖找出他們是如何添加陰影的。所有這些工作加在一起,再加上一些嘗試,最后終於成功了!現在讓我來和對此感興趣的人分享這一切吧。

  在開始之前,我得提早指出——當你使用表面着色器的時候,Unity為你做了很多工作;其中一件事當你使用 deffered 或 forward渲染時的內部處理。當你使用自己的頂點/片段着色器的時候,你得把這一點計算在內。實際上,我只需要應付forward渲染這一種情況,並且只對deffered渲染做了簡短的測試。雖然在deffered渲染模式下沒出什么差錯,但是我並不保證它在此模式一切正常。請牢記這點。

  我們來看看這個漫反射的shader。它能夠投射(和接受)陰影。

Shader "Sample/Diffuse" 
{
	Properties 
	{
		_DiffuseTexture ("Diffuse Texture", 2D) = "white" {}
		_DiffuseTint ( "Diffuse Tint", Color) = (1, 1, 1, 1)
	}

	SubShader 
	{
		Tags { "RenderType"="Opaque" }

		pass
		{		
			Tags { "LightMode"="ForwardBase"}

			CGPROGRAM

			#pragma target 3.0
			#pragma fragmentoption ARB_precision_hint_fastest

			#pragma vertex vertShadow
			#pragma fragment fragShadow
			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

			sampler2D _DiffuseTexture;
			float4 _DiffuseTint;
			float4 _LightColor0;

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 lightDir : TEXCOORD0;
				float3 normal : TEXCOORD1;
				float2 uv : TEXCOORD2;
				LIGHTING_COORDS(3, 4)
			};

			v2f vertShadow(appdata_base v)
			{
				v2f o;

				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.texcoord;
				o.lightDir = normalize(ObjSpaceLightDir(v.vertex));
				o.normal = normalize(v.normal).xyz;

				TRANSFER_VERTEX_TO_FRAGMENT(o);

				return o; 
			}

			float4 fragShadow(v2f i) : COLOR
			{					
				float3 L = normalize(i.lightDir);
				float3 N = normalize(i.normal);	 

				float attenuation = LIGHT_ATTENUATION(i) * 2;
				float4 ambient = UNITY_LIGHTMODEL_AMBIENT * 2;

				float NdotL = saturate(dot(N, L));
				float4 diffuseTerm = NdotL * _LightColor0 * _DiffuseTint * attenuation;

				float4 diffuse = tex2D(_DiffuseTexture, i.uv);

				float4 finalColor = (ambient + diffuseTerm) * diffuse;

				return finalColor;
			}

			ENDCG
		}		

	} 
	FallBack "Diffuse"
}

   如果你曾經寫過頂點/片段着色器的話,你應該知道shader中需要注意的就是一些宏。但是讓我們看一下要獲得陰影,首先應該怎么做。

  首先你得定義 pass里的一個Tag : LightMode

Tags { "LightMode"="ForwardBase"}

  這會告訴Unity這個通道會使用主光源來投射陰影。Unity對每個光源使用它們自己的通道進行處理。如果我們想要使用多重光照,在另一個通道里面這個值會變成 ForwardAdd

  接下來我們要定義:

#pragma multi_compile_fwdbase

  這是為了確保shader為所需的通道執行恰當的編譯。有了這個tag,在所有疊加的光源自己的通道里面, fwdbase 就會變成 fwdadd

  為了獲得陰影,我們還需要一些額外的代碼/宏。所以我們得把AutoLight.cginc 包括進來。

#include "AutoLight.cginc"

  因為Unity知道該如何處理光照的一切信息,我們只需要獲取相關數據然后讓我們的陰影顯示出來就好了。這需要做到以下三件事:

  1. Unity生成/包含 所需的參數來對陰影進行采樣。
  2. 使用正確的數據來填充這些參數。
  3. 獲取最終值。

  要讓Unity“生成”我們所需的值,我們所需做的就是在頂點到片段的結構體中添加 LIGHTING_COORDS 指令。

struct v2f
{
	float4 pos : SV_POSITION;
	float3 lightDir : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float2 uv : TEXCOORD2;
	LIGHTING_COORDS(3, 4)
};

  LIGHTING_COORDS指令定義了對陰影貼圖和光照貼圖采樣所需的參數。指令參數的數字說明了所需的TEXCOORD參數的數量。 如果我想要一個視點方向來計算高光反射,結構體如下:

struct v2f
{
	float4 pos : SV_POSITION;
	float3 lightDir : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float2 uv : TEXCOORD2;
	float3 viewDir : TEXCOORD3;
	LIGHTING_COORDS(4, 5)
};

  這有點像是自己定義了這些參數,其實不然。Unity很確定它們使用的是正確的數值,正確的光源,或許還有一個cookie 紋理。如果你好奇到底定義了些什么,請查看 AutoLight.cginc文件。

下一步是頂點着色器。光有這些參數還不夠,我們還得提供正確的數據。Untiy提供了另一個指令以便在正確的情況下填充正確的數據 ——TRANSFER_VERTEX_TO_FRAGMENT。這個指令必須在返回 v2f 結構體之前定義。所以頂點着色器如下:

v2f vertShadow(appdata_base v)
{
	v2f o;

	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
	o.uv = v.texcoord;
	o.lightDir = normalize(ObjSpaceLightDir(v.vertex));
	o.normal = normalize(v.normal).xyz;

	TRANSFER_VERTEX_TO_FRAGMENT(o);

	return o; 
}

  沒有太多要點。只需知道它為你計算不同光源的光照和陰影坐標。

  現在我們只剩下創建我們的片段着色器並且使用LIGHT_ATTENUATION指令來返回正確的最終值了。你可以像平時一樣使用 attenuation 值。在漫反射shader里我把它用在漫反射計算中了。就像這樣:

float4 fragShadow(v2f i) : COLOR
{					
	float3 L = normalize(i.lightDir);
	float3 N = normalize(i.normal);	 

	float attenuation = LIGHT_ATTENUATION(i) * 2;
	float4 ambient = UNITY_LIGHTMODEL_AMBIENT * 2;

	float NdotL = saturate(dot(N, L));
	float4 diffuseTerm = NdotL * _LightColor0 * _DiffuseTint * attenuation;

	float4 diffuse = tex2D(_DiffuseTexture, i.uv);

	float4 finalColor = (ambient + diffuseTerm) * diffuse;

	return finalColor;
}

  現在你已經擁有讓你的頂點/片段着色器獲得陰影所需的一切了。LIGHT_ATTENUATION指令對陰影貼圖采樣並且返回數據供你使用。再說一次,如果你想知道LIGHT_ATTENUATION指令具體做了些什么,檢查 AutoLight.cginc文件。 

  對了,還有一件小事要處理。如果要讓Unity投射/接受陰影,你必須提供一個 shadow receiver 和 shadow caster通道。我這里沒有提供這些。我只是加了一個提供了這些通道的 fallback shader。那樣我就用不着自己添加這些通道,令shader的體積變大。當然,你還可以把這些代碼放到一個 .cginc 文件里,或者干脆放到最底部,眼不見為凈。不過現在來講簡簡單單的加一個 fallback 就能工作的很好了。

  我希望這些內容能對那些試圖在自己的着色器里面添加陰影的人有所幫助。有任何問題請給我評論!

 

原文地址:http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/let-there-be-shadow-r3642

蠻牛發布地址:http://www.manew.com/thread-41638-1-1.html

原文

Shadows in Unity is something that in most cases is a given through the use of surface shaders, but sometimes you don't want to use a surface shader for whatever reason and create your own vertex/fragment shader. The biggest advantage is that everything is in your hands now, but this is also one of the drawbacks because you now have to handle a lot of stuff that Unity conveniently handled for you in a surface shader. Among such things are support for multiple lights and shadows.

Luckily, Unity provides you the means to get this working! The catch? Documentation on this is lacking or even non-existent. I was in the same position as most people and somewhat clueless on how to get shadows in my vertex/fragment shader, I did my fair share of googling and found some clues that didn't quite do the trick, but gave me a good impression on where to search. I also went through a compiled surface shader to see if I could figure out how they did it. All of the research combined and some trying out finally gave me the results I needed: Shadows! And now I will share it with whoever is interested.

Before I begin, I want to make note that, as mentioned earlier, Unity solves a lot of cases for you when you are using surface shaders; among such things are the inner workings when you are using deferred or forward rendering. With your own vertex/fragment shaders, you will need to take that into account yourself for some cases. Truth is, I only needed to get this to work with forward rendering and only briefly tested how this works with deferred rendering and although I did not notice anything off, I can't guarantee it will work in all cases, so keep that in mind!

I will start off with showing you the shader that casts (and receives) a nice shadow and break it down, going over the different elements of interest. It's a simple diffuse shader that looks like this:

Shader "Sample/Diffuse" 
{
	Properties 
	{
		_DiffuseTexture ("Diffuse Texture", 2D) = "white" {}
		_DiffuseTint ( "Diffuse Tint", Color) = (1, 1, 1, 1)
	}

	SubShader 
	{
		Tags { "RenderType"="Opaque" }

		pass
		{		
			Tags { "LightMode"="ForwardBase"}

			CGPROGRAM

			#pragma target 3.0
			#pragma fragmentoption ARB_precision_hint_fastest

			#pragma vertex vertShadow
			#pragma fragment fragShadow
			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "AutoLight.cginc"

			sampler2D _DiffuseTexture;
			float4 _DiffuseTint;
			float4 _LightColor0;

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 lightDir : TEXCOORD0;
				float3 normal : TEXCOORD1;
				float2 uv : TEXCOORD2;
				LIGHTING_COORDS(3, 4)
			};

			v2f vertShadow(appdata_base v)
			{
				v2f o;

				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.texcoord;
				o.lightDir = normalize(ObjSpaceLightDir(v.vertex));
				o.normal = normalize(v.normal).xyz;

				TRANSFER_VERTEX_TO_FRAGMENT(o);

				return o; 
			}

			float4 fragShadow(v2f i) : COLOR
			{					
				float3 L = normalize(i.lightDir);
				float3 N = normalize(i.normal);	 

				float attenuation = LIGHT_ATTENUATION(i) * 2;
				float4 ambient = UNITY_LIGHTMODEL_AMBIENT * 2;

				float NdotL = saturate(dot(N, L));
				float4 diffuseTerm = NdotL * _LightColor0 * _DiffuseTint * attenuation;

				float4 diffuse = tex2D(_DiffuseTexture, i.uv);

				float4 finalColor = (ambient + diffuseTerm) * diffuse;

				return finalColor;
			}

			ENDCG
		}		

	} 
	FallBack "Diffuse"
}

 

If you have ever worked with vertex/fragment shaders you will notice that there isn't much to be noted except for a few macros, but let's address the first things you will need to do to get those shadows.
 
The first thing you will need to define is the LightMode pass Tag:

Tags { "LightMode"="ForwardBase"}
 
This will tell unity that this pass will make use of the main light that will cast our shadow (there's more to this tag, check the link for more info). Unity handles each light in their own pass, so if we want to work with multiple lights, this value in another pass would change to ForwardAdd.

Next to the tag, we also need to define the following:

#pragma multi_compile_fwdbase
 
This is to ensure the shader compiles properly for the needed passes. As with the tag, for any additional lights in their own pass,fwdbase becomes fwdadd.

To make use of all the needed code/macros to sample shadows in our shader, we will need to include the AutoLight.cginc that holds all the goodness:

#include "AutoLight.cginc"

 

Now that Unity knows all it needs on how to handle the lights, we just have to get the relevant data to get our shadow to appear and for that we only have to do 3 things:
  1. Make Unity generate/include the needed parameters to sample the shadow.
  2. Fill these parameters with values that makes sense.
  3. Get the final values.
To make Unity "generate" the values we need, all we have to do is add the LIGHTING_COORDS macro to our vertex to our fragment struct like so:

struct v2f
{
	float4 pos : SV_POSITION;
	float3 lightDir : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float2 uv : TEXCOORD2;
	LIGHTING_COORDS(3, 4)
}; 

The LIGHTING_COORDS macro defines the parameters needed to sample the shadow map and the light map depending on the light source. The numbers specified are the next 2 available TEXCOORD semantics. So if I would need a viewing direction for a specular highlight, the struct would look like this:

struct v2f
{
	float4 pos : SV_POSITION;
	float3 lightDir : TEXCOORD0;
	float3 normal : TEXCOORD1;
	float2 uv : TEXCOORD2;
	float3 viewDir : TEXCOORD3;
	LIGHTING_COORDS(4, 5)
}; 

This is much like defining them yourself, except that now it's guaranteed for Unity that they're using the right values for the right light sources with perhaps also a cookie texture attached to them. If you're curious as to what gets defined exactly, check out the AutoLight.cginc file.

Next up is the vertex shader. Having the values is one thing, but we need them to hold the right data and Unity provides another macro that fills it up with the right data for the right situation, this is done with the TRANSFER_VERTEX_TO_FRAGMENT macro. This macro must be defined before returning the v2f struct, so your vertex shader would look something like this:

v2f vertShadow(appdata_base v)
{
	v2f o;

	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
	o.uv = v.texcoord;
	o.lightDir = normalize(ObjSpaceLightDir(v.vertex));
	o.normal = normalize(v.normal).xyz;

	TRANSFER_VERTEX_TO_FRAGMENT(o);

	return o; 
}

  

Not much is to be said about this, other than that it takes care of calculating the light and shadow coordinates for you for the different lights.

At this moment, all we have left is to create our fragment program that is able to use the LIGHT_ATTENUATION macro that returns the correct values we need for our shadow. You can use the attenuation value like you would normally, for diffuse shading I use it in the diffuse term like this in the fragment shader:

float4 fragShadow(v2f i) : COLOR
{					
	float3 L = normalize(i.lightDir);
	float3 N = normalize(i.normal);	 

	float attenuation = LIGHT_ATTENUATION(i) * 2;
	float4 ambient = UNITY_LIGHTMODEL_AMBIENT * 2;

	float NdotL = saturate(dot(N, L));
	float4 diffuseTerm = NdotL * _LightColor0 * _DiffuseTint * attenuation;

	float4 diffuse = tex2D(_DiffuseTexture, i.uv);

	float4 finalColor = (ambient + diffuseTerm) * diffuse;

	return finalColor;
}

And there you have it, everything you need to get that lovely shadow in your vertex/fragment shaders. The LIGHT_ATTENUATIONsamples the shadowmap and returns the value for you to use. Once again, if you want to know what LIGHT_ATTENUATION exactly does, check out the AutoLight.cginc.

There is still one little thing to be noted however. For Unity to have something cast and/or receive a shadow, you must provide a shadow receiver and caster pass which I didn't provide here. Instead of making them yourself, I simply added a fallback shader that has these passes so I don't have to add them myself and make the shader bigger than it already is. You can of course add this to a .cginc or put them all the way down and never look back at it, but just adding a fallback works just as well for our shadow purpose.

I hope this clears things up a bit for those struggling to get their shaders to cast and/or receive shadows. Feel free to leave me a comment or mail me if you have any questions or remarks on this post!

About the Author(s)

I am a game development student from the Netherlands, studying at the NHTV, the International Game Architecture and Design course in Breda. currently I am graduating at Digital Dreams working on the game: Metrico. 

License

GDOL (Gamedev.net Open License)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM