=> GuardHei

Illusory Walls Ahead

F+ or Deferred? Which one should I choose?


Here we go again!

Unity中实现简单的非连续的随时间变化而改变的光照效果

概述

最近在开发一个移动端项目,涉及到了一天之内场景的光线随时间变化而改变,比如午后的阳光和黄昏的光线就是不一样的。这可以使场景更动态,但是却对机器的机能有了更高的要求。下面我简单的说说我的实现方法。

实现

最简单的一种实现

最简单的方法我想大家一定都能想到,用代码动态改变light组件的color属性和transform信息,从而实现连续的,真实的动态光照。这种做法的优势是十分明显的,即实现起来简单,而且是连续的,效果相对真实,但是对设备的机能要求比较苛刻,因为是实时渲染光照。作为一个移动端的项目,如果场景稍微大一点的话,帧率下降比较严重,耗电也更快。同时对于一些风格化的游戏来说,光照的色彩改变可能不够理想,不够强调,当然这一点是和真实性相对的。总而言之,这种方法很少会用在实际当中。

网上的一些实现

我之前看到网上大部分提出的都是通过动态加载lightmap光照贴图的方式来解决问题的,这样的实现除了不能做到连续的变化和包体积增大(想想每次bake完场景那恐怖的gl cache的体积)以外基本算比较合理的了。但是!Unity自己挖了不少坑给开发者,动态加载光照贴图听起来不难,但做起来还是有不少弯弯绕的,也要花不少时间。不过这已经是兼顾多个方面对解决方案了。

我的实现

前面扯了这么多,赶紧来说说我是怎么实现的吧。首先我们要来考虑一下随着时间的变化,光线是怎么变的?首先物体的影子会移动,其次就是光线的亮度和色彩。然而在世纪的过程中我们会发现,物体影子的方向的改变是不那么容易被察觉的,因为玩家大部分情况下不会一直盯着影子不看,最能直观的体现出时间变化的其实是光线的亮度和色彩。所以我的方法其实就是改变光线的色泽,忽视掉影子的变化。

那么怎么做呢?改变lightcolor属性?那不就变成实时渲染了吗?所以我们不能使用这种方式,而是通过改变Post Processing Stack中的lut属性来处理。

什么是Post Processing Stack?

Post Processing Stack是新版Unity中进行画面处理的插件,需要在AssetStore中下载并导入进来,当然,这个是Unity自己的产品,所以是免费的(话说原来的Image Effect包还要pro版的才能用)。这个插件顾名思义,是对画面进行后处理。什么叫后处理?简单的说就是在把像素即将画到屏幕上的时候在进行处理,是整个渲染管线的末端(就是处理整个画面啦)。通过后处理,我们可以实现包括噪点,雾气,阴影边缘,色阶等等各种效果,非常方便。

什么是Lut?

LutLook Up Table,是显示器根据rgb值输出像素点颜色时的参照表,所以如果我们改变Lut参照,同样的rgb输入就会显示出不一样的色彩,从而达到动态改变光照颜色的效果。Lut本身是一张长条形的Texture2D的贴图,请美工出一下就行了,这里就不具体讲怎么做了。

配置Post Processing Stack

好的,下面进入正式的操作部分,首先我们下载并导入Post Processing Stack这个插件,然后找到当前场景中的camera,点击Add Component按钮添加Post Processing Behaviour组件。这个组件其实就是一个脚本,在Inspector上暴露了一个Profile的变量。这个变量便是用于配置修改处理效果的了,所以我们还需要在资源目录下创建一个Post Processing Profile的配置资产,并把它拖进组件的Profile变量上赋值。

这些完成之后,我们就开始配置Post Processing Profile了。在Inspector上打开这个Profile,你会发现有不少的属性,由于这边博文不是介绍Post Processing Stack怎么用的,所以我们只关注我们需要的那一个属性。找到User Lut这一栏,先钩上左上的圆圈,表示启用User Lut,然后展开这个选项,为Lut那个属性选择美术给你的Lut贴图即可,注意Lut贴图的导入选项是Default。下面那个Contribution属性是这个Lut的强度,即影响力。因为我们需要通过Lut来改变色彩,所以这个值是1。

OK,目前为止你应该可以看到场景的色彩突然变化了吧,如果没有的话可能是Lut变化不是非常明显,需要和美术好好沟通一下呢。

动态改变Lut

刚刚我们对Lut有了初步的感知,但是有的人会问,现在Lut是定死的,那我们怎么在运行时改变它呢?

首先我们先常见一个Monobehaviour脚本挂在camera上面,就起名叫EnvironmentController吧! EnvironmentController代码如下:

using UnityEngine;
using UnityEngine.PostProcessing;
using System;
using System.Collections;

public class EnvironmentController : MonoBehaviour {
    // 不同时间点使用不同的Lut
	public Texture2D dawnLut;
	public Texture2D morningLut;
	public Texture2D noonLut;
	public Texture2D duskLut;
	public Texture2D nightLut;

	PostProcessingBehaviour postProcessingBehaviour;
	PostProcessingProfile profile;

	void Start() {
		postProcessingBehaviour = GetComponent<PostProcessingBehaviour>(); // 获取camera上挂载的Post Processing Behaviour脚本
		profile = postProcessingBehaviour.profile; // 获取脚本上的profile字段
	}

	public void ChangeLut(TimeOfDay timeOfDay) {
		UserLutModel.Settings settings = profile.userLut.settings; // 创建新的profile属性设定
		switch (timeOfDay) { // 给lut赋值,注意是Texture2D类型的
			case TimeOfDay.Dawn: settings.lut = dawnLut; break;
			case TimeOfDay.Morning: settings.lut = morningLut; break;
			case TimeOfDay.Noon: settings.lut = noonLut; break;
			case TimeOfDay.Dusk: settings.lut = duskLut; break;
			case TimeOfDay.Night: settings.lut = nightLut; break;
		}
		profile.userLut.settings = settings; // 赋值给profile的userLut的settings属性
		profile.userLut.OnValidate(); // 刷新效果
		Debug.Log("Change To " + timeOfDay + " Lut !");
	}
}

public enum TimeOfDay {
	Dawn, Morning, Noon, Dusk, Night
}

代码其实不难,本质上就是不同的时间点给Lut贴上不同的贴图。这里有两个点需要注意:

  1. UserLutModel.Settings是一个struct而不是class,所以需要将profile.userLut.settings整体替换掉而不是只替换settings.lut这个属性。
  2. 当更改完成之后需要调用一下profile.userLut.OnValidate();这句代码祈祷了刷新的作用,如果没有的话,画面是不会有改变的!一定要加上!

这样,我们就可以随着时间改变而使用不同的Lut,达到光线变化的效果。事实上,在一些风格化的游戏中,因为画风的原因,使用Lut可以更好的自定义画面,而不是全都用自然光模拟。

缺点

老规矩,列一下这个方法的问题:

  1. 影子不能动!这是这个方案最大的不足!
  2. 因为色彩是整个画面一起变,所以如果涉及到ui的话,需要使用另一个camera,专门渲染ui
  3. Lut生成需要美术的合作,不能使用Unity直接生成

疑问

有的人可能会好奇,说Post Processing Stack这种全屏的像素处理是不是会有比较大的性能消耗。事实上这边问题不大,首先我们只对Lut进行处理,并不是一个非常高开销的操作,同时由于移动端屏幕分辨率和屏幕大小的限制,实际的像素点比电脑上要少许多,基本上不用担心效率的问题。

总结

这个方法就是这样的,其实比较适合那种卡通风格的游戏啦,不是很写实的,自定义调Lut效果更好。如果真的是商业级的大型项目我还是建议各位去把动态加载lightmap看看吧,这边仅提供一种解决方案。

Recent Articles

Waste Of Water

It is often heard by people like me living in a wet city of water scarcity. However, fewer people really gets a figure of what is really happening just around us - the human-beings are now touching on the bottom of the earth’s well.It is hard to...…

IB,EnglishContinue
Earlier Articles

Unity中使用Delegate, Action, Func, Reflection, UnityAction动态调用函数优劣对比

概述在游戏开发中(其实别的领域也一样啦),为了保证代码框架的灵活,低耦合,拓展性强,许多时候需要动态地调用函数。在C++里面,一种特殊的指针,函数指针,承担了传递函数的功能。Javascript里也有高级函数这么一说来将函数也作为参数使用。C#作为一门拥有许多特性的现代编程语言来说,提供了多种解决方法。比较 以下内容不会特别具体介绍各个方法该如何使用,而是从实现机制这一角度来分析运行效率等方面(如果有错,请指出😂)。使用Delegate机制Delegate,直接翻译就是委托,本质上是像C...…

UnityContinue