简单的builltin下的PBRshader手撸练习——UnityShader学习笔记

自言自语

又是很久没有写笔记了。因为一直在啃PBR。总算在昨晚算是啃通了一遍。不过这只是个简单的builltin下的相对简单的PBR框架的shader 不过对于我来说也算是一个阶段性总结吧。 shader是metallic work flow 金属度工作流的。 模型和贴图都是我几年前做的一个东西,那会儿记得刚开始接触SP,瞎捣鼓一通,昨晚翻箱倒柜弄出来,换上自己刚写顺的shader,因为自己做美术这么些年所谓次时代的作品实在太少了。不管怎样,自己做的模型,贴图,配上自己写的shader,算是小功告成哈哈。(尽管公式都是抄,毛病也不少,总归让我有继续坚持下去的小动力了)

一、截图效果

因为优快云没办法上传视频,所以。。只能截图啦
单一平行光照单一平行光照

在这里插入图片描述平行光加点光源

二、shader

Shader "TNShaderPractise/ShaderPractise_PBR"
{
	Properties 
	{
		//所用贴图放一起
		[Header(Texutrue)]
		_Albedo("颜色贴图",2D) = "white"{}
		_Metallic("金属度贴图",2D) = "white"{}
		[Normal]_Normal("法线贴图",2D) = "bump"{}
		_Occlusion("环境遮蔽贴图",2D ) ="white"{}
		//调节参数放一起
		[Space(20)][Header(Parameter)]
		[HDR]_Color ("主体颜色",Color) = (1,1,1,1)
		_MetallicValue("金属度",Range(0,1.0))= 1
		_Roughness ("粗糙度",Range(0,1))=1
		//自己瞎编的一个参数
		_GGX("迪士尼拖尾控制",Range(0.001,8))=2
		_NormalScale("法线贴图强度",Range(-5,5))=1
		_OcclusionStrenth("AO强度",Range(0,1))=0
		//自发光算是另类 单独放
		[Space(20)][Header(Emission)]
		_EmissiveTex ("自发光贴图",2D) ="black"{} 
		[HDR]_Emissive ("自发光颜色",Color)=(0,0,0,1)
	}

	SubShader
	{
		Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }
		//单一平行光照pass
		pass
		{
			Tags {"LightMode" = "ForwardBase" }

			ZWrite On
			Cull Back 
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_fwdbase
			#include "Lighting.cginc"
			#include "UnityCg.cginc"
			#include "AutoLight.cginc"
			#include "UnityPBSLighting.cginc"
			#include "HLSLSupport.cginc"

			struct a2v 
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};
			struct v2f 
			{
				float4 pos : SV_POSITION;
				float4 TtoW1 : TEXCOORD1;
				float4 TtoW2 : TEXCOORD2;
				float4 TtoW3 : TEXCOORD3;
				float2 uv : TEXCOORD4;
				//阴影宏必须
				LIGHTING_COORDS(5,6)
				UNITY_FOG_COORDS(7)

			};

			sampler2D _Albedo;
			sampler2D _Metallic;
			sampler2D _Normal;
			sampler2D _Occlusion;
			sampler2D _EmissiveTex;
			float4 _Color;
			float4 _Emissive;

			float _MetallicValue;
			float _Roughness;
			float _NormalScale;
			float _OcclusionStrenth;

			float _GGX;

			v2f vert (a2v v)
			{
				v2f o;
				UNITY_INITIALIZE_OUTPUT(v2f,o);
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				float3 posWS = mul(unity_ObjectToWorld,v.vertex).xyz;
				float3 Normal = normalize(UnityObjectToWorldNormal(v.normal));
				float3 tangent = normalize(UnityObjectToWorldDir(v.tangent.xyz));
				float3 binormal = cross (Normal,tangent)*v.tangent.w;
				//构建TBN矩阵
				//其实也可以直接构建一个矩阵在片元着色器.新学的.URP下撸PBR的时候试试
				o.TtoW1 = float4 (tangent.x,binormal.x,Normal.x,posWS.x);
				o.TtoW2 = float4 (tangent.y,binormal.y,Normal.y,posWS.y);
				o.TtoW3 = float4 (tangent.z,binormal.z,Normal.z,posWS.z);


				//添加Unity内置雾影响
				UNITY_TRANSFER_FOG(o,o.pos);

				//阴影宏  
				TRANSFER_VERTEX_TO_FRAGMENT(o);
				return o;
			}


			-------------------------------------------------------------------------BRDF计算-------------------------------------------------------------
			//Diffuse Term  f diffuse(l,v) = baseColor / (UNITY_PI*lightF*viewF)
			//用inline来声明函数为 内联调用,减少函数因为调用频繁造成的开销
			inline float4 DiffuseTerm (float Roughness, float LH,float NL, float NV,float4 Albedo)
			{
				float Fd90 = 0.5+2*Roughness*LH*LH;
				float lightScatter = 1+(Fd90-1)*pow(1-NL,5);
				float viewScatter = 1+(Fd90-1)*pow(1-NV,5);
				return  Albedo *UNITY_INV_PI* lightScatter * viewScatter;  //UNITY_INV_PI = 1/3.1415926532
			}

			//Schlick Fresnel项
			inline float3 FresnelTerm (float3 Albedo,float3 SPC ,float LH)
			{
				float t = pow(1-LH,5);
				float3 F0 = SPC;
				return F0+(1-F0)*t;
			}

			//可见性项 把分母部分的4(nl)(nv)一与阴影遮掩函数 Shadowing-Masking Function 进行合并 组成可见性项 V  SmithJointGGXVisibilityTerm
			inline float CustomSmithJointGGXVisibilityTerm ( float NL,float NV, float R)
			{
				//错误修正 去掉多余平方
				//float a = R*R;
				float a2 = R*R;
				float lambedV = NV*(NL*(1-a2)+a2);
				float lambedL = NL*(NV*(1-a2)+a2);
				//1e-5f 科学计数 1e-5的意思就是1乘以10的负5次幂。就是0.000001
				return 0.5/(lambedV+lambedL+1e-5f);
			}

			//正向法线分布函数 Normal Distribution Function
			inline float GGXDistribution (float R,float NH)
			{
				//错误修正 去掉多余平方
				//float a = R*R;
				float a2 = R*R;
				float d = (a2-1)*NH*NH+1;
				float t = UNITY_PI*pow(d,_GGX);
				return  a2/t;
			}
			//用于做IBL矫正的颜色插值函数
			inline half4 CustomFresnelLerp (float4 C1, float4 C2, float NV)
			{
				//C1 为高光颜色非高光项
				half t = pow(1-NV,5);
				return lerp (C1,C2,t);
			}
			-------------------------------------------------------------------------BRDF计算-------------------------------------------------------------

			float4 frag (v2f i):SV_Target
			{
				//世界空间下的顶点
				float3 posWS = float3 (i.TtoW1.w,i.TtoW2.w,i.TtoW3.w);

				//准备采样的贴图
				float4 albedo = tex2D(_Albedo,i.uv);
				float4 occlusionTex = tex2D(_Occlusion,i.uv);
				//金属度贴图采样,采样得到的r通道为金属度 通用SP流程是把金属度放在R通道的 当然可以自己改
				//一般来说金属度纹理的R通道中 白色表示金属 黑色表示非金属 一开始我也不知道自己怎么记得给记反了所以效果总是不对...
				float metallic = tex2D (_Metallic,i.uv).r*_MetallicValue;
				做掠射角系数 以此来得到更好的菲涅耳反射效果
				//非金属部分高光相对较弱但也不是完全没有,所以是个恒定的值0.04 相关材质数值查找表可以去unity或者substance上查找 知乎上也有,就是那张有名的图
				float4 specularCol = lerp(0.04,albedo,metallic);
				//这一步操作是为了利用金属度贴图 来区分金属部分和非金属部分 金属部分漫反射为0光线都被金属材质吸收了很少反射或者不反射 非金属部分则正常漫反射表现
				float oneMinusReflectivityFromMetallic = 0.96*(1-metallic);//根据URP计算更新为乘以0.96f
				//这里我把AO单独做为一张图塞进来 为了方便控制AO 其实可以做的更细一点 调节AO范围及颜色. 但都是小trick 这里就不写了 这个shader只是为了总体了解PBR的流程和shader框架
				albedo = lerp(albedo,albedo*occlusionTex,_OcclusionStrenth)*oneMinusReflectivityFromMetallic*_Color;
				
				//粗糙度贴图放在金属度纹理的A通道上.这个根据贴图导出设置,可以在计算正向法线分布函数的时候	注意其的指数是否需要再平方
				//做个取反操作方便视觉上调整 以达到 _Roughness越大 越光滑 越小越粗糙
				float roughness =tex2D (_Metallic,i.uv).a*max(0.02,(1-_Roughness));

				//准备向量
				float3 bump = UnpackNormal (tex2D(_Normal,i.uv));
				bump.xy *= _NormalScale;
				//法线贴图的B通道意义不大 因为Z轴还是要通过xy来计算得到. 如果纹理资源紧张,可以利用法线贴图的b通道和a通道来存放一些纹理信息供我们实现更多的需求
				bump.z = sqrt(1-saturate(dot(bump.xy,bump.xy)));
				//TBN计算世界空间法线贴图信息,URP shader中我会直接用3x3矩阵构建TBN
				float3 normal = normalize( half3 (dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump),dot(i.TtoW3.xyz,bump)));
				float3 lightDir = normalize(UnityWorldSpaceLightDir(float4(posWS,1)));
				float3 viewDir = normalize(UnityWorldSpaceViewDir(float4(posWS,1)));
				float3 halfDir = normalize(lightDir+viewDir);
				float3 reflDir = normalize(reflect(-viewDir,normal));

				//准备点乘变量
				float nl = saturate(dot(normal,lightDir))*0.5+0.5;
				float nv = saturate(dot(normal,viewDir));
				float nh = saturate(dot(normal,halfDir));
				float lh = saturate (dot(halfDir,lightDir));
				float lv = saturate(dot(lightDir,viewDir));


				//准备灯光衰减系数
				UNITY_LIGHT_ATTENUATION (atten,i,posWS);

				//漫反射项 Diffuse term (Bidirectional Reflectance Distribution Function)
				float4 diffCol = DiffuseTerm (roughness,lh,nl,nv,albedo);

				//高光反射项 Specular term 
				float3 F = FresnelTerm (albedo.rgb,specularCol.rgb,lh);//新的间歇使用NV 
				float   V = CustomSmithJointGGXVisibilityTerm(nl,nv,roughness);
				float   D = GGXDistribution(roughness,nh);
				//f(l,v)= FGD/(4*nl*nv) ===> V = G/nl*vn ===> f(l,v)= FVD/4 ===>  f(l,v)= FVD*0.25 	   因为可见性项V中已经进行了 除以4的操作 所以后边直接输出FVD相乘
				float4 SpecularCol =float4(F * V * D ,1);

				//自发光项 Emissive Term
				float4 Emissive = tex2D(_EmissiveTex,i.uv)*_Emissive;

				//IBL Term (Image Based Lighting)	环境光部分的计算 氛围高光反射项 和漫反射项两部分	
				首先是高光反射项
				half perceptualRoughness = roughness*(1.7-0.7*roughness);
				half mip = perceptualRoughness*6;
				天空球和反射探针材质声明专用方式 采用Unity内置的 UNITY_DECLARE_TEXCUBE 宏来实现. HLSLSupport.cginc文件中定义
				//unity_SpecCube0 可以在 UnityShaderVariables.cginc中找到其声明
				//求得的环境贴图若开启了HDR 则要进行解码。测试发现不解码 反射探针不能参数调节不能实时传递
				half3 envMap = DecodeHDR(UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0,reflDir,mip),unity_SpecCube0_HDR);
				half grazingTerm = saturate((1-roughness)+1-oneMinusReflectivityFromMetallic);
				half surfaceReduction = 1/(roughness*roughness+1);
				half4 indirectSpecular = surfaceReduction*float4(envMap,1)*CustomFresnelLerp(specularCol,grazingTerm,nv);

				IBL的漫反射想部分. 因为球谐光照计算比较复杂,懒得敲了.也就直接伸手党,直接用Unity的ShaderSH9函数进行套用
				//这里的球谐函数计算是3阶球谐函数,而SP里的是2阶球谐函数,所以两遍效果是有差异的,要想尽可能减少表现差异,需要进行look dev 矫正
				//冯乐乐在UWA中有一篇look dev 矫正文章,付费查看,我买了,看了. 之前没看懂,现在还没找机会去实践.
				float4 indirectDiffuse = float4(ShadeSH9(float4(normal,1)),1)*albedo;

				//最后输出的颜色	 直接光照部分的漫反射项和高光反射项相加 乘以直接光照和阴影衰减系数再乘以π和NL 这就是图形大佬写出的光照方程.最后再把额外的环境光照和自发光也加上就成了
				float4 col = Emissive+UNITY_PI*( diffCol+SpecularCol)*_LightColor0*nl*atten+indirectDiffuse +indirectSpecular;
				//混合雾效
				UNITY_APPLY_FOG(i.fogCoord,col.rgb);

				//return albedo;
				return col;
			}

			ENDCG
		}
		//这部分是其他光照pass 包括额为的点光 平行光 等等
		pass
		{
		//额外光照部分的LightMode 需要 设置好标签
			Tags {"LightMode" = "ForwardAdd" }
			 //并且要注意设置混合模式. 不然会有灯光表现错误
			Blend One One
		
			Cull Back 
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			//这里的pragma也要注意 compile_multi_fwdadd
			#pragma multi_compile_fwdadd
			#include "Lighting.cginc"
			#include "UnityCg.cginc"
			#include "AutoLight.cginc"
			#include "UnityPBSLighting.cginc"
			#include "HLSLSupport.cginc"
		   //其他部分和直接光照部分一样,复制粘贴。 然后去掉已经在直接光照部分计算过的 自发光, 环境光  即可。 阴影衰减由于每个光源不atten的值不同 所以仍然要计算
			struct a2v 
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;

			};
			struct v2f 
			{
				float4 pos : SV_POSITION;
				float4 TtoW1 : TEXCOORD1;
				float4 TtoW2 : TEXCOORD2;
				float4 TtoW3 : TEXCOORD3;
				float2 uv : TEXCOORD4;
				LIGHTING_COORDS(5,6)
			};

			sampler2D _Albedo;
			sampler2D _Metallic;
			sampler2D _Normal;
			sampler2D _Occlusion;
			sampler2D _EmissiveTex;
			float4 _Color;
			float4 _Emissive;

			float _MetallicValue;
			float _Roughness;
			float _NormalScale;
			float _OcclusionStrenth;

			float _GGX;

			v2f vert (a2v v)
			{
				v2f o;
				UNITY_INITIALIZE_OUTPUT(v2f,o);
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				float3 posWS = mul(unity_ObjectToWorld,v.vertex).xyz;
				float3 Normal = normalize(UnityObjectToWorldNormal(v.normal));
				float3 tangent = normalize(UnityObjectToWorldDir(v.tangent.xyz));
				float3 binormal = cross (Normal,tangent)*v.tangent.w;

				o.TtoW1 = float4 (tangent.x,binormal.x,Normal.x,posWS.x);
				o.TtoW2 = float4 (tangent.y,binormal.y,Normal.y,posWS.y);
				o.TtoW3 = float4 (tangent.z,binormal.z,Normal.z,posWS.z);


				return o;
			}


			-------------------------------------------------------------------------BRDF计算-------------------------------------------------------------
			//Diffuse Term  f diffuse(l,v) = baseColor / (UNITY_PI*lightF*viewF)
			//用inline来声明函数为 内联调用,减少函数因为调用频繁造成的开销
			inline float4 DiffuseTerm (float Roughness, float LH,float NL, float NV,float4 Albedo)
			{
				float Fd90 = 0.5+2*Roughness*LH*LH;
				//float4 diff =albedo*UNITY_INV_PI* (1+(Fd90-1)*pow(1-nl,5))*(1+(Fd90-1)*(pow(1-nv,5)));
				float lightScatter = 1+(Fd90-1)*pow(1-NL,5);
				float viewScatter = 1+(Fd90-1)*pow(1-NV,5);
				//return  Albedo /3.1415926532 * lightScatter * viewScatter;  //UNITY_INV_PI = 1/3.1415926532
				return  Albedo *UNITY_INV_PI* lightScatter * viewScatter;  //UNITY_INV_PI = 1/3.1415926532
			}

			//Schlick Fresnel函数 金属度工作流
			inline float3 FresnelTerm (float3 Albedo,float3 SPC ,float LH)
			{
				float t = pow(1-LH,5);
				//return SpecColor+(SpecColor-1)*t;
				float3 F0 = SPC;
				return F0+(1-F0)*t;
			}

			//可见性项 把分母部分的4(nl)(nv)一与阴影遮掩函数 Shadowing-Masking Function 进行合并 组成可见性项 V  SmithJointGGXVisibilityTerm
			inline float CustomSmithJointGGXVisibilityTerm ( float NL,float NV, float R)
			{
				float a = R*R;
				float a2 = a*a;
				float lambedV = NV*(NL*(1-a2)+a2);
				float lambedL = NL*(NV*(1-a2)+a2);
				return 0.5/(lambedV+lambedL+1e-5f);
			}

			//正向法线分布函数 Normal Distribution Function
			inline float GGXDistribution (float R,float NH)
			{
				float a = R*R;
				float a2 = a*a;
				float d = (a2-1)*NH*NH+1;
				float t = UNITY_PI*pow(d,_GGX);
				return  a2/t;
			}

			inline half4 CustomFresnelLerp (float4 C1, float4 C2, float NV)
			{
				half t = pow(1-NV,5);
				return lerp (C1,C2,t);
			}
			-------------------------------------------------------------------------BRDF计算-------------------------------------------------------------

			float4 frag (v2f i):SV_Target
			{
				//世界空间下的顶点
				float3 posWS = float3 (i.TtoW1.w,i.TtoW2.w,i.TtoW3.w);

				//准备采样的贴图
				float4 albedo = tex2D(_Albedo,i.uv);
				float4 occlusionTex = tex2D(_Occlusion,i.uv);
				float metallic = tex2D (_Metallic,i.uv).r*_MetallicValue;
				做掠射角系数 以此来得到更好的菲涅耳反射效果
				float4 specularCol = lerp(0.04,albedo,metallic);
				float oneMinusReflectivityFromMetallic = 1-metallic;
				albedo = lerp(albedo,albedo*occlusionTex,_OcclusionStrenth)*oneMinusReflectivityFromMetallic*_Color;
				
				
				float roughness =tex2D (_Metallic,i.uv).a*max(0.02,(1-_Roughness));
				//roughness = lerp(roughness,1-_Roughness,_Roughness);

				//准备向量
				float3 bump = UnpackNormal (tex2D(_Normal,i.uv));
				bump.xy *= _NormalScale;
				bump.z = sqrt(1-saturate(dot(bump.xy,bump.xy)));
				float3 normal = normalize( half3 (dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump),dot(i.TtoW3.xyz,bump)));
				#ifdef USING_DIRECTIONAL_LIGHT
				float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
				float3 lightDir = normalize(_WorldSpaceLightPos0.xyz -posWS);
				#endif
				float3 viewDir = normalize(UnityWorldSpaceViewDir(float4(posWS,1)));
				float3 halfDir = normalize(lightDir+viewDir);
				float3 reflDir = normalize(reflect(-viewDir,normal));

				//准备点乘变量
				float nl = saturate(dot(normal,lightDir));
				float nv = saturate(dot(normal,viewDir));
				float nh = saturate(dot(normal,halfDir));
				float lh = saturate (dot(halfDir,lightDir));
				float lv = saturate(dot(lightDir,viewDir));


				//准备灯光衰减系数
				UNITY_LIGHT_ATTENUATION(atten,i,posWS);

				//漫反射项 Diffuse term (Bidirectional Reflectance Distribution Function)
				float4 diffCol = DiffuseTerm (roughness,lh,nl,nv,albedo);

				//高光反射项 Specular term 
				float3 F = FresnelTerm (albedo.rgb,specularCol.rgb,lh);
				float   V = CustomSmithJointGGXVisibilityTerm(nl,nv,roughness);
				float   D = GGXDistribution(roughness,nh);
				//f(l,v)= FGD/(4*nl*nv) ===> V = G/nl*vn ===> f(l,v)= FVD/4 ===>  f(l,v)= FVD*0.25 
				float4 SpecularCol =max(0, float4(F * V * D ,1));


				//最后输出的颜色
				float4 col = UNITY_PI*( diffCol+SpecularCol)*_LightColor0*nl*atten;

				return col;
			}
			ENDCG
		}
	}

	//最后注意由于加入了阴影和投影的计算,所以仍然需要一个 ShadowCaster pass 来提供阴影渲染. 这里 我们只要FallBack一个带有此Pass的shader 即可实现调用 不用手打
	//不过如果需要进行 AlphaTest  和 Alphablend 这样的渲染需求  阴影pass 则需要手动进行修改 保证其投影和接受阴影的表现上的正确性
	FallBack "Diffuse"
}

总结

过几天看看能不能把URP下的 PBRshader给撸出来 加油吧 !

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值