在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)

posted @ 2015-09-05 17:32  amazonove  阅读(5301)  评论(0编辑  收藏  举报