着色:根据材质属性、光源信息,通过光照模型(Lighting Model)去计算沿某个观察方向的出射度的过程。
逐顶点光照与逐像素光照:逐顶点光照在每个顶点上计算光照,再在渲染图元内部进行线性插值输出像素着色(Gouraud shading);逐像素光照以每个像素为基础进行光照计算(Phong shading);由于顶点数量远小于像素数量(光栅化为进行像素填充),因此逐顶点光照的计算复杂度往往小于逐像素光照,而逐顶点光照依赖于线性插值得像素光照,因此当有非线性计算时,逐顶点光照便会有问题,如出现锯齿。 对应在Shader编程中,逐顶点光照就在顶点着色器(vertex)中实现相应光照函数,逐像素光照就在片元着色器(fragment)中进行。
纹理映射:将一张图映射到模型表面,通过纹理映射坐标(UV坐标)记录顶点在纹理中对应的2D坐标位置,通过(u,v)来表示,u表示横坐标,v表示纵坐标。Unity的纹理空间符合OpenGL传统,原点位于纹理左下角。
标准光照模型只关心直接光照,也就是直接从光源发射出来照射到物体表面后,经过物体表面一次反射直接进入摄像机的光线,包含是个部分:自发光(Emissive)、高光反射(Specular)、漫反射(Diffuse)、环境光(Ambient)。
标准光照模型的公式为:
Color = Emissive + Ambient + Diffuse + Specular
环境光模型公式如下:
Ambient =Aintensity * AColor * Amaterial
参数说明:
漫反射模型公式如下:
Diffuse = SColor * DColor * Max(0, dot(n, I))
参数说明:
备注:光线强度与表面法线与光源方向夹角的余弦值成正比(点乘),Max截取0,防止物体被后面来的光源照亮
Phong高光反射模型:
Specular = SColor * SpColor * Max(0, pow(dot(v,r), MGloss))
r = 2(n*I) *n - I
参数说明:
Blinn高光反射模型:
Specular = SColor * SpColor * Max(0, pow(dot(n,h), MGloss))
h = (v+I) / |v+I|
参数说明:
最基本的纹理映射就是将一张贴图通过纹理坐标映射到模型表面,一个模型导出时,每个顶点包含其纹理坐标及模型空间坐标,自然而然就可以用不同的贴图来映射。
以Unity的内置表面着色器为例:
可通过Tiling设置平铺粒度,Offset设置纹理起始偏移值。
凹凸映射主要用来实现模型表面凹凸不平的效果,原理是都是通过修改法线的方式来改变光照进而表现出来,高度纹理与法线纹理就是其中两种方式。可参考:Unity Shader-法线贴图(Normal)及其原理
高度纹理: 高度图存储的是模型表面强度值,颜色越深表示越向里凹,反之则向外凸。
法线纹理:通过存储表面的法线方向,来描述凹凸程度。法线纹理可以使用模型空间或切线空间,法线值范围为[-1,1],通过加一除二的方法映射成颜色值[0,1]。模型空间存储绝对法线信息,与顶点坐标空间在同个空间,计算量少,简单直观。切线空间则以顶点自身为坐标系原点,所以每个顶点有自己的切线空间,以顶点所在平面的法线方向为Z轴,以uv坐标展开轴作为X轴(切线)和Y轴(次法线),通过(u,v)、(u,v’)、(u’,v)三点即可构成这两个轴,但是这种情况X与Y轴可能垂直,因此一般用法线与切线的叉积作为Y轴。
渐变纹理:通过纹理值来控制漫反射光照的结果,可得到渐变效果。
遮罩纹理:通过纹理值来控制某些区域的属性值不被修改或控制修改程度,这样可以以达到更细腻的效果。
基于前面的章节,开始书写第一个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效果。