参考知识点来源于: 人间自有韬哥在, 唐老狮,窗外听轩雨 , 呆呆敲代码的小Y little_fat_sheep, AitTech, DeepSeek, 百度, 豆包
目录
一、渲染管线
1.应用阶段
- 应用阶段(由CPU处理):为GPU渲染提供几何信息,输出渲染图元。
- 数据的准备:
- 剔除不需要的数据,如视锥体剔除、遮挡剔除、层级剔除等。
- 根据UI对象在Hierarchy面板深度值顺序(DFS深度优先搜索)设置渲染顺序,其余物体按离摄像机先近后远排队。
- 先将渲染数据从硬盘读取到主存,再把GPU渲染所需数据打包发给显存。
- 设置渲染状态:设置着色器、纹理、材质、灯光等渲染状态,即SetPassCall,告诉GPU用什么渲染模型,SetPassCall次数影响性能。
- 发送DrawCall:CPU调用图形API接口(如glDrawElements或DrawIndexedPrimitive)命令GPU对指定物体渲染,即DrawCall,告诉GPU使用哪个模型数据。
- 性能指标:
- DrawCall:CPU调用图形API命令GPU渲染的操作。
- SetPassCall:设置/切换一次渲染状态。
- Batch:加载数据到显存、设置渲染状态、CPU调用GPU渲染的过程,一个Batch至少包含一个DrawCall。
- 数据的准备:
2.几何阶段
- . 几何阶段(由GPU处理):处理几何相关绘制,将顶点坐标变换到屏幕空间。
- 顶点着色器:流水线第一个可编程阶段,输入顶点信息,每个顶点调用一次。主要工作是坐标转换(将顶点坐标从模型空间转换到齐次裁剪空间)和可选的逐顶点光照(计算输出顶点颜色值)。
- 裁剪:剔除摄像机视野外的物体。图元与摄像机关系有完全在视野内、部分在视野内、完全在视野外三种,完全在视野内的传递给下阶段,完全在视野外的舍弃,部分在视野内的进行裁剪,生成新顶点并舍弃外部顶点。
- 屏幕映射:将实际场景对象映射到屏幕上,对坐标进行放缩。
3.光栅化阶段
- . 光栅化阶段(由GPU处理):使用上阶段数据产生屏幕像素,渲染最终图像。
- 三角形设置:为光栅化提供计算信息,如计算三角形网格边的表达式,判断像素点是否被覆盖。
- 三角形遍历:遍历像素点,判断是否被三角网格覆盖,若覆盖则生成片元(包含屏幕坐标、深度值、法线、纹理等状态信息,由三角形顶点信息插值得到),输出片元序列。
- 片元着色器:可编程着色器阶段,输入是顶点信息插值结果,输出片元颜色值,重要技术是纹理采样(顶点着色器输出纹理坐标,经光栅化阶段插值得到片元纹理坐标),但仅影响单个片元。
- 逐片元操作(OpenGL)/输出合并阶段(DirectX):
- 决定可见性:进行深度测试、模板测试等,判断片元是否可见,通过所有测试的片元才能进入合并阶段。
- 模板测试:开启后,GPU读取模板缓冲区值与参考值比较,根据开发者指定比较函数判断片元是否舍弃,可根据测试结果修改模板缓冲区,常用于限制渲染区域。
- 深度测试:开启后,GPU比较片元深度值和深度缓冲区值,根据开发者设置的比较函数判断片元是否舍弃,通过测试后开发者可指定是否用片元深度值覆盖原有缓冲区深度值。
- 合并操作:通过测试的片元与颜色缓冲区颜色合并。不透明物体可关闭混合,片元颜色直接覆盖原有像素值;半透明物体需混合操作,GPU取出源颜色(片元着色器颜色)和目标颜色(颜色缓冲区颜色)混合。
- 提前测试:为提高性能,大多数GPU尽可能在片元着色器之前进行测试,如Unity渲染流水线中的Early-Z技术(提前深度测试),但可能与片元着色器操作冲突。
二、矩阵的几何意义
1. 平移
2. 旋转
3. 缩放
4.复合运算
- 在进行平移、旋转、缩放的复合运算时,绝大多数情况下,我们约定的变换顺序为:先缩放、再旋转、后平移
- 在进行x轴、y轴、z轴旋转的复合运算时绝大多数情况下,我们约定的变换顺序为:z->x->y,之后我们在Unity中进行Shader开发时,遵从这两个规则即可。
三、坐标空间的变换
变换顺序:模型空间→世界空间→观察空间→裁剪空间→屏幕空间
3.1.模型空间→世界空间
相对世界坐标系的位置 = 平移矩阵 * 旋转矩阵 * 缩放矩阵 * 模型坐标下的点的列矩阵
3.2.世界空间→观察空间
3.3.观察空间→裁剪空间
3.4.裁剪空间→屏幕空间
四、Unity Shader
1.基本结构
1.1顶点片元着色器
//Shader "着色器名字"
{
//第二部分
Properties
{
//材质面板上可以看到的属性
}
//第三部分
SubShader
{
//顶点-片段着色器 或 表面着色器 或 固定函数着色器
// 渲染标签
Tags {
"标签名1" = "标签值1"
"标签名2" = "标签值2"
// 可继续添加更多标签
}
// 渲染状态
// 渲染通道
Pass
{
// 第一个渲染通道
CGPROGRAM
//先通过编译指令指定实现顶点着色器的指定函数
#pragma vertex myVert
//先通过编译指令指定实现片元着色器的指定函数
#pragma fragment myFrag
//顶点着色器 回调函数
//POSITION 和 SV_POSITION是CG语言的语义
//POSITION:把模型的顶点坐标填充到输入的参数v当中
//SV_POSITION:顶点着色器输出的内容是裁剪空间中的顶点坐标
//如果没有这些语义来限定输入和输出参数的话,那么渲染器就完全不知道用户输入输出的是什么,就会得到错误的效果
float4 myVert(float4 v:POSITION):SV_POSITION
{
//mul是CG语言提供的矩阵和向量的乘法运算函数(就是一个内置的函数)
//UNITY_MATRIX_MVP 代表一个变换矩阵 是Unity内置的模型、观察、投影矩阵的集合
//UnityObjectToClipPos它的作用和之前的矩阵乘法是一样的,主要目的就是在进行坐标变换 只不过新版本将其封装起来了 使用更加方便
//return mul(UNITY_MATRIX_MVP,v);
return UnityObjectToClipPos(v);
}
//片元着色器 回调函数
//SV_Target:告诉渲染器,把用户输出颜色存储到一个渲染目标中,这里将输出到默认的帧缓存中
fixed4 myFrag():SV_Target
{
return fixed4(0, 1, 0, 1); //返回绿色
}
ENDCG
}
Pass
{
// 第二个渲染通道
}
// 可继续添加更多Pass
}
SubShader
{
//更加精简的版本
//目的是适配旧设备
}
// 可以有n个SubShader代码块
//第四部分
Fallback "备用的Shader"
}
1.2.表面着色器
//Shader "ShaderTeach/Lesson10_NewSurfaceShader"
{
//第二部分
Properties
{
//材质面板上可以看到的属性
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
//第三部分
SubShader
{
// 表面着色器
// 渲染标签
Tags {
"RenderType" = "Opaque"
}
// 渲染状态
LOD 200
// 渲染通道(表面着色器逻辑区域)
CGPROGRAM
// 相关编译指令
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
// 纹理采样器声明
sampler2D _MainTex;
// 输入结构体声明
struct Input
{
float2 uv_MainTex;
};
// 变量声明
half _Glossiness;
half _Metallic;
fixed4 _Color;
// 实例化相关区域
UNITY_INSTANCING_BUFFER_START(Props)
// 可放置更多实例属性
UNITY_INSTANCING_BUFFER_END(Props)
// 表面着色器函数声明(无具体实现内容)
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
//第四部分
Fallback "Diffuse"
}
2.Shader的属性
ShaderLab主要由Shader名字、属性、1至多个子着色器、备用Shader四部分构成。其中,Shader的属性作用关键,为提升Shader可调节性,将部分变量作为开放属性显示于材质面板,供使用者调节。它有两个特点:一是可在材质面板编辑;二是能作为输入变量供所有子着色器使用。
属性声明位于Shader语句块的Properties属性语句块中,语法为_Name("Display Name", type) = defaultValue[{options}]
,各部分含义如下:
_Name
:属性名,前加下划线便于获取。Display Name
:材质面板显示名。type
:属性类型。defaultValue
:指定给材质时的默认值。
属性主要分为三类:
- 数值类型
- 整形:
_Name("Display Name", Int) = number
。 - 浮点型:
_Name("Display Name", Float) = number
(Unity中虽有Int,但编译时会转Float,故多用Float)。 - 范围浮点型:
_Name("Display Name", Range(min,max)) = number
。
- 整形:
- 颜色和向量类型
- 颜色:
_Name("Display Name", Color) = (number1,number2,number3,number4)
,RGBA取值范围0 - 1(映射0 - 255)。 - 向量:
_Name("Display Name", Vector) = (number1,number2,number3,number4)
,XYZW取值无限制 。
- 颜色:
- 纹理贴图类型
2D纹理:最常用,如漫反射、法线贴图,
_Name("Display Name", 2D) = "defaulttexture" {}
。2DArray纹理:纹理数组,一般脚本创建,较少用,
_Name("Display Name", 2DArray) = "defaulttexture" {}
。Cube map texture纹理:立方体纹理,如天空盒、反射探针,
_Name("Display Name", Cube) = "defaulttexture" {}
。3D纹理:一般脚本创建,极少用,
_Name("Display Name", 3D) = "defaulttexture" {}
。关于 defaulttexture 默认值取值:
- 不写:默认贴图为空。
white
:默认白色贴图(RGBA: 1,1,1,1)。black
:默认黑色贴图(RGBA: 0,0,0,1)。gray
:默认灰色贴图(RGBA: 0.5,0.5,0.5,1)。bump
:默认凸贴图(RGBA: 0.5,0.5,1,1),一般用于法线贴图默认贴图。red
:默认红色贴图(RGBA: 1,0,0,1)。
3.Tags渲染标签
3.1.渲染队列 RenderQueue
- 作用:确定物体的渲染顺序。
- 语法:
Tags{ "Queue" = "标签值" }
- 常用预定义标签值及队列号:
Background
(背景,队列号:1000):最早被渲染的物体队列,常用于渲染天空盒或背景。示例:Tags{ "Queue" = "Background" }
Geometry
(几何,队列号:2000):不透明几何体默认队列,未声明渲染队列时,Unity 会默认使用。示例:Tags{ "Queue" = "Geometry" }
AlphaTest
(透明测试,队列号:2450):有透明通道且需进行 Alpha 测试的几何体使用,在所有 Geometry 队列实体绘制完后再绘制,效率更高。示例:Tags{ "Queue" = "AlphaTest" }
Transparent
(透明的,队列号:3000):半透明物体渲染队列,几何体按由远到近顺序绘制,如玻璃材质、粒子特效等。示例:Tags{ "Queue" = "Transparent" }
Overlay
(覆盖,队列号:4000):最后渲染的队列,用于叠加渲染效果,如镜头光晕等。示例:Tags{ "Queue" = "Overlay" }
- 自定义队列:基于预定义渲染队列标签进行加减运算定义,加减号中间不要有空格,不能在 Shader 中直接赋值数字,若需直接赋值可在材质面板设置。示例:
Tags{ "Queue" = "Geometry+1" }
队列号为 2001Tags{ "Queue" = "Transparent-1" }
队列号为 2999
3.2.渲染类型 RenderType
- 作用:对着色器进行分类,以便利用着色器替换功能,可通过摄像机 API 指定渲染类型替换其他着色器。
- 语法:
Tags{ "RenderType" = "标签值" }
- 常用预定义标签值:
Opaque
(不透明的):用于普通 Shader,如不透明、自发光、反射等。示例:Tags{ "RenderType" = "Opaque" }
Transparent
(透明的):用于半透明 Shader,如透明、粒子。示例:Tags{ "RenderType" = "Transparent" }
TransparentCutout
(透明切割):用于透明测试 Shader,如植物叶子。示例:Tags{ "RenderType" = "TransparentCutout" }
Background
(背景):用于天空盒 Shader。示例:Tags{ "RenderType" = "Background" }
Overlay
(覆盖):用于 GUI 纹理、Halo(光环)、Flare(光晕)。示例:Tags{ "RenderType" = "Overlay" }
- 了解即可的标签值:
TreeOpaque
、TreeTransparentCutout
、TreeBillboard
、Grass
、GrassBillboard
等,用于地形系统中的不同元素。
3.3.禁用批处理 DisableBatching
- 作用:解决使用批处理时,某些使用模型空间顶点数据的 Shader 无法实现预期效果的问题。
- 语法及取值:
Tags{ "DisableBatching" = "True" }
:总是禁用批处理Tags{ "DisableBatching" = "False" }
:不禁用批处理(默认值)Tags{ "DisableBatching" = "LODFading" }
:当 LOD(层级细节)效果激活时才禁用批处理,主要用于地形系统上的树
3.4.禁止阴影投影 ForceNoShadowCasting
- 作用:控制该 SubShader 的物体是否投射阴影。
- 语法及取值:
Tags{ "ForceNoShadowCasting" = "True" }
:不投射阴影Tags{ "ForceNoShadowCasting" = "False" }
:投射阴影(默认值)
3.5.忽略投影机 Projector IgnoreProjector
- 作用:确定物体是否受 Projector(投影机)投射影响。
- 语法及取值:
Tags{ "IgnoreProjector" = "True" }
:忽略 Projector,一般半透明 Shader 需要开启Tags{ "IgnoreProjector" = "False" }
:不忽略 Projector(默认值)
3.6.其他标签
- 是否用于精灵:
Tags{ "CanUseSpriteAtlas" = "False" }
- 预览类型:
Tags{ "PreviewType" = "Panel" }
:材质预览窗口从默认球形改为平面Tags{ "PreviewType" = "SkyBox" }
:材质预览窗口从默认球形改为天空盒
4.States渲染状态
4.1.剔除方式
- 作用:决定模型正面或背面是否被渲染。
- 语法及取值:
Cull Back
:背面剔除(默认)。Cull Front
:正面剔除。Cull Off
:不剔除。
4.2.深度缓冲
- 作用:控制是否写入深度缓冲,深度缓冲是与屏幕像素对应的缓冲区,存储每个像素的深度值。
- 语法及取值:
ZWrite On
:写入深度缓冲(默认)。ZWrite Off
:不写入深度缓冲,常用于做透明等特殊效果时。
4.3.深度测试
- 作用:确保像素按正确深度顺序绘制,创建正确的遮挡关系和透视效果。
- 测试流程:渲染前深度缓冲初始化为最大深度值,渲染时每个像素的深度值与深度缓冲对应位置的值比较。若当前像素深度值小于深度缓冲中的值,说明在其他物体之前,会被绘制并更新深度缓冲;若大于等于,则会被丢弃,不绘制且保持深度缓冲不变。
- 语法及取值:
ZTest Less
:小于当前深度缓冲中的值,通过测试并写入深度缓冲。ZTest Greater
:大于当前深度缓冲中的值,通过测试并写入深度缓冲。ZTest LEqual
:小于等于当前深度缓冲中的值,通过测试并写入深度缓冲(默认)。ZTest GEqual
:大于等于当前深度缓冲中的值,通过测试并写入深度缓冲。ZTest Equal
:等于当前深度缓冲中的值,通过测试并写入深度缓冲。ZTest NotEqual
:不等于当前深度缓冲中的值,通过测试并写入深度缓冲。ZTest Always
:始终通过深度测试写入深度缓冲。
4.4.混合方式
- 作用:设置渲染图像的混合方式,实现多种颜色叠加混合,如透明、半透明效果和遮挡物体的颜色混合。
- 混合流程:在深度测试之后进行,开始混合时先判断是否开启混合。开启混合则先得到源颜色(片元颜色)和目标颜色(颜色缓冲区中的颜色值),混合后更新颜色缓冲区的值;未开启则直接使用片元颜色值。经过多轮计算,最终留在颜色缓冲区的内容即为屏幕上看到的颜色。
- 语法及取值:
Blend One One
:线性减淡。Blend SrcAlpha OneMinusSrcAlpha
:正常透明混合。Blend OneMinusDstColor One
:滤色。Blend DstColor Zero
:正片叠底。Blend DstColor SrcColor
:X光片效果。Blend One OneMinusSrcAlpha
:透明度混合。(默认不进行混合)
4.5.其他渲染状态
- LOD:控制 LOD 级别,在不同距离下使用不同的渲染方式处理,如
LOD 100
。 - ColorMask:设置颜色通道的写入蒙版,默认蒙版为 RGBA ,如
ColorMask RG
表示只允许红色和绿色通道写入到帧缓冲区,蓝色和 alpha 通道被禁用。
5.Pass渲染通道
5.1.SubShader 与 Pass 的关系
SubShader:可将其理解为一组完整的渲染设置集合。在一个 Shader 中,能够存在多个 SubShader。每个 SubShader 都拥有一整套详细的物体渲染设定,例如 Tags 可用于指定渲染队列等关键信息,States 则可对混合模式、深度测试等进行设置。不同的 SubShader 能够适应不同的硬件条件以及特定的渲染需求,这使得开发者可以根据实际情况灵活选择合适的 SubShader 来实现理想的渲染效果。
Pass:是 SubShader 中的具体渲染步骤。一个 SubShader 中可以包含一个或多个 Pass。每个 Pass 都定义了一轮完整的渲染流程,涵盖了顶点和片元着色器的代码编写、纹理的运用以及各种渲染状态的设定等内容。通过在一个 SubShader 中合理运用多个 Pass,能够实现复杂的渲染效果。例如,为实现物体在不同光照条件下的显示效果,可创建一个 SubShader,并在其中包含两个 Pass,一个用于正常光照下的渲染,另一个用于特殊光照条件(如强光照射)下的渲染;或者为达成物体的立体效果,可在一个 SubShader 中通过多个 Pass 分别从不同角度渲染物体,然后将这些渲染结果进行合成,从而呈现出立体的视觉效果。
5.2.Pass 的相关内容
(1)Pass 的名字:对 Pass 进行命名的主要目的是方便在其他 Shader 中复用该 Pass 的代码。通过 UsePass 命令即可实现复用,使用时需在其他 Shader 中输入 UsePass “Shader 路径/Pass 名”。需要注意的是,Unity 内部会将 Pass 名称自动转换为大写字母,因此在使用 UsePass 命令时必须使用大写形式的名字。例如:
Pass
{
Name "MyLesson08Pass"
}
在其他 Shader 中复用该 Pass 代码时,则需写成:
UsePass "ShaderTeach/Lesson08_NewUnlitShader/MYLESSON08PASS"
(2)Pass 中的渲染标签:Pass 中的渲染标签语法虽然与 SubShader 中相同,但 SubShader 语句块中的渲染标签不能在 Pass 中使用,Pass 拥有自己专门的渲染标签。
Tags{ “LightMode” = “标签值” }:该标签主要用于指定 Pass 应在哪个阶段执行,通过合理设置标签值,能够将着色器代码分配到合适的渲染阶段,进而实现所需的效果。常见的标签值及其含义如下:
- Always:始终进行渲染,且不应用光照。
- ForwardBase:用于前向渲染,会应用环境光、主方向光、顶点/SH 光源和光照贴图。
- ForwardAdd:同样用于前向渲染,应用附加的每像素光源(每个光源对应一个通道)。
- Deferred:在延迟渲染中使用,主要用于渲染 G 缓冲区。
- ShadowCaster:将对象的深度渲染到阴影贴图或深度纹理中。
- MotionVectors:用于计算每对象的运动矢量。
- PrepassBase:在旧版延迟光照中使用,用于渲染法线和镜面反射指数。
- PrepassFinal:在旧版延迟光照中使用,通过组合纹理、光照和反光来渲染最终颜色。
- Vertex:当对象不进行光照贴图时,在旧版顶点光照渲染中使用,应用所有顶点光源。
- VertexLMRGBM:当对象不进行光照贴图时,在旧版顶点光照渲染中使用,适用于光照贴图为 RGBM 编码的平台(如 PC 和游戏主机)。
- VertexLM:当对象不进行光照贴图时,在旧版顶点光照渲染中使用,适用于光照贴图为双 LDR 编码的平台(如移动平台)。
Tags{ “RequireOptions” = “标签值” }:主要用于指定只有在满足某些条件时才渲染该 Pass。目前 Unity 仅支持 Tags{ “RequireOptions” = “SoftVegetation” },即仅当 Quality 窗口中开启了 SoftVegetation 时才会渲染此通道。
Tags{ “PassFlags” = “标签值” }:一个渲染通道 Pass 可通过该标签指示一些标志,从而更改渲染管线向 Pass 传递数据的方式。目前 Unity 仅支持 Tags{ “PassFlags” = “OnlyDirectional” },在 ForwardBase 向前渲染的通道类型中使用时,此标志的作用是仅允许主方向光和环境光/光照探针数据传递到着色器,这意味着非重要光源的数据将不会传递到顶点光源或球谐函数着色器变量。
(3)Pass 中的渲染状态:在 SubShader 语句块中学习的渲染状态同样适用于 Pass。例如,剔除方式决定了模型正面和背面是否能够被渲染;深度缓冲和深度测试决定了景深关系的确定以及透明效果的正确表达;混合方式决定了透明和半透明颜色的正确表现,以及一些特殊颜色效果的呈现。这些渲染状态都可以在单个 Pass 中进行设置。需要注意的是,如果在 SubShader 语句块中设置渲染状态,会影响之后的所有渲染通道 Pass;而如果在 Pass 语句块中设置,则只会影响当前 Pass 渲染通道,不会对其他 Pass 产生影响。此外,Pass 中还可以使用固定管线着色器的命令。
(4)GrabPass 命令:利用 GrabPass 命令可以将即将绘制对象时的屏幕内容抓取到纹理中,在后续通道中即可使用此纹理,从而执行基于图像的高级效果。例如,将绘制该对象之前的屏幕抓取到 _BackgroundTexture 中,可使用如下代码:
GrabPass
{
"_BackgroundTexture"
}
需要注意的是,该命令一般写在某个 Pass 前,在之后的 Pass 代码中可以利用 _BackgroundTexture 变量进行相应的处理。
五、CG语法
1.CG-基础数据类型
1.1.基础数据类型
- 整形:
uint
:32 位无符号整形。int
:32 位整形。
- 浮点型:
float
:32 位浮点数,后缀符号为f
。half
:16 位浮点数,后缀符号为h
。fixed
:12 位浮点数。
- 特殊类型:
bool
:布尔类型。string
:字符串(在 Shader 中很少使用,HLSL/Cg 中不直接支持,一般在高层语言中使用)。sampler
:通用的纹理采样器,可处理各种不同维度和类型的纹理。sampler1D
:用于一维纹理,如从左到右的渐变色。sampler2D
:用于二维纹理,常见的二维图像纹理,如贴图。sampler3D
:用于三维纹理,如体积渲染的体积纹理。samplerCUBE
:用于立方体纹理,常用于环境映射等需要立方体贴图的情况。samplerRECT
:用于处理矩形纹理,满足一些非标准的纹理映射需求。
1.2.基础复合数据类型
- 数组:与 C# 中类似。
- 一维数组:使用
int a[4]
形式声明,CG 语法无法通过Length
获取数组长度,需手动用变量记录,之后使用该变量遍历,如int aLength = 4
。 - 二维数组:使用
int b[2][3]
形式声明,同样需手动记录数组长度,如int bRowsLength = 2
,int bColsLength = 3
。
- 一维数组:使用
- 结构体:
- 与 C# 基本一样。
- 没有访问修饰符。
- 结构体声明结束需加分号。
- 一般在函数外声明。
1.3.特殊数据类型
向量基本构成
数据类型2 = 数据类型2(n1, n2)
数据类型3 = 数据类型3(n1, n2, n3)
数据类型4 = 数据类型4(n1, n2, n3, n4)
示例代码
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// float2 类型示例
float2 vec2 = float2(1.0, 2.0);
// fixed3 类型示例
fixed3 vec3 = fixed3(1.0, 2.0, 3.0);
// int4 类型示例
int4 vec4 = int4(1, 2, 3, 4);
return col;
}
矩阵基本构成
数据类型’n’x’m’ = {n1m1, n1m2, n1m3…..}
,为增加可读性,书写矩阵时最好手动换行。
** 示例代码**
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// half2x2 矩阵示例
half2x2 mat2x2 = half2x2(
1.0h, 2.0h,
3.0h, 4.0h
);
// fixed3x3 矩阵示例
fixed3x3 mat3x3 = fixed3x3(
1.0, 2.0, 3.0,
4.0, 5.0, 6.0,
7.0, 8.0, 9.0
);
// int4x4 矩阵示例
int4x4 mat4x4 = int4x4(
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
);
return col;
}
bool 类型特殊使用
bool 类型可像向量一样声明,用于存储逻辑判断结果。
示例代码
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
// bool 类型特殊使用示例
half3 a = half3(0.5h, 0.0h, 1.0h);
half3 b = half3(0.6h, -0.1h, 0.9h);
bool3 c = a < b;
// 结果为 bool3(true, false, false) 用ab两个向量的对应值比较
return col;
}
2.Swizzle操作符
作用:利用它来提取分量;利用它来重新排列分量;利用它来创建新向量。
示例代码
v2f vert(appdata v)
{
v2f o;
// 1.利用swizzle来提取分量
fixed4 f4 = fixed4(1, 2, 3, 4); // 创建一个包含四个固定点数的向量
fixed f = f4.w; //xyzw f=4
f = f4.a; //rgba f=4
// 2.利用它来重新排列分量
f4 = f4.yzxw; // 将f4的分量重新排列为yzxw
f4 = f4.abgr; // 将f4的分量重新排列为abgr
// 3.利用它来创建新的向量
fixed3 f3 = f4.xyz; // 包含f4的前三个分量
fixed2 f2 = f3.xz; // 包含f3的x和z分量
fixed4 f4_1 = fixed4(f2, 3, 4); // 包含f2一次次,即xz的值加上3和4
fixed4 f4_2 = fixed4(f2, f2); //包含f2两次,即xzxz
f4_2 = fixed4(f3, f); // 包含f3和f,即xyza
return o;
}
3.CG-运算符
比较运算符
- 大于
>
- 小于
<
- 大于等于
>=
- 小于等于
<=
- 等于
==
- 不等于
!=
条件运算符
// condition ? value_if_true : value_if_false
其中,condition
是一个条件表达式,若为真则返回 value_if_true
,否则返回 value_if_false
。CG 里条件运算符的使用和 C# 相同。
// 条件运算符
fixed f3 = f1 < f2 ? 4.5 : 6.7; // 变量值: 4.5 (因为 1 < 2)
逻辑运算符
- 逻辑或运算符
||
- 逻辑与运算符
&&
- 逻辑非运算符
!
CG 里逻辑运算符的使用和 C# 一样,但要注意,CG 中不存在 C# 里的“短路”操作。
// 逻辑运算符
bool b2 = (f1 < f2) && (f2 > 1); // 变量值: true (1 < 2 且 2 > 1)
bool b3 = (f1 > f2) || (f2 == 2); // 变量值: true (1 > 2 为 false, 但 2 == 2 为 true)
bool b4 = !(f1 > f2); // 变量值: true (!(1 > 2) 为 true)
数学运算符
- 加法
+
- 减法
-
- 乘法
*
- 除法
/
- 取余
%
- 自增减
++
、--
CG 里数学运算符的使用和 C# 一样,不过需要注意,CG 中取余符号只能对整数取余。
// 数学运算符
int i1 = 2; // 变量值: 2
fixed f4 = f1 + f2; // 加法, 变量值: 3 (1 + 2)
fixed f5 = f2 - f1; // 减法, 变量值: 1 (2 - 1)
fixed f6 = f1 * f2; // 乘法, 变量值: 2 (1 * 2)
fixed f7 = i1 / f1; // 除法, 变量值: 2 (2 / 1)
fixed f8 = f2 % f1; // 取余, 变量值: 0 (2 % 1)
f1++; // 自增, 变量值: 2 (f1 由 1 变为 2)
f2--; // 自减, 变量值: 1 (f2 由 2 变为 1)
4.CG语法流程控制语句
4.1.条件分支语句
CG 中条件分支语句(if
、switch
)与 C# 用法一致。
// if 语句
fixed f1 = 1, f2 = 2, fResult;
if (f1 < f2) fResult = 4.5;
else fResult = 6.7;
// switch 语句
fixed fSwitch = 1.0;
switch ((int)fSwitch) {
case 0: fSwitch = 0.0; break;
case 1: fSwitch = 2.0; break;
default: fSwitch = -1.0; break;
}
4.2.循环语句
CG 中循环语句(for
、while
、do while
)与 C# 用法一致。
// for 循环
fixed sumFor = 0.0;
for (int i = 0; i < 3; i++) sumFor += 1.0;
// while 循环
fixed sumWhile = 0.0;
int countWhile = 0;
while (countWhile < 3) { sumWhile += 1.0; countWhile++; }
// do while 循环
fixed sumDoWhile = 0.0;
int countDoWhile = 0;
do { sumDoWhile += 1.0; countDoWhile++; } while (countDoWhile < 3);
5.CG-函数
5.1. 无返回值的函数
基本结构
void name(in 参数类型 参数名, out 参数类型 参数名)
{
函数体
}
void
:表示无返回值。in
:输入参数,由外部传入,内部仅使用不修改,可多个。out
:输出参数,由内部传给调用者,内部须初始化或修改,可多个。
实例
// 无返回值的函数
void test(in fixed inF, out fixed outF)
{
outF = inF + 10;
}
// 调用示例
fixed f = 10;
fixed f2;
test(f, f2); // f2的值会变成20
注意:in
和 out
可省略,但编写 Shader 时建议保留,能明确参数传递方式,提升代码可读性与可维护性。
5.2. 有返回值的函数
基本结构
type name(in 参数类型 参数名)
{
函数体
return 返回值;
}
type
:返回值类型。return
:返回指定类型数据。
实例
// 有返回值的函数
float test2(in float inF, out fixed f)
{
f = inF + 2;
return inF * 2;
}
// 调用示例
float f3 = test2(11, f); // f3: 22, f: 13
注意:有返回值函数中可用 out
参数,但不常见,顶点/片元着色器函数多用单返回值处理。
6.CG语法语义
语义的作用
在 CG 语言中,“语义”是一种特殊关键字,用于修饰函数的传入参数和返回值。其主要作用是让 Shader 明确数据的读取来源和输出去向,使得在 Shader 开发过程中能够获取所需数据,并将数据进行传递。需注意的是,Unity 仅支持 CG 中的部分语义。
常用语义
- 应用阶段——>顶点着色器:当应用阶段向顶点着色器传递模型数据时,Unity 所支持的语义,一般应用于顶点着色器回调函数的传入参数中。
- POSITION:表示模型空间中的顶点位置,通常为
float4
类型。 - NORMAL:即顶点法线,通常是
float3
类型。 - TANGENT:顶点切线,一般为
float4
类型。 - TEXCOORDn:例如
TEXCOORD0
、TEXCOORD1
等。代表该顶点的纹理坐标,通常是float2
或者float4
类型。其中TEXCOORD0
表示第一组纹理坐标,依此类推。纹理坐标也叫 UV 坐标,用于表示该顶点在纹理图像上的对应位置。 - COLOR:顶点颜色,通常为
fixed4
或float4
类型。
- POSITION:表示模型空间中的顶点位置,通常为
- 顶点着色器——>片元着色器:从顶点着色器向片元着色器传递数据时,Unity 支持的语义,一般用于顶点着色器回调函数的返回值中。
- SV_POSITION:裁剪空间中的顶点坐标,这是必备的语义。
- COLOR0:通常用于输出第一组顶点颜色,并非必须。
- COLOR1:通常用于输出第二组顶点颜色,不是必需的。
- TEXCOORD0~TEXCOORD7:通常用于输出纹理坐标,不是必要的。
- 片元着色器输出:片元着色器输出时,Unity 支持的常用语义,一般在片元着色器回调函数的返回值中使用。
- SV_Target:输出值会被存储到渲染目标中。
7.ShaderLab属性类型和CG变量类型的匹配关系
对应关系列表
ShaderLab 属性类型 | CG 变量类型 |
---|---|
Color, Vector | float4, half4, fixed4 |
Range, Float, Int | float, half, fixed |
2D | sampler2D |
Cube | samplerCube |
3D | sampler3D |
2DArray | sampler2DArray |
解释
- Color 和 Vector:ShaderLab 中的
Color
和Vector
属性用于表示颜色或者四维向量。在 CG 里可以用float4
、half4
或者fixed4
类型来对应。float4
是 32 位浮点数的四维向量,精度最高;half4
是 16 位浮点数的四维向量,精度适中;fixed4
是 12 位浮点数的四维向量,精度最低,适合对精度要求不高的场景。 - Range、Float 和 Int:
Range
用于定义一个有范围的浮点数,Float
表示普通浮点数,Int
表示整数。在 CG 中可以使用float
、half
或者fixed
类型。同样,float
精度最高,half
次之,fixed
精度最低。 - 2D:ShaderLab 中的
2D
属性用于表示二维纹理,在 CG 中使用sampler2D
类型来进行纹理采样操作。 - Cube:
Cube
属性用于表示立方体纹理,常用于环境映射等,在 CG 中用samplerCube
类型来处理。 - 3D:
3D
属性表示三维纹理,在 CG 里对应sampler3D
类型,用于体积渲染等场景。 - 2DArray:
2DArray
属性代表二维纹理数组,在 CG 中使用sampler2DArray
类型来处理。
了解这些对应关系有助于在 Shader 开发时,正确地在 ShaderLab 中定义属性,并在 CG 代码中使用合适的变量类型来处理这些属性。
8.CG语法内置函数
- 数学函数
- 三角函数相关:
sincos(float x, out s, out c)
:能同时计算输入值x
的正弦值和余弦值,并分别通过s
和c
返回,相较于分别计算效率更高。sin(x)
:计算正弦值。cos(x)
:计算余弦值。tan(x)
:计算正切值。sinh(x)
:计算双曲正弦值。cosh(x)
:计算双曲余弦值。tanh(x)
:计算双曲正切值。asin(x)
:反正弦函数,输入参数范围为[-1,1]
,返回[-π/2,π/2]
区间的角度值。acos(x)
:反余弦函数,输入参数范围是[-1,1]
,返回[0,π]
区间的角度值。atan(x)
:反正切函数,输入参数范围为[-1,1]
,返回[-π/2,π/2]
区间的角度值。atan2(y,x)
:计算y/x
的反正切值,与atan
功能类似,只是输入参数不同(atan(x)=atan2(x,1)
)。
- 向量、矩阵相关:
cross(A,B)
:计算两个三维向量的叉乘。dot(A,B)
:计算两个三维向量的点乘。mul(M,N)
:计算两个矩阵的乘积。mul(M,v)
:计算矩阵与向量的乘积(矩阵左乘向量)。mul(v,M)
:计算向量与矩阵的乘积(向量左乘矩阵)。transpose(M)
:计算矩阵M
的转置矩阵。determinant(m)
:计算矩阵的行列式因子。
- 数值相关:
abs(x)
:返回输入参数的绝对值。ceil(x)
:对输入参数向上取整。floor(x)
:对输入参数向下取整。clamp(x,a,b)
:若x
小于a
,返回a
;若x
大于b
,返回b
;否则返回x
(“夹紧”函数)。radians(x)
:将角度转换为弧度。degrees(x)
:将弧度转换为角度。max(a,b)
:返回a
和b
中的最大值。min(a,b)
:返回a
和b
中的最小值。sqrt(x)
:计算x
的平方根(x
必须大于 0)。pow(x,y)
:计算x
的y
次方的值。round(x)
:对x
进行四舍五入。rsqrt(x)
:计算x
的反平方根(x
必须大于 0)。lerp(a,b,f)
:差值函数,计算(1-f)a + bf
或者a + f*(b-a)
的值。exp(x)
:计算e
的x
次方的值(e=2.71828182845904523536
)。exp2(x)
:计算2
的x
次方的值。fmod(x,y)
:返回x/y
的余数(y
不为 0)。frac(x)
:返回标量或每个矢量分量的小数部分。frexp(x,out exp)
:将浮点数x
分解为尾数和指数,即x = m * 2
的exp
次方,返回m
,并将指数存储在exp
中。isfinite(x)
:判断标量或者向量中的每个数据是否为有限数,若是返回true
,否则返回false
。isinf(x)
:判断标量或者向量中的每个数据是否为无限,若是返回true
,否则返回false
。isnan(x)
:判断标量或者向量中的每个数据是否为非数值,若是返回true
,否则返回false
。ldexp(x,n)
:计算x * 2
的n
次方的值。log(x)
:计算ln(x)
的值(x
必须大于 0)。log2(x)
:计算log2(x)
的值(x
必须大于 0)。log10(x)
:计算log10(x)
的值(x
必须大于 0)。saturate(x)
:若x
小于 0,返回 0;若x
大于 1,返回 1;否则返回x
。sign(x)
:若x
大于 0,返回 1;若x
小于 0,返回 -1;否则返回 0。smoothstep(min,max,x)
:当值x
位于min
、max
区间内时,若x = min
,返回 0;若x = max
,返回 1;若在两者之间,返回-2* ((x-min)/(max - min))
的三次方+ 3* ((x - min)/(max - min))
的二次方。step(a,x)
:若x < a
,返回 0;否则返回 1。all(x)
:输入参数均不为 0,则返回true
;否则返回false
,相当于逻辑与&&
。any(x)
:输入参数只要有一个不为 0,则返回true
,相当于逻辑或||
。
- 其他:
lit(NdotL,NdotH,m)
:其中N
表示法向量,L
表示入射光向量,H
表示半角向量,m
表示高光系数。该函数用于计算环境光、散射光、镜面光的贡献,返回一个 4 维向量,x
位表示环境光贡献,y
位表示散射光贡献,z
位表示镜面光贡献,w
始终为 1。noise(x)
:噪声函数,返回值始终在0~1
之间,对于相同的输入始终返回相同值,并非真正意义的随机噪声。
- 三角函数相关:
- 几何函数:
length(v)
:返回一个向量的模长。normalize(v)
:对向量进行归一化处理。distance(p1,p2)
:计算两点之间的距离。reflect(I,N)
:计算反射光方向向量,I
为入射光(指向顶点),N
为顶点法向量,I
和N
必须被归一化且为 3 维向量。refract(I,N,eta)
:计算折射向量,I
为入射光(指向顶点),N
为顶点法向量,eta
为折射系数,I
和N
必须被归一化且为 3 维向量。
- 纹理函数:这些纹理采样函数返回值均为
fixed4
类型的颜色值。二维纹理:
tex2D(sampler2D tex, float2 s)
:进行二维纹理查询。tex2D(sampler2D tex, float2 s, float2 dsdx, float2 dsdy)
:使用导数值查询二维纹理。tex2D(sampler2D tex, float3 sz)
:二维纹理查询,并进行深度值比较。tex2D(sampler2D tex, float3 sz, float2 dsdx, float2 dsdy)
:使用导数值查询二维纹理,并进行深度值比较。tex2Dproj(sampler2D tex, float3 sq)
:二维投影纹理查询。tex2Dproj(sampler2D tex, float4 szq)
:二维投影纹理查询,并进行深度值比较。
立方体纹理:
texCUBE(samplerCUBE tex, float3 s)
:查询立方体纹理。texCUBE(samplerCUBE tex, float3 s, float3 dsdx, float3 dsdy)
:结合导数值查询立方体纹理。texCUBEDproj(samplerCUBE tex, float4 sq)
:查询立方体投影纹理,并进行深度值比较。
其他纹理:
- 一维纹理:
tex1D(sampler1D tex, float s)
:一维纹理查询。tex1D(sampler1D tex, float s, float dsdx, float dsdy)
:使用导数值查询一维纹理。tex1D(sampler1D tex, float2 sz)
:一维纹理查询,并进行深度值比较。tex1D(sampler1D tex, float2 sz, float dsdx, float dsdy)
:使用导数值查询一维纹理,并进行深度值比较。tex1Dproj(sampler1D tex, float2 sq)
:一维投影纹理查询。tex1Dproj(sampler1D tex, float3 szq)
:一维投影纹理查询,并进行深度值比较。
- 矩形纹理:
texRECT(samplerRECT tex, float2 s)
:矩形纹理查询。texRECT(samplerRECT tex, float2 s, float2 dsdx, float2 dsdy)
:使用导数值查询矩形纹理。texRECT(samplerRECT tex, float3 sz)
:矩形纹理查询,并进行深度值比较。texRECT(samplerRECT tex, float3 sz, float2 dsdx, float2 dsdy)
:使用导数值查询矩形纹理,并进行深度值比较。texRECTproj(samplerRECT tex, float3 sq)
:矩形投影纹理查询。texRECTproj(samplerRECT tex, float3 szq)
:矩形投影纹理查询,并进行深度值比较。
- 三维纹理:
tex3D(sampler3D tex, float3 s)
:查询三维纹理。tex3D(sampler3D tex, float3 s, float3 dsdx, float3 dsdy)
:结合导数值查询三维纹理。tex3DDproj(sampler3D tex, float4 sq)
:查询三维投影纹理,并进行深度值比较。
- 一维纹理:
9.CG-内置文件
(1)Unity 中常用的内置文件如下:
- UnityCG.cginc:包含了最常用的帮助函数、宏和结构体等。
- Lighting.cginc:包含各种内置光照模型。若编写的是 Surface Shader(标准表面着色器),该文件会自动被包含进来。
- UnityShaderVariables.cginc:在编译 Unity Shader 时,会自动包含此文件,其中包含许多内置的全局变量。
- .HLSLSupport.cginc:同样在编译 Unity Shader 时自动包含,声明了很多用于跨平台编译的宏和定义等。
(2)如何使用 CG 内置文件
在 CG 语句块中,通过编译指令 #include "内置文件名.cginc"
的形式进行引用,如此便可以在 CG 语言中使用其中的内容。虽然一些常用的函数、宏、变量,即便不引用,Unity 在编译时也可能自动识别,但为避免报错,建议都进行引用。
“宏”通常是一种预处理指令或代码片段,用于在代码中进行文本替换。即程序员定义一个标识符(通常为大写字母)来代表一个代码片段,编译时该标识符会被替换为相应的代码,这个过程称为宏展开。通俗来讲,就是给一些代码片段取别名以方便使用,在真正编译时再将别名翻译成对应的代码。
(3)常用内容
方法(UnityCG.cginc 中):
float3 WorldSpaceViewDir(float4 v)
:输入模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。float3 ObjSpaceViewDir(float4 v)
:输入模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向。float3 WorldSpaceLightDir(float4 v)
:仅用于向前渲染,输入模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向(返回值未归一化)。float3 ObjSpaceLightDir(float4 v)
:仅用于向前渲染,输入模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向(返回值未归一化)。float3 UnityObjectToWorldNormal(float3 norm)
:将法线方向从模型空间转换到世界空间。float3 UnityObjectToWorldDir(in float3 dir)
:把方向矢量从模型空间转换到世界空间。float3 UnityWorldToObjectDir(float3 dir)
:把方向矢量从世界空间转换到模型空间。
结构体(UnityCG.cginc 中):
- appdata_base:用于顶点着色器输入,包含顶点位置、顶点法线、第一组纹理坐标。
- appdata_tan:用于顶点着色器输入,包含顶点位置、顶点法线、顶点切线、第一组纹理坐标。
- appdata_full:用于顶点着色器输入,包含顶点位置、顶点法线、顶点切线、四组(或更多)纹理坐标。
- appdata_img:用于顶点着色器输入,包含顶点位置、第一组纹理坐标。
- v2f_img:用于顶点着色器输出,包含裁剪空间中的位置、纹理坐标。
变换矩阵宏(UnityShaderVariables.cginc 中):
坐标空间变换顺序为:模型空间 -> 世界空间 -> 观察空间 -> 裁剪空间 -> 屏幕空间。- UNITY_MATRIX_MVP:当前的模型观察投影矩阵,用于将顶点/方向向量从模型空间变换到裁剪空间。
- UNITY_MATRIX_MV:当前的模型*观察矩阵,用于将顶点/方向向量从模型空间变换到观察空间。
- UNITY_MATRIX_V:当前的观察矩阵,用于将顶点/方向向量从世界空间变换到观察空间。
- UNITY_MATRIX_P:当前的投影矩阵,用于将顶点/方向向量从观察空间变换到裁剪空间。
- UNITY_MATRIX_VP:当前的观察*投影矩阵,用于将顶点/方向向量从世界空间变换到裁剪空间。
- UNITY_MATRIX_T_MV:UNITY_MATRIX_MV 的转置矩阵。
- UNITY_MATRIX_IT_MV:UNITY_MATRIX_MV 的逆转置矩阵,可用于将法线从模型空间变换到观察空间,也能得到 UNITY_MATRIX_MV 的逆矩阵。
- _Object2World:当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间(Unity 5.5 版本中已变为 unity_ObjectToWorld)。
- _World2Object:_Object2World 的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间(Unity 5.5 版本中已变为 unity_WorldToObject)。由于 Unity 的向下兼容性,自动改写后不会出错,且会在代码最上方出现自动改后的提示。
变换矩阵宏使用示例:
//使用UnityCG.cginc内置结构体v2f_img和appdata_base
v2f_img vert(appdata_base data)
{
//...
//把模型的点转换成世界坐标
float worldPos = mul(unity_ObjectToWorld,data.vertex);
//...
}
- 变量:
- _Time:自关卡加载以来的时间(t/20、t、t2、t3),可用于对着色器内的事物进行动画处理,无需引用,可直接使用。
- _LightColor0:向前渲染时,在 UnityLightingCommon.cginc 中;延迟渲染时,在 UnityDeferredLibrary.cginc 中,代表光的颜色等。
六、光照模型
1. 兰伯特光照模型(Lambertian Lighting Model)
兰伯特光照模型是一种简单且经典的漫反射光照模型。它基于兰伯特定律,该定律指出漫反射表面反射的光强度与表面法线和光源方向之间夹角的余弦值成正比。
原理:在该模型中,假设物体表面是理想的漫反射表面,即光线在表面向各个方向均匀散射。对于一个给定的点,其受到的漫反射光照强度仅取决于该点的表面法线方向和光源方向。
2. 半兰伯特光照模型(Half Lambert Lighting Model)
半兰伯特光照模型是对兰伯特光照模型的改进。兰伯特模型的一个缺点是当表面法线与光源方向夹角大于 90 度时,表面完全变黑(漫反射光强度为 0),这在某些情况下会导致视觉效果不自然。半兰伯特模型通过引入一个偏移量来改善这种情况。
原理:它在计算光照强度时,将兰伯特模型中的 ( \vec{N} \cdot \vec{L} ) 进行了修改,使得即使表面点背对光源,也能有一定的光照强度,从而增加了暗部的细节和层次感。
3. Phong 式高光反射模型(Phong Specular Reflection Model)
Phong 式高光反射模型用于模拟物体表面的镜面反射效果,即当光线照射到光滑表面时,会在特定方向上产生明亮的高光区域。
原理:该模型基于这样的假设,即高光的强度取决于观察者方向、反射光线方向以及表面的光滑程度。当反射光线方向与观察者方向接近时,会看到明显的高光。
4. Blinn-Phong 式高光反射模型(Blinn-Phong Specular Reflection Model)
Blinn-Phong 式高光反射模型是对 Phong 模型的优化和改进。在 Phong 模型中,计算反射光线方向 ( \vec{R} ) 的计算量较大,而 Blinn-Phong 模型通过引入半角向量简化了计算。
原理:它使用半角向量 ( \vec{H} )(即光源方向 ( \vec{L} ) 和观察者方向 ( \vec{V} ) 的角平分线方向向量)来替代 Phong 模型中的反射光线方向 ( \vec{R} ) 进行高光计算。当半角向量 ( \vec{H} ) 与表面法线 ( \vec{N} ) 接近时,产生高光。
5. Phong 光照模型
严格来说,Phong 光照模型是一个综合性的光照模型,它同时考虑了漫反射和高光反射,是在兰伯特漫反射模型的基础上增加了 Phong 式高光反射部分。
原理:一个表面点的最终光照强度由漫反射光强度和高光反射光强度以及环境光强度(通常是一个常量,表示场景中的环境光对物体的影响)共同组成。