Unity TA 学习笔记系列(1)- 《Unity Shader 入门精要》基础光照
一、前言
距离写下第一篇博客后过了挺久了,期间一直在忙项目,一直没啥时间做做技术整理或是巩固自己的基础。最近也算是告一段落,开始进行一些整理和查漏补缺,目前主要是分为开发框架和Shader两个方向,因为开发框架涉及知识点很杂,需要花点时间整理优化,于是就先从Shader开始写起。
本系列内容主要针对《Unity Shader入门精要》一书中所涉及的效果实现进行整理,第六章前面的基础知识暂时不做重点,后面会单开一篇进行整理,本篇会从第六章基础光照开始,整理其中涉及的知识点和代码实现。
二、基础光照
1. 标准光照模型
标准光照模型,也就是为人熟知的Blinn-Phong模型,其基本理念只关心光源直接照射到物体表面后经过一次反射进入摄像机的光线,它将进入摄像机的光线分为4个部分:环境光(ambient)、漫反射(diffuse)、高光反射(specular)和自发光(emissive)。
1.1 环境光
在标准光照模型中,使用环境光来近似模拟简介光照,通常为一个全局变量,场景中所有物体都使用该环境光。在Unity中,我们通过Window/Rendering/Lighting窗口中的Environment页面对场景的环境光进行修改。

1.2 漫反射
漫反射用于描述光被物体表面随机散射到各个方向。在漫反射中,由于反射完全随机,可以近似为在各反射方向上相同,因此视角位置并不重要。
1.2.1 兰伯特光照模型
顾名思义,该模型符合兰伯特定律,即反射光线强度与表面法线和光源方向间的夹角的余弦值成正比,因此入射光线的角度在漫反射中十分重要,我们可以用以下公式来描述漫反射:
其中,为表面法线,
是指向光源的单位向量,
是材质的漫反射颜色。同时,为防止物体被背后的光源照亮,需要保持法线和光源方向点乘结果非负。
1.2.2 半兰伯特光照模型
兰伯特光照模型存在背光区域明暗一样,失去了模型细节表现的缺点,半兰伯特光照模型是对其的一种改善技术,广义的半兰伯特光照模型公式如下:
相较于原公式,半兰伯特光照模型没有使用操作来避免赋值,而是对结果进行了缩放和偏移,大部分情况下
和
的值均为0.5,以此将点积范围从[-1,1]映射到[0,1]区间,使背光面也可以有明暗变化。
需要注意的是半兰伯特模型没有任何物理依据,只是一种视觉加强技术
1.3 高光反射
此处的高光反射是一种经验模型,不完全符合现实的高光反射现象,主要用于计算被完全镜面反射的光线,从而模拟材质的光泽效果。
高光反射通常涉及四个向量,表面法线、视角方向、光源方向和反射方向,其中反射方向可以通过其他信息计算获得,我们可以用以下公式来描述高光反射:
其中,是反射方向,
是材质的高光反射颜色,
是视角方向,
是材质的光泽度,所有向量均使用单位向量。与漫反射相同,此处也需要保证视角方向与反射方向的点乘结果非负。
为简化计算,Blinn对上面的公式进行了修改,通过视角方向和光源方向取平均后再归一化获得新的向量,从而规避计算反射方向
,计算公式如下:
对于上述两种算法,当摄像机和光源距离模型足够远时,和
可以视为定值,此时
是一个常量,Blinn模型效率更高,反之,Phong模型可能会更快。
1.4 逐像素和逐顶点
在Shader中,我们有两种方式来计算光照:在片元着色器中计算,称为逐像素光照;在顶点着色器中计算,称为逐顶点光照。
在逐像素光照中,会根据每个像素的法线(顶点法线插值或法线纹理采样获得)来进行光照计算,而对顶点法线进行插值的技术被称为Phong着色,也被称为Phong插值或法线插值着色技术,需要区别于Phong光照模型。
逐顶点光照,又被称为高洛德着色,则是在每个顶点上计算光照,然后在渲染图元内部进行线性插值,通常顶点数量远小于像素数目,因此计算量也会更小,但由于依赖线性插值获得像素光照,在进行光照中的非线性计算时(如高光反射)会出现问题,而且会导致图元内部颜色总暗于顶点处的最高颜色值,有时会产生明显的棱角现象。
2. Shader实现
由于《Unity Shader入门精要》原书使用的Unity版本较老,部分方法或变量在新版Unity中有所变更或废弃,可能会出现报错,本系列涉及代码均已进行了更改。
2.1 漫反射光照模型
2.1.1 逐顶点光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shader/Chapter6-DiffuseVertexLevel"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color : COLOR;
};
fixed4 _Diffuse;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
o.color = ambient + diffuse;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = fixed4(i.color, 1);
return col;
}
ENDCG
}
}
Fallback "Diffuse"
}
2.1.2 逐像素光照

Shader "Unity Shader/Chapter6-DiffusePixelLevel"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
fixed4 _Diffuse;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.normal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.normal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 col = ambient + diffuse;
return fixed4(col,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
2.1.3 半兰伯特模型
Shader "Unity Shader/Chapter6-HalfLambert"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
};
fixed4 _Diffuse;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.normal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.normal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 col = ambient + diffuse;
return fixed4(col,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
2.2 高光反射光照模型
2.2.1 逐顶点光照

Shader "Unity Shader/Chapter6-SpeculerVertexLevel"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8, 256)) = 20
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color : COLOR;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
fixed3 reflectDir = normalize(reflect(-worldLight, worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)),_Gloss);
o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = fixed4(i.color, 1);
return col;
}
ENDCG
}
}
Fallback "Specular"
}
2.2.2 逐像素光照

Shader "Unity Shader/Chapter6-SpecularPixelLevel"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8, 256)) = 20
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float3 worldPos : TEXCOORD1;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.normal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.normal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)),_Gloss);
fixed3 col = ambient + diffuse + specular;
return fixed4(col,1);
}
ENDCG
}
}
Fallback "Specular"
}
2.3 Blinn-Phong光照模型

Shader "Unity Shader/Chapter6-BlingPhong"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8, 256)) = 20
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal : NORMAL;
float3 worldPos : TEXCOORD1;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//o.normal = mul(v.normal, (float3x3)unity_WorldToObject);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.normal);
//fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal, halfDir)),_Gloss);
fixed3 col = ambient + diffuse + specular;
return fixed4(col,1);
}
ENDCG
}
}
Fallback "Specular"
}
三、总结
本篇笔记主要围绕基础光照章节展开,记录了光照模型背后的实现原理,并给出了在Unity中的Shader实现。光照作为渲染的基础,涉及法线、光照方向、视角方向等重要信息,巩固本章内容不仅是打下光照渲染的基础,更重要的是熟悉各种常用数据在Shader中的获取方式和转换方式,才能在后续的Shader学习中更加顺利。
作为第一篇正式的技术博客,我也在权衡文章的内容和篇幅,也希望各路大神给出一些建议,代码也均为本人手动复现,如有纰漏,还望指正。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)