Unity 光照文档: https://docs.unity3d.com/cn/2022.3/Manual/LightingOverview.html光照
目的:提升游戏体验和视觉效果
Unity 使用详细的光线工作模型来获得更逼真的结果,并使用简化模型来获得更具风格化的结果。
1. 光照概念
1.1 光照分类
Directional 方向光 : 日光、月光,无限远的光源,不会随距离而衰竭
Spot 探照光: 锥形的方向光,从添加点向一个方向照射,适合模拟:路灯、台灯、手电、舞台灯、车灯等等锥形光源;
Point 点光: 全方向的一个点光源,适合模拟:萤火虫、蜡烛、夜光宝石等
Area 面光: 面光源,不能是实时的,可以模拟一个静态发光面,适合模拟:荧光灯、LED 平面灯 等等
-
它的 形状将决定光波发射的 方向。
-
它的 大小将决定光影响的 区域。
-
它的 强度(或 强度)将决定这些光波可以传播多远以及它的亮度。
天空盒 Skybox: 是每个面上都有不同纹理的立方体。
使用天空盒来渲染天空时,Unity 本质上是在将场景放置在天空盒立方体中。Unity 首先渲染天空盒,因此天空总是在背面渲染。使用天空盒执行以下操作:
- 在场景周围渲染一个天空盒。
- 配置光照设置来根据天空盒创建逼真的环境光照。
- 使用天空盒组件覆盖由单个摄像机使用的天空盒。
梯度(渐变、Gradient):种用于创建平滑过渡颜色的工具。在Unity中,使用梯度来定义环境光、材质、粒子效果、UI元素等的颜色变化。在环境光中实现光线渐变视觉效果,仿真霞光、地面色的渐变光照效果。
2. 光照设置
2.1 Light Explorer (光照资源管理窗口)
在Unity界面 Window -> Rendering -> Light Explorer
光照资源管理窗口 Lights 选项卡
光源设置:Directional Light 室外必备光源,还可以添加多个,提高整体光照效果,夜晚应该降低光照强度
光照类型: 例如 spot, area
Shape : 形状影响光发射的方向
Mode 光照模式:
- 烘培光照 Baked: Unity 提前执行光照计算并将结果保存为光照数据,然后在运行时应用
- 实时光照 Realtime:Unity 在运行时计算光照。需要更高的配置,消耗更多算力
- 混合光照 Mixed:实时和烘焙的混合
color : 光的颜色很重要,合理使用好光色,可以更好地凸显游戏氛围
Range 光照范围:光在场景中的传播距离。但要受到光源形状的限制,比如 spot light 聚光灯,将值增加到超过该上限,就把不会导致任何进一步的范围增加
Intensity 光照强度: 一般夜晚的光源,在比较符合现实的游戏氛围中,电灯其强度应该设置为 200-600 之间; 蜡烛、火把等前度应该介于 50 -100 之间
Indirect Multiplier 间接光乘数:此光源提供的间接光的强度(在被传感器接收之前多次反弹的光)。
- 低于1时,间接光在每次从物体反弹时都会变暗。这是真实光照的行为方式,但您可能希望覆盖该行为以实现特定的光照效果。
- 高于1 ,间接光会随着每次反弹而变亮。这并不自然,但如果您试图照亮场景中的黑暗封闭空间,它会非常有用。
- 一般默认为1
2.2 Lighting (光源场景设置)窗口
在Unity界面 Window -> Rendering -> Lighting
Scene 场景选项卡
显示有关分配给活动场景光照设置资源的信息。如果没有为活动场景分配光照设置资源,它会显示有关默认 LightingSettings 对象的信息
- Lighting Settings Asset controls: 新建或选择不同的光照配置文件,将光照设置资源分配给活动(当前)场景
- Mixed Lighting 混合光照:
- Baked Indirect 烘焙间接光: 将实时直接光照与烘焙间接光照相结合。它提供实时阴影。此光照模式提供逼真的光照和合理的阴影保真度,适用于中档硬件。
- Shadowmask 阴影遮罩: 将实时直接照明与烘焙间接照明相结合。它可以为远处提供烘焙阴影游戏对象,并自动将它们与实时阴影混合。它是最现实的,也是最耗费资源的光照模式。您可以使用质量设置来配置其性能和视觉保真度。此照明模式适用于高端或中档硬件。
- Subtractive 减法: 提供烘焙的直接和间接照明。它只为一个定向光渲染直接实时阴影。此光照模式不提供特别逼真的光照效果,适用于风格化艺术或低端硬件。
- Lightmapping Settings:
- Lightmap Resolution 光照贴图解决方案: 指定每单位用于光照贴图的纹素(texel)数。增加此值可提高光照贴图质量,但也会增加烘焙时间。请注意,将该值加倍会使纹素的数量增加四倍,因为它决定了光照贴图的高度和宽度。
- Lightmap Padding 光照贴图填充: 确定烘焙光照贴图中不同形状之间的间隔(以纹素为单位)。默认值为 2。
- Max Lightmap Size 最大光照贴图大小: 指定完整光照贴图纹理的大小(以像素为单位),其中包含每个包含的游戏对象的单独区域。默认值为 1024。
- Lightmap compression 光照贴图压缩: 编辑器用于光照贴图的压缩级别。四个选项:
- none,低中高质量。
- 无:不压缩光照贴图。
- 低质量:这可能比正常质量使用更少的内存和存储空间,但也会引入视觉伪影。
- 正常质量:这是内存使用和视觉质量之间的良好折衷。
- 高质量:比普通质量需要更多的内存和存储空间,但提供更好的视觉效果。
- Workflow Settings:工作流设置
Environment 环境选项卡
包含相关当前场景的环境光照效果的设置。内容取决于项目使用的渲染管线。
- Environment(环境):包含与光照相关的设置和控件,这些设置和控件适用于当前场景中的环境光照,例如天空盒、漫射光照和反射
- Skybox Material 天空盒是一种材质,它出现在场景中的所有其他对象后方,用于模拟天空或其他遥远的背景。使用此属性可选择要用于场景的天空盒。默认值是内置的默认天空盒 (Default Skybox)。
- Environment Lighting 此部分包含可影响当前场景中的环境光的设置。
- Environment Reflections 此部分包含反射探针烘焙的全局设置,以及影响全局反射的设置。
- Other Settings 部分包含雾、光环、光晕和剪影的设置
- Fog 在场景中启用或禁用雾效。请注意,此模式无法用于延迟渲染路径。
- Halo Texture 设置要用于在光源周围绘制光环的纹理。
- Halo Strength 定义光源周围光环的可见性,值在 0 到 1 之间。
- Flare Fade Speed 定义最初出现镜头光晕之后从视图中淡出的时间(以秒为单位)。默认情况下,该值设置为 3。
- Flare Strength 定义光源下镜头光晕的可见性,值在 0 到 1 之间。
Baked Lightmaps 烘焙光照贴图选项卡
此选项卡显示光照贴图为当前场景生成的所有光照贴图的列表,以及光照数据资源。如果项目中未启用烘焙全局光照 (Baked Global Illumination),则此选项卡将为空。
3. 天空盒
天空盒是每个面上都有不同纹理的立方体。使用天空盒来渲染天空时,Unity 本质上是在将场景放置在天空盒立方体中。Unity 首先渲染天空盒,因此天空总是在背面渲染。
使用天空盒执行以下操作:
- 在场景周围渲染一个天空盒。
- 配置光照设置来根据天空盒创建逼真的环境光照, 例如场景中的环境光 Ambient lighting 是由天空背景产生。
- 使用天空盒组件覆盖由单个摄像机使用的天空盒。
3.1 天空盒着色器 SkyBox Shader
天空盒着色器应用到SkyBox,可以订制skybox,下面4.2,4.3将讲述一个订制的实例
SkyBox Shaders 共有四种,可以分为两大类:
- 使用纹理 Textured:
- 6 Sided 六面:
从六个单独的纹理生成天空盒。每个纹理代表沿特定世界轴的天空视图。为了说明这一点,想想场景就像在一个立方体里面。每个纹理代表立方体的一个内部面,所有六个组合起来创建一个无缝的环境。 - Cubemap 立方体贴图:
从单个Cubemap Asset生成天空盒。 - Panoramic 全景:
将单个 Texture 球形包裹在场景
- 6 Sided 六面:
2. 不使用纹理:
4. Procedural 程序生成,不需要任何输入纹理,而是纯粹从材质中设置的属性生成天空盒。
4. 光照练习
下载项目文件
解压缩文件,在Unity Hub中点击“打开”按键,在Unity中打开项目后在Project窗口中选择 CreativeCore_Lighting -> Scenes,在其右侧窗口中双击 Tutorial Scene_Lighting_Outdoor 文件,如下图所示:
4.1 配置定向光
在 Unity 中,每当创建场景时,它都会附带已添加到层次结构中的定向光;这盏灯的作用就像真实世界的太阳或月亮。
- 新建一个定向光并放置在场景中的任何位置,并且不会影响投射的光线,需要显式更改光线的方向才能在场景中更改它。
- 在Hierarchy(层次结构)窗口中点击 Lighting, 在展开的目录中点击 Directonal Light, 在Scene(场景)窗口中看到该游戏物体
- 尝试将定向光移动到场景中的不同位置:更近或更远。这些变化引起的光照没有差异 - 与真实太阳不同,定向光的位置不会改变光的方向。
- 使用旋转工具 更改定向光的方向。注意到场景中已有的天空会对其位置变化做出响应。
- 在上图右侧Inspector 窗口并在 Light 组件中找到Color属性(上图中红色箭头所指)。
- 选择颜色框以打开颜色选择器窗口并选择不同的色调。注意圆形剧场的结构——这是最容易看到颜色变化影响的地方。这可以帮助营造场景气氛。
这种效果可能相对微妙,并且不一定适合场景当前配置的方式。如果场景设置在深夜而不是清晨,神秘的紫色或不祥的红色可能会产生更大的影响。现在可以随意尝试不同的颜色,但在本教程结束时您将有机会更多地调整定向光。
4.2 配置Skybox
1. 新建1个skybox material
在右侧 Project 窗口中 打开窗口 Assets > CreativeCore-Lighting > Materials,然后鼠标放在Materials上点击鼠标右键弹出菜单,选择 Create -> Material, 新建一个材质 Material
2. 更新材质名字为 CustomSky, 在右侧Inspector 窗口点击 Shader,弹出下拉菜单,选择SkyBox,在接下来的窗口中选择 Procedural 用程序生成skybox 材质
3. 将上述新建的skybox着色器应用在当前场景的sky。
点击Unity主界面 Window > Rendering > LIghting,打开 (2.2的)Lighting设置窗口,可以将该窗口移到右侧 并嵌入到 Inspector窗口右侧
4. 点击Lighting窗口中 Skybox Material 右侧的按键,在弹出的窗口中输入CustomSky,选择该新建的skybox material,更新原有的 Default-Skybox material
5. 在scene窗口中显示skybox (如果skybox已经存在,即天空是蓝色的,不是灰色的,可以省略该步骤)
6.再次在Project窗口中点击 Assets > CreativeCore-Lighting > Materials 在打开的窗口中选择刚才新建的CustomSky 材质,在右侧的Inspect窗口中,更改各个参数数值,看场景中Sky的变化,例如更改太阳大小尺寸,然后在Scene窗口中旋转场景看天空中太阳尺寸的变化。再尝试更改其他的参数,看场景中环境变化。
4.3 设计日夜交替
设计1个日夜交替的场景,完成下述任务:
- 通过构建1个TimeController空游戏物件;
- 绑定编写的脚本script文件,控制上述CutomSky天空盒的材质参数,实现天空白天与黑夜的交替显示;
- 同时构建1个TextMeshPro 的UI控件,实时显示当前的时间。
1. 点击Unity主界面 GameObject > UI > Text - TextMeshPro,在场景中添加1个文字控件 。首次使用TextMeshPro 会弹出窗口 TMP Import 导入该控件资源。
2. 在 Project 窗口中增加了如下信息,更改 Text(TMP) 为 TimeText
3. 鼠标放在上述 Canvas上,点击右键,在弹出菜单中点击“Create Empty”在该目录下构建1个空游戏物件。
4. 更改该GameObject 名称为 TimeController, 在点击 "Add Compontent", 在该 TimeController文件下添加1个脚本文件, 也取名为TimeController。
5. 在Inspector窗口中,新增了TimeController (Script),点击右侧的 三点按键,在弹出菜单中选择 Edit Script
6. 该script将在Visual Studio 2022中打开,供代码编辑
7. 更新代码如下
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimeController : MonoBehaviour
{
//设置1帧时间转换为1秒的乘数
[SerializeField]
private float timeMultiplier;
//设置一天开始的时间
[SerializeField]
private float startHour;
//设置显示时间的text物件
[SerializeField]
private TMPro.TextMeshProUGUI timeText;
//当前时刻时间
private DateTime currentTime;
//场景中的定向光游戏物件
[SerializeField]
private Light sunLight;
//日出时间 浮点变量
[SerializeField]
private float sunriseHour;
//日落时间 浮点变量
[SerializeField]
private float sunsetHour;
//日出时间值 timespan变量
private TimeSpan sunriseTime;
//日落时间 timespan变量
private TimeSpan sunsetTime;
// Start is called before the first frame update
void Start()
{
//初始化当前时间
currentTime = DateTime.Now.Date + TimeSpan.FromHours(startHour);
//初始化日出时间
sunriseTime = TimeSpan.FromHours(sunriseHour);
//初始化日落时间
sunsetTime = TimeSpan.FromHours(sunsetHour);
Debug.Log("currentTime 0: "+ currentTime.ToString("HH:mm"));
}
// Update is called once per frame
void Update()
{
UpdateTimeOfDay();
RotateSunLight();
}
private void UpdateTimeOfDay()
{
//当前时间的累计,程序运行时间deltaTime (1帧时间大概0.004秒) 乘以 timeMultiplier (设置250) = 1 秒
currentTime = currentTime.AddSeconds(Time.deltaTime * timeMultiplier);
if (timeText != null)
{
timeText.text = currentTime.ToString("HH:mm");
}
Debug.Log("currentTime: " + currentTime.ToString("HH:mm"));
Debug.Log("deltaTime: " + Time.deltaTime.ToString());
}
//旋转定向光
private void RotateSunLight()
{
float sunLightRotation =0.0f;
//如果当前时间是白天
if (currentTime.TimeOfDay > sunriseTime && currentTime.TimeOfDay < sunsetTime)
{
//日升到日落的时间差
TimeSpan sunriseToSunsetDuration = CalculateTimeDifference(sunriseTime, sunsetTime);
//日升到当前时间的时间差
TimeSpan timSinceSunrise = CalculateTimeDifference(sunriseTime, currentTime.TimeOfDay);
//当前时间差和总白天时间的比率
double percentage = timSinceSunrise.TotalMinutes / sunriseToSunsetDuration.TotalMinutes;
//计算定向光的角度值
sunLightRotation = Mathf.Lerp(0, 180, (float)percentage);
}
else
{
//日落到日升的时间差
TimeSpan sunsetToSunriseDuration = CalculateTimeDifference(sunsetTime, sunriseTime);
//日落到当前时间的时间差
TimeSpan timeSinceSunset = CalculateTimeDifference(sunsetTime, currentTime.TimeOfDay);
//当前时间差和总黑夜时间的比率
double percentage = timeSinceSunset.TotalMinutes / sunsetToSunriseDuration.TotalMinutes;
//计算定向光的角度值
sunLightRotation = Mathf.Lerp(180, 360, (float)percentage);
}
Debug.Log("rotation: " + sunLightRotation.ToString());
//设置定向光的角度值
sunLight.transform.rotation = Quaternion.AngleAxis(sunLightRotation, Vector3.right);
}
//计算fromTime 和 toTime 之间的时间差
private TimeSpan CalculateTimeDifference(TimeSpan fromTime, TimeSpan toTime)
{
TimeSpan diff = toTime - fromTime;
//如果时间差是负值,加上24小时,新的1天
if (diff.TotalSeconds < 0)
{
diff += TimeSpan.FromHours(24);
}
return diff;
}
}
4.4 添加路灯
1. 场景中添加路灯,点击 Project 窗口中 Assets > Prefabs, 可以看到项目自带的路灯,鼠标点击其中1个路灯,例如StreetLamp,拖拽到左侧的 Hierarchy窗口中,这时场景中会显示该路灯,调整路灯在场景中的位置,也可以在拖拽其他的灯到场景中
2. 依次共添加 4 个路灯,可以如图所示:
3. 更改点光源光照生成方式,由 Mixed 改为 Baked。 点击一个路灯,例如下图中 StreeLamp(2),在下拉目录中选择Spot Light,在右边 Inspector 窗口中看到 Light > Genenal > Mode 中为Mixed,点击该下拉控件,选择Baked
可以尝试在Scene窗口中移动上述选择的路灯 StreeLamp(2),可以看到灯光照射区域随着路灯的移动而移动,表明该灯照区域是系统(实时)生成的,改为Baked的话,则灯照区域会保留 在原地,不随着灯的位置而改变。
虽然上述操作已经改 Mixed 为 Baked,但场景中 灯照区域还是 mixed,这是因为还没有对场景中灯光进行重新 生成。
4. 打开lighting窗口,点击unity界面上Window菜单,选择Rendering > lighting
5.在Lighting窗口中选择Scene选项卡,在页面最下端点击 "Generate Lighting" 按键,将场景中点光源光照区域进行烘焙,系统需要一段时间烘焙灯光区域。
5. 当生成结束后,可以再尝试在Scene窗口中移动上述选择的路灯 StreeLamp(2),可以看到灯光照射区域依然保留 在原地,不随着灯的位置而改变。
4.5 路灯开关
1. 添加 StreetLampLight tag。 选择上述 StreetLamp(2)目录中的 SpotLgith, 在右侧Inspector窗口中 Tag行点击 Untagged下拉窗口,选择 Add Tag
在弹出的界面中,Tags下方的 “+” 按键
在弹出窗口中输入 “StreetLampLight”,在点击下方 Save 按键
再回到原来Unity 界面,将Spot Light 的Tag改为StreetLampLight
2. 再次在visual stuidio 打开上述TimeController 脚本。 点击TimeController,在右侧Inspector窗口中,点击Timecontroller脚本右侧的三点空间,在弹出窗口中选择 Edit Script
3. 在程序中加入如下黑体代码,
public class TimeController : MonoBehaviour
{
[SerializeField]
private bool streetLampLightEnabled;
[SerializeField]
private float timeMultiplier;
。。。。
private void RotateSunLight()
{
float sunLightRotation;
if (currentTime.TimeOfDay > sunriseTime && currentTime.TimeOfDay < sunsetTime)
{
TimeSpan sunriseToSunsetDuration = CalculateTimeDifference(sunriseTime, sunsetTime);
TimeSpan timSinceSunrise = CalculateTimeDifference(sunriseTime, currentTime.TimeOfDay);
double percentage = timSinceSunrise.TotalMinutes / sunriseToSunsetDuration.TotalMinutes;
sunLightRotation = Mathf.Lerp(0, 180, (float)percentage);
if (streetLampLightEnabled)
{
GameObject[] streetLamps = GameObject.FindGameObjectsWithTag("StreetLampLight");
foreach (GameObject streetLamp in streetLamps)
{
Light streetLampLight = streetLamp.GetComponent<Light>();
if (streetLampLight != null)
{
streetLampLight.enabled = false;
}
}
}
}
else
{
TimeSpan sunsetToSunriseDuration = CalculateTimeDifference(sunsetTime, sunriseTime);
TimeSpan timeSinceSunset = CalculateTimeDifference(sunsetTime, currentTime.TimeOfDay);
double percentage = timeSinceSunset.TotalMinutes / sunsetToSunriseDuration.TotalMinutes;
sunLightRotation = Mathf.Lerp(180, 360, (float)percentage);
if (streetLampLightEnabled)
{
GameObject[] streetLamps = GameObject.FindGameObjectsWithTag("StreetLampLight");
foreach (GameObject streetLamp in streetLamps)
{
Light streetLampLight = streetLamp.GetComponent<Light>();
if (streetLampLight != null)
{
streetLampLight.enabled = true;
}
}
}
}
sunLight.transform.rotation = Quaternion.AngleAxis(sunLightRotation, Vector3.right);
}
。。。。
4. 返回Unity界面,等编译完成后,点击TimeController脚本下方 Street lamp Light Enable 边的框,打钩
5.点击界面正上方的 play (三角形) 按键,查看上述街灯 StreetLamp(2)是否在白天(6点后)关闭,晚上(18点后)打开
注意:这个灯没有改变 (为什么?)
6. 将另外三个StreetLight 目录下的SpotLight的 tag 改为 “StreetLampLight”,例如下图所示:
7.再次点击界面正上方的 play (三角形) 按键,查看上述新的三个街灯 (除了StreetLamp(2))是否在白天(6点后)关闭,晚上(18点后)打开使得日夜交替的时候,路灯在白天关闭,黑夜点亮!
注意:这三灯开关会随着时间改变 (为什么?)
代码解释视频: 日夜更替日夜更替-3
4.6 配置路灯
在场景中完成如下的路灯配置:
-
添加红色墙灯
1. 点击 Project窗口在 Assets > Creativecore_Lighting > Prefabs 的窗口中选择 LongLight, 拖拽到右边Scene串口,放在一个墙壁上,注意初始的灯光是青蓝色的,在Hierarchy窗口中会自动增添该墙灯名字 LongLight
2. 点击上述 LongLight目录下的 LongLight,在右侧Inspector中,Materials下Element0中为BlueEmission, 点击右侧的圈点控件,在弹出窗口中,输入Red,在窗口中双击选择 RedEmission,这样改蓝光为红光,
3. 在Hierarchy窗口中,点击原先LongLight目录下的 Area Light,在右侧Inspector中,点击Emission下的Color行右端的蓝色框,在弹出颜色窗口中选择红色光
4. 打开lighting窗口,点击unity界面上Window菜单,选择Rendering > lighting
5.在Lighting窗口中选择Scene选项卡,在页面最下端点击 "Generate Lighting" 按键,将场景中新加入的红色墙灯生成红色灯照区域(注意,没有生成的红色强灯,在游戏场景中是不会显示红灯的,还是蓝灯,虽然在Scene场景中看到红灯),系统需要一段时间生成灯光
生成红色光照后的场景如下图所示:
作业1
添加1个绿色墙灯,如图所示:
Unity 教学课程:光照练习