Unity Shader基础-第一个Unity Shader程序

Unity Shader基础-第一个Unity Shader程序


1 基本概念


  • 着色:根据材质属性、光源信息,通过光照模型(Lighting Model)去计算沿某个观察方向的出射度的过程。

  • 逐顶点光照与逐像素光照:逐顶点光照在每个顶点上计算光照,再在渲染图元内部进行线性插值输出像素着色(Gouraud shading);逐像素光照以每个像素为基础进行光照计算(Phong shading);由于顶点数量远小于像素数量(光栅化为进行像素填充),因此逐顶点光照的计算复杂度往往小于逐像素光照,而逐顶点光照依赖于线性插值得像素光照,因此当有非线性计算时,逐顶点光照便会有问题,如出现锯齿。 对应在Shader编程中,逐顶点光照就在顶点着色器(vertex)中实现相应光照函数,逐像素光照就在片元着色器(fragment)中进行。

  • 纹理映射:将一张图映射到模型表面,通过纹理映射坐标(UV坐标)记录顶点在纹理中对应的2D坐标位置,通过(u,v)来表示,u表示横坐标,v表示纵坐标。Unity的纹理空间符合OpenGL传统,原点位于纹理左下角。


2 光照模型


标准光照模型只关心直接光照,也就是直接从光源发射出来照射到物体表面后,经过物体表面一次反射直接进入摄像机的光线,包含是个部分:自发光(Emissive)、高光反射(Specular)、漫反射(Diffuse)、环境光(Ambient)。

  • 环境光:标准光照模型中用环境光用于模拟间接光照(经过不只一个物体反射);
  • 自发光:不需要经过任何物体反射进入摄像机;
  • 漫反射:对物体表面随机散射到各个方向的辐射度进行建模,符合半伯特定律(Lambert’s law),反射光线强度与表面法线和淘汰方向之间的夹角的余弦值成正比。
  • 高光反射:当光线从光源照射到模型表面时,该表面在完全镜表反射方向散射出多少辐射量。

标准光照模型的公式为:

Color = Emissive + Ambient + Diffuse + Specular

2.1 环境光模型


环境光模型公式如下:

Ambient =Aintensity * AColor * Amaterial

参数说明:

  • Aintensity: 环境光强度
  • AColor:环境光颜色
  • Amaterial:物体对环境光各颜色成分的反射系数

2.2 漫反射模型


漫反射模型公式如下:

Diffuse = SColor * DColor * Max(0, dot(n, I))

参数说明:

  • SColor: 光源颜色
  • DColor:漫反射颜色
  • n:表面法线
  • I:光源方向)

备注:光线强度与表面法线与光源方向夹角的余弦值成正比(点乘),Max截取0,防止物体被后面来的光源照亮


2.3 高光反射模型


Phong高光反射模型:

Specular = SColor * SpColor * Max(0, pow(dot(v,r), MGloss))

r = 2(n*I) *n - I

参数说明:

  • SColor: 光源颜色
  • SpColor:高光反射颜色
  • MGloss: 光泽度
  • r: 反射方向
  • n:表面法线
  • v:视角方向
  • I:光照方向

Blinn高光反射模型:

Specular = SColor * SpColor * Max(0, pow(dot(n,h), MGloss))

h = (v+I) / |v+I|

参数说明:

  • SColor: 光源颜色
  • SpColor:高光反射颜色
  • MGloss: 光泽度
  • n:表面法线
  • v:视角方向
  • I:光照方向

3 纹理


3.1 基本纹理


最基本的纹理映射就是将一张贴图通过纹理坐标映射到模型表面,一个模型导出时,每个顶点包含其纹理坐标及模型空间坐标,自然而然就可以用不同的贴图来映射。

以Unity的内置表面着色器为例:

UnitySurfaceShader

可通过Tiling设置平铺粒度,Offset设置纹理起始偏移值。


3.2 高度纹理与法线纹理


凹凸映射主要用来实现模型表面凹凸不平的效果,原理是都是通过修改法线的方式来改变光照进而表现出来,高度纹理与法线纹理就是其中两种方式。可参考:Unity Shader-法线贴图(Normal)及其原理

  • 高度纹理: 高度图存储的是模型表面强度值,颜色越深表示越向里凹,反之则向外凸。

  • 法线纹理:通过存储表面的法线方向,来描述凹凸程度。法线纹理可以使用模型空间切线空间,法线值范围为[-1,1],通过加一除二的方法映射成颜色值[0,1]。模型空间存储绝对法线信息,与顶点坐标空间在同个空间,计算量少,简单直观。切线空间则以顶点自身为坐标系原点,所以每个顶点有自己的切线空间,以顶点所在平面的法线方向为Z轴,以uv坐标展开轴作为X轴(切线)和Y轴(次法线),通过(u,v)、(u,v’)、(u’,v)三点即可构成这两个轴,但是这种情况X与Y轴可能垂直,因此一般用法线与切线的叉积作为Y轴。


3.3 渐变纹理与遮罩纹理


  • 渐变纹理:通过纹理值来控制漫反射光照的结果,可得到渐变效果。

  • 遮罩纹理:通过纹理值来控制某些区域的属性值不被修改或控制修改程度,这样可以以达到更细腻的效果。


4 第一个Unity Shader程序

基于前面的章节,开始书写第一个Unity Shader程序:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/OneTextureShader"
{
	//属性声明
	Properties{
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
		_DiffuseFactor("_DiffuseFactor", Range(0, 256)) = 1
		_SpecularFactor("_SpecularFactor", Range(0, 256)) = 1
	}
	//着色器
	SubShader{
		Pass{
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			//顶点着色器定义
			#pragma vertex vert
			//片元/像素着色器定义
			#pragma fragment frag

			#include "Lighting.cginc"
			
			//在CG代码中,需要定义一个与属性名称与类型都匹配的变量
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Specular;
			float _Gloss;	
			float _DiffuseFactor;
			float _SpecularFactor;

			//通过结构体定义顶点着色器输入
			struct a2v{
				//POSTION语义:使用模型空间顶点坐标填充vertex变量
				float4 vertex : POSITION; 
				//NORMAL语义:使用模型空间法向量填充normal变量
				float3 normal : NORMAL; 
				//TEXCOORD0语义:使用模型第一套纹理坐标填充texcoord变量
				float4 texcoord : TEXCOORD0; 
			};

			struct v2f{
				//SV_POSTION语义:pos存储了裁剪空间中顶点位置信息
				float4 pos : SV_POSITION; 
				//TEXCOORD0语义:使用模型第一套纹理坐标填充worldNormal变量
				float3 worldNormal : TEXCOORD0;
				//TEXCOORD1语义:使用模型第二套纹理坐标填充worldPos变量
				float3 worldPos : TEXCOORD1;
				//TEXCOORD2语义:使用模型第三套纹理坐标填充uv变量
				float2 uv : TEXCOORD2;
			};


			//SV_POSITION语义:顶点着色器的输出作为裁剪空间顶点坐标
			v2f vert(a2v v){
				v2f o;
				//将坐标转换到裁剪空间,乘以MVP矩阵,模型空间-世界空间-观察空间-裁剪空间
				o.pos = UnityObjectToClipPos (v.vertex);
				//将模型空间法向量转换至世界法向量, ==normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				o.worldNormal = UnityObjectToWorldNormal(v.normal); 
				//将模型顶点转换至世界空间坐标
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				//获取纹理坐标 ==o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; (xy为纹理缩放,zw为平移)
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				
				return o;
			}

			//SV_Target语义:像素着色器的输出存储到一个渲染目标(RenderTarget)当中
			fixed4 frag(v2f i) : SV_Target{		
			    //精度转换
				fixed3 worldNormal = normalize(i.worldNormal);
				//世界光照的方向
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));				
				//采样颜色值
				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				//环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;	
				//漫反射
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				//世界空间观察方向(视角方向=相同位置-模型位置),内置函数:UnityWorldSpaceViewDir(i.worldPos)
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				//光照模型
				return fixed4(ambient + diffuse  * _DiffuseFactor + specular * _SpecularFactor, 1.0);
			}
			ENDCG
		}
	}
}

下图中左边为以上代码实现的效果,右图为Unity的内置Shader效果。

FirstShaderDemo


参考



Tags: Unity Shader Vistied:
Share: Twitter Facebook LinkedIn