Unity Shader -- 基于物理的渲染技术

总结自Unityshader入门精要

  • 一,光的传播
  • 二,双向反射分布函数(BRDF)
  • 三,实现一个基于物理渲染的shader
    • 基于以上公式编写的基于物理渲染的shader:

一,光的传播

光是一种电磁波,由光源发射出来,与物体相交,一部分被吸收转化为其他能量,而另一些被散射,最后被感应器(如眼睛)吸收成像
吸收:改变光的能量,不改变光的方向
散射:不改变光的能量,改变光的方向
影响光的一个重要的特质就是物体材质的折射率

二,双向反射分布函数(BRDF)

DRDF可以用f(l,v)来表示,l是入射方向,v是观察方向
一个直观的理解是:f(l,v)表示了当一束光线沿入射方向到达物体某个点时,有多少能量被反射到了观察方向v上
BRDF可以用来描述高光散射和漫反射
得到BRDF模型基本有两种方法:
1.使用光学仪器对现实中的材质进行测量
2.学术界根据光学理论分析出一些通用的BRDF模型

三,实现一个基于物理渲染的shader

简化后的渲染方程:
简化后的渲染方程
BRDF 的高光反射项:
BRDF 的高光反射项

基于以上公式编写的基于物理渲染的shader:

Shader "Hidden/CustomPBS"
{
    Properties
    {
		_Color("Color", Color) = (1, 1, 1, 1)
		_MainTex("Albedo", 2D) = "white" {}
		_Glossiness("Smoothness", Range(0.0, 1.0)) = 0.5
		_SpecColors("Specular", Color) = (0.2, 0.2, 0.2)
		_SpecGlossMap("Specular (RGB) Smoothness (A)", 2D) = "white" {}
		_BumpScale("Bump Scale", Float) = 1.0
		_BumpMap("Normal Map", 2D) = "bump" {}
		_EmissionColor("EmissionColor", Color) = (0, 0, 0)
		_EmissionMap("Emission", 2D) = "white" {}
    }
    SubShader
    {
		Tags{"RenderType"="Opaque"}
		LOD 300
		

        Pass
        {
			Tags{"LightMode"="ForwardBase"}

            CGPROGRAM
			#pragma target 3.0

            #pragma vertex vert
            #pragma fragment frag
			#pragma multi_compile_fwdbase
			#pragma multi_compile_fog
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			#include "UnityCG.cginc"
           

			sampler2D _MainTex;
			sampler2D _BumpMap;
			sampler2D _SpecGlossMap;
			sampler2D _EmissionMap;
			float4 _MainTex_ST;
			float4 _BumpMap_ST;
			float4 _SpecGlossMap_ST;
			float4 _EmissionMap_ST;

			float4 _Color;
			float3 _SpecColors;
			float4 _EmissionColor;
			float _BumpScale;
			float _Glossiness;

            struct a2v
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
            };

            struct v2f
            {
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
				float4 TtoW0 : TEXCOORD1;
				float4 TtoW1 : TEXCOORD2;
				float4 TtoW2 : TEXCOORD3;

				//注意使用宏定义时候后面不可以跟分号
				SHADOW_COORDS(4)//定义阴影变量  在AutoLight.cginc有定义

				UNITY_FOG_COORDS(5)//定义烟雾变量 在UnityCG.cginc有定义

				


            };

            v2f vert (a2v v)
            {
                v2f o;
				UNITY_INITIALIZE_OUTPUT(v2f, o);

                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv,_MainTex);

				float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
				float3 worldNormal = UnityObjectToWorldNormal(v.normal);
				float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);

				//次法线与法线切线构成坐标系
				fixed3 worldBinormal = cross(worldNormal, worldTangent)*v.tangent.w;

				//为了将从法线纹理中得到的切线空间下的法线向量转换到世界空间下
				//我们计算出了切线空间转世界空间的变换矩阵
				o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
				o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
				o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

				//计算阴影和雾效所需要的一些纹理参数
				TRANSFER_SHADOW(o);
				UNITY_TRANSFER_FOG(o, o.pos);
                return o;
            }

			//根据迪士尼的漫反射公式得到的漫反射方法
			inline half3 CustomDisneyDiffuseTerm(half NdotV,half NdotL, half LdotH, half roughness, half3 baseColor)
			{
				half fd90 = 0.5 + 2 * LdotH * LdotH * roughness;
				// Two schlick fresnel term
				half lightScatter = (1 + (fd90 - 1) * pow(1 - NdotL, 5));
				half viewScatter = (1 + (fd90 - 1) * pow(1 - NdotV, 5));
				return baseColor * UNITY_INV_PI * lightScatter * viewScatter;
			}

			inline half CustomSmithJointGGXVisibilityTerm(half NdotL, half NdotV, half roughness)
			{

				half a2 = roughness * roughness;
				half lambdaV = NdotL * (NdotV * (1 - a2) + a2);
				half lambdaL = NdotV * (NdotL * (1 - a2) + a2);
				return 0.5f / (lambdaV + lambdaL + 1e-5f);
			}

			inline half CustomGGXTerm(half NdotH, half roughness) {
				half a2 = roughness * roughness;
				half d = (NdotH * a2 - NdotH) * NdotH + 1.0f;
				return UNITY_INV_PI * a2 / (d * d + 1e-7f);
			}

			inline half3 CustomFresnelTerm(half3 c, half cosA) {
				half t = pow(1 - cosA, 5);
				return c + (1 - c) * t;
			}

			inline half3 CustomFresnelLerp(half3 c0, half3 c1, half cosA) {
				half t = pow(1 - cosA, 5);
				return lerp(c0, c1, t);
			}

            fixed4 frag (v2f i) : SV_Target
            {

				//首先为后续计算准备好所有的输入数据

				half4 specGloss = tex2D(_SpecGlossMap,i.uv);
				specGloss.a *= _Glossiness;
				half3 specColor = specGloss.rgb*_SpecColor.rgb;
				half roughness = 1 - specGloss.a;
				//1-反射率
				half3 oneMinusReflectivity = 1 - max(max(specColor.r, specColor.g), specColor.b);

				half3 diffColor = _Color.rgb*tex2D(_MainTex, i.uv).rgb*oneMinusReflectivity;

				//转换得到切线空间下的法线
				half3 normalTangent = UnpackNormal(tex2D(_BumpMap, i.uv));
				normalTangent.xy *= _BumpScale;
				normalTangent.z = sqrt(1-saturate(dot(normalTangent.xy, normalTangent.xy)));
				//得到世界空间下的法线
				half3 normalWorld = normalize(float3(dot(i.TtoW0.xyz, normalTangent), dot(i.TtoW1.xyz, normalTangent), dot(i.TtoW2.xyz, normalTangent)));

				//在定点着色器中我们计算出了顶点的世界坐标并放在了转换矩阵的w变量上
				//在此我们直接使用避免再次计算损耗性能
				float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
				half3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
				half3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				//通过flect方法计算出反射方向
				half3 reflDir = reflect(-viewDir, normalWorld);

				//计算出阴影和光照的衰减值atten 定义自AutoLight.cginc
				UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

				//在计算BRDF光照模型之前,我们需要准备好各个角度的余弦值,用来计算点乘,如n·v等
				half3 halfDir = normalize(lightDir + viewDir);
				//saturate(使充满,使饱和),使结果取值在0-1之间
				half nv = saturate(dot(normalWorld, viewDir));
				half nl = saturate(dot(normalWorld, lightDir));
				half nh = saturate(dot(normalWorld, halfDir));
				half lv = saturate(dot(lightDir, viewDir));
				half lh = saturate(dot(lightDir, halfDir));

				
				half3 diffuseTerm = CustomDisneyDiffuseTerm(nv, nl, lh, roughness, diffColor);

			


				//接下来实现高光反射项
				//首先是可见性项 V,它计算的是阴影-遮掩函数除以高光反射项的分母部分后的结果。
				half V = CustomSmithJointGGXVisibilityTerm(nl, nv, roughness);
				//接下来是法线分布项 D,CustomGGXTerm 函数的实现(依照基于 GGX 模型的法线分布函数)
				half D = CustomGGXTerm(nh, roughness * roughness);
				//最后是菲涅耳反射项 F,CustomFresnelTerm 函数(依照 Schlick 菲涅耳近似等式[7])
				half3 F = CustomFresnelTerm(specColor, lh);
				//三个结果相乘就是最终的高光反射
				half3 specularTerm = F * V * D;

				

				//接下来计算自发光
				half emissionTerm = tex2D(_MainTex, i.uv).rgb*_EmissionColor.rgb;

				//为了效果更加真实,我们还要计算基于图像的光照部分
				half perceptualRoughness = roughness * (1.7 - 0.7 * roughness);
				half mip = perceptualRoughness * 6;
				//Defined inHLSLSupport.cginc
				half4 envMap = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflDir, mip); 
				half grazingTerm = saturate((1 - roughness) + (1 - oneMinusReflectivity));
				half surfaceReduction = 1.0 / (roughness * roughness + 1.0);
				half3 indirectSpecular = surfaceReduction * envMap.rgb * CustomFresnelLerp(specColor,grazingTerm, nv);
				

				//最后我们将所有结果相加就得到了最终效果

				half3 col = emissionTerm + UNITY_PI * (diffuseTerm + specularTerm) * _LightColor0.rgb * nl * atten + indirectSpecular;
				UNITY_APPLY_FOG(i.fogCoord, c.rgb); // Defined in UnityCG.cginc
				return half4(col, 1);
            }
            ENDCG
        }
    }
}

你可能感兴趣的:(Unity,Shader)