shader入门13 阴影

原理

阴影渲染的原理是将相机放在光源的位置,相机看不到的地方也就是光源无法照射的地方,即阴影.
我们有两个步骤需要实现,分别是

1.向物体投射阴影
2.接收其他物体投射的阴影

阴影投射

我们首先需要打开光源的阴影投射,和物体的阴影投射,组件中的设置会影响shader中是否计算阴影.
在这里插入图片描述
在这里插入图片描述
我们以普通的高光反射为基础,测试阴影投射
细心的人一定发现了我注释了Fallback,这是因为即便是我们没写阴影投射的方法,Fallback还是会从Diffuse中找到阴影投射的方法.

高光反射代码
Shader "Unity/Custom/NewSurfaceShader" {
	Properties{
		_Diffuse("Diffuse", Color) = (1, 1, 1, 1)//漫反射颜色
		_Specular("Specular", Color) = (1, 1, 1, 1)//高光反射颜色
		_Gloss("Gloss", Range(8.0, 256)) = 20//光泽度
	}
	SubShader{
		Pass{
			Tags{ "LightMode" = "ForwardBase" }
			CGPROGRAM
			#pragma multi_compile_fwdbase//在forwardBase中获取正确的光照变量
			#pragma vertex vert//声明顶点处理方法
			#pragma fragment frag//声明片段处理方法
			#include "Lighting.cginc"//调用光照函数库
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			struct a2v {
				float4 vertex : POSITION;//顶点的模型空间位置
				float3 normal : NORMAL;//模型空间法线
			};
			struct v2f {
				float4 pos : SV_POSITION;//输出给剪裁空间的顶点坐标
				float3 worldNormal : TEXCOORD0;//世界坐标系法线
				float3 worldPos : TEXCOORD1;//世界坐标系坐标
			};
			v2f vert(a2v v) {
				v2f f;
				f.pos = UnityObjectToClipPos(v.vertex);//模型空间到剪裁空间转换
				f.worldNormal = UnityObjectToWorldNormal(v.normal);//模型空间到世界坐标系法线
				f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//模型空间到世界坐标系位置
				return f;
			}
			fixed4 frag(v2f f) : SV_Target{
				fixed3 worldNormal = normalize(f.worldNormal);//单位化
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//获取光照方向 并单位化
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//环境光颜色
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));//漫反射颜色
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);//朝向相机的方向
				fixed3 halfDir = normalize(worldLightDir + viewDir);//h方向 用于计算高光反射补偿度
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);//高光反射
				fixed atten = 1.0;//光照衰减 在这个pass中是平行光 没有衰减
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			ENDCG
		}
	}
	//FallBack "Diffuse"
}
效果

关闭了fallBack 此时的效果没有阴影
在这里插入图片描述

阴影投射代码

我们需要定义一个pass,并使用Tags{ “LightMode” = “ShadowCaster” }来进行阴影投射的预处理

pass {
	Tags{ "LightMode" = "ShadowCaster" }
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#pragma multi_compile_shadowcaster
		#include "UnityCG.cginc"
		struct v2f {
		V2F_SHADOW_CASTER;
	};
	v2f vert(appdata_base v) {
		v2f f;
		TRANSFER_SHADOW_CASTER_NORMALOFFSET(f);
		return f;
	}
	float4 frag(v2f i) :SV_Target{
		SHADOW_CASTER_FRAGMENT(i);
	}
		ENDCG
}
效果

我们把阴影投射的pass加入到高光反射的代码中,物体恢复了阴影投射的效果.
另外需要注意的是此时的Plane使用的是默认的shader,里面有接收阴影的处理,如果我们使用的shader中没有接收阴影的代码,Plane将不接收阴影.
在这里插入图片描述

总结

我们通常只需要设置一个带有阴影投射的Fallback就可以实现阴影投射了,除非我们有更特别的需求,才会改写上面的代码.

阴影接收

阴影接收的步骤如下
1.声明阴影接收相关的函数库

#include "AutoLight.cginc"//阴影投射相关的宏在这个库中

2.在v2f中声明一个变量用于阴影纹理采样.

struct v2f {
	float4 pos : SV_POSITION;//输出给剪裁空间的顶点坐标 在阴影接收的宏中规定了pos这个字段 名称不可以改
	float3 worldNormal : TEXCOORD0;//世界坐标系法线
	float3 worldPos : TEXCOORD1;//世界坐标系坐标
	SHADOW_COORDS(2)//声明一个坐标用于阴影纹理采样 2为下一个可用的寄存器索引值 也就是TEXCOORD2
};

3.在vert函数中计算阴影纹理坐标

v2f vert(a2v v) {
	v2f f;
	f.pos = UnityObjectToClipPos(v.vertex);//模型空间到剪裁空间转换
	f.worldNormal = UnityObjectToWorldNormal(v.normal);//模型空间到世界坐标系法线
	f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//模型空间到世界坐标系位置
	TRANSFER_SHADOW(f);//计算阴影纹理坐标
	return f;
}

4.在frag函数中计算阴影值,并与高光反射,漫反射相乘

fixed4 frag(v2f f) : SV_Target{
	fixed shadow = SHADOW_ATTENUATION(f);//计算阴影值
	fixed3 worldNormal = normalize(f.worldNormal);//单位化
	fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//获取光照方向 并单位化
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//环境光颜色
	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));//漫反射颜色
	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);//朝向相机的方向
	fixed3 halfDir = normalize(worldLightDir + viewDir);//h方向 用于计算高光反射补偿度
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);//高光反射
	fixed atten = 1.0;//光照衰减 在这个pass中是平行光 没有衰减
	return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}

阴影接收完整代码

Shader "Unity/Custom/NewSurfaceShader" {
	Properties{
		_Diffuse("Diffuse", Color) = (1, 1, 1, 1)//漫反射颜色
		_Specular("Specular", Color) = (1, 1, 1, 1)//高光反射颜色
		_Gloss("Gloss", Range(8.0, 256)) = 20//光泽度
	}
	SubShader{
		Pass{
			Tags{ "LightMode" = "ForwardBase" }
			CGPROGRAM
			#pragma multi_compile_fwdbase//在forwardBase中获取正确的光照变量
			#pragma vertex vert//声明顶点处理方法
			#pragma fragment frag//声明片段处理方法
			#include "Lighting.cginc"//调用光照函数库
			#include "AutoLight.cginc"//阴影投射相关的宏在这个库中
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			struct a2v {
				float4 vertex : POSITION;//顶点的模型空间位置 在阴影接收的宏中规定了vertex这个字段 名称不可以改
				float3 normal : NORMAL;//模型空间法线
			};
			struct v2f {
				float4 pos : SV_POSITION;//输出给剪裁空间的顶点坐标 在阴影接收的宏中规定了pos这个字段 名称不可以改
				float3 worldNormal : TEXCOORD0;//世界坐标系法线
				float3 worldPos : TEXCOORD1;//世界坐标系坐标
				SHADOW_COORDS(2)//声明一个坐标用于阴影纹理采样 2为下一个可用的寄存器索引值 也就是TEXCOORD2
			};
			v2f vert(a2v v) {
				v2f f;
				f.pos = UnityObjectToClipPos(v.vertex);//模型空间到剪裁空间转换
				f.worldNormal = UnityObjectToWorldNormal(v.normal);//模型空间到世界坐标系法线
				f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//模型空间到世界坐标系位置
				TRANSFER_SHADOW(f);//计算阴影纹理坐标
				return f;
			}
			fixed4 frag(v2f f) : SV_Target{
				fixed shadow = SHADOW_ATTENUATION(f);//计算阴影值
				fixed3 worldNormal = normalize(f.worldNormal);//单位化
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//获取光照方向 并单位化
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//环境光颜色
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));//漫反射颜色
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldPos.xyz);//朝向相机的方向
				fixed3 halfDir = normalize(worldLightDir + viewDir);//h方向 用于计算高光反射补偿度
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);//高光反射
				fixed atten = 1.0;//光照衰减 在这个pass中是平行光 没有衰减
				return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值