【LensFlare镜头光晕】Unity3D奇葩实现

前言

今天我们来做LensFlare的另类奇葩模拟实现(通过绘制2D—UI的方式来实现LensFlare)。不需要读者编写任何特效Shader,不涉及到任何跟渲染相关的知识点。

实现尽可能的模拟光晕,基于本思路实现的光晕效果如何最终取决于“光晕”纹理的设计如何,以及光晕的摆放问题,后面会说。效果当然可能比不上使用高级图形学知识去通过shader渲染的效果。本文重在分享这种奇葩方式。如文章有错误的地方,还望诸位大神指出。


什么是Lens Flare?

Lens Flare 又称为”镜头光晕“。在游戏中镜头指的是经过视椎体做可视裁剪过后将可视物体渲染到RenderTarget中所呈现出来的画面。RenderTarget可以简单理解成当前游戏窗口的画面。光晕则指的是当镜头中存在光源时,光源会在镜头中会产生一种带有颜色的连续”光圈“特效。看下图就明白了。
图片来源互联网,侵权删
呃,这个是网上的图,我们做的效果肯定没那么逼真,因为PS不好做的素材不行,如果你有逼真点的光晕素材的话,效果绝对杠杠的。


对读者的要求

虽然这次模拟LensFlare不涉及到任何shader特效,但是Demo基于Unit3D开发。
因此笔者假设诸位读者具备以下技能:

1、对”向量”有一定了解。
2、熟悉C#或者其他编程语言
3、熟悉Unity3D
4、Photoshop(可有可无,会的话可以自己做素材)


Step1:【光晕素材】

1、在PS中新建一张1600X900的透明图,使用PS工具的多边形工具摁住左Shift键拖动鼠标绘制出一个正五边形,颜色要白色。
PS多边形工具
正五边形



2、新建图层使用PS工具的椭圆工具摁住左Shift键拖动鼠标绘制一个正圆,颜色也要白色。
新建图层
椭圆工具
正圆

3、好了到这里我们就把素材做完了。
What?这么简单?因为我们只需要模拟光晕,不强调逼真度,不然你可以给上述2个形状添加透明渐变。可以实现更逼真的效果。现在我们只需要将素材导入到Unity3D中去。


Step2:【Unit3D资源准备】

1、新建3D工程(默认工程里面会有一个Main Camera和一个Directional Light)
新建工程


2、新建一个Light->Point Light, 虽然太阳光是用方向光来表示,但是在这里我们不打算用这个,而是用一个Point Light来模拟光源位置,命名为Sun,老油条自己决定拿什么做光源。
SUN
将Sun移动到下图屏幕的大致位置来模拟太阳,并设置如下属性。
Range = 4;
Intensity = 8;
Draw Halo[打钩];
SUN设置


3、将上面完成的素材导入到Unity3D中去,并做一些配置.
导入图片



点击图片在右边的编辑栏里面设置相应的图片属性,然后保存
Texture Type 设置为 Sprite(2D and UI)
Sprite Mode 设置为 Multiple
设置图片属性



打开Sprite Editor
打开SE



如下图直接点击(1步骤)按钮Slice,会发现2个素材出现绿色矩形边框(2步骤),然后点击(步骤3)Apply
编辑图片



可以看到图片被切分成2张图,Flare1和Flare2
切分图片


4、接着我们开始创建光晕UI

首先创建一个画布Canvas,在Canvas下通过Create Empty创建一个GameObject并改名为LensFlareManager.如下图所示
LFM



接着在LensFlareManager下创建UI->Image用来存放光晕精灵图片。多复制几个,具体多少看自己想做多少个光晕了。我们在这里创建10个。并全部改名为Flare1~10,如下图所示
Flare1-10



在Scene场景编辑中切换到2D,开始摆放这些Flare,摆放序号从左下到右上依次1->10.如下图所示
FlareSetPos



将已经切分出来的素材(正五边形和正圆)分别放入这些UI中,随便放看自己的设计了。同时调整UI的大小、RGB颜色和透明度。对于位置粗略摆放即可,最终位置将会由代码计算出来,现在设置位置只是为了做个大致参考而已。笔者的摆法如下图。
LENSFALRE
相比较上图是不是有点感觉了?哈哈。不过现在只是摆放而已,现在光晕还不会根据太阳的方位动起来,因此我们需要编写代码来计算出这10个光晕的具体位置在哪里。在编写代码之前我们需要在原理的基础上介绍Flare的位置选取原理。不然直接上代码可能有些人会被搞晕。


Step3:【解析实现原理】

1、首先我们来看下面这张图,要想计算出10个光晕摆放的位置,首先我们需要求出图中所示的关键点位置。

  • 2D-SunPos:太阳在当前摄像机中的2D屏幕位置。
    获取方式Camera.WorldToScreenPoint(Vector3 position);
    在函数参数中传入3D-Sun的位置即可获取太阳在屏幕上的位置。

  • 2D-Mid-ScreenPos:2D屏幕空间中心位置。
    获取方式:Vector2 midScreenPos = new Vector2( Screen.Width * 0.5f , Screen.Height * 0.5f );

  • LensDir : 是一条向量,方向是从2D屏幕中心点指向太阳在屏幕中的2D位置点。长度为两点间距离,后面我们也要用到这条向量的长度来根据比例算出对应光晕在这条向量的方向上的对应坐标。

  • LensDir2 : 也是一条向量,方向与LensDir相反,长度不确定(因为我们要通过程序动态控制)

原理图1




2、在计算出关键点位置之后我们就要开始计算10个光晕对应的位置了,计算的原理是基于LensDir的方向上根据给定的比例来计算对应的坐标,这些坐标都会坐落在LensDir这条向量上。

  • 接下来我们要给定10个光晕的比例值,如下图中的X1~X10.

  • 由上面计算出信息可知,设P1~P10为对应10个光晕的位置点
    则有: P(x=1~10) = 2D_mid_ScreenPos + ( Dis * X(x=1~10) * LensDir.Normalized )

    • Dis:2D太阳在屏幕位置2D_SunPos和2D屏幕空间位置2D_Mid_ScreenPos之间的距离。

    • X(x=1~10) : 对应光晕在LensDir上的比例值应该在(-1 <= X <= 1 ) 区间内。值为负数时方向与LensDir2一致

    • LensDir.Normalized : LensDir的单位方向向量。


原理图2




3、需要注意的是,当太阳离开屏幕时我们不希望看到太阳光晕,因此我们还需要对太阳的位置进行判断。当太阳不在屏幕中时将不会显示LensFlare.
  • 通过上面的操作之后我们能获取到太阳在屏幕中的2D位置”2D_SunPos”,我们只需要判断这个点在不在2D屏幕中即可,当太阳离开屏幕时(太阳还在摄像机正面时),其2D坐标值必然不在屏幕中,而屏幕的范围横向是(0~Width)的范围,纵向是(0~Height)的范围,也就是说我们需要判断(0<=x<=Width)&&(0 <=y <=Height)条件满足即可。

屏幕

  • 除了做以上操作以外,我们还需要判断太阳是不是处在摄像机的前面,如下图Sun_Pos1很明显映射后会出现在屏幕中,Sun_Pos2已经超出屏幕位置,Sun_Pos3虽然在后面,但是X-Y映射还是会在屏幕上,因此会出现屏幕被朝太阳时还是会出现光晕,这个情况是我们不希望看到的,因此我们需要判断太阳是不是在摄像机的前面,庆幸的是这个条件很好判断。摄像机当前的Forward向量是已知的,然后再计算出另一条从摄像机位置指向太阳位置的就可以得到向量Dir.然后将Forward和Dir做点积运算结果大于0时则表示太阳在摄像机前方。
  • 方向判断
  • 从下图可以看出Forward跟P1、P2、P3的夹角小于90°因此点积结果大于0
    说明对应位置的太阳在摄像机前面,而Forward和P4的夹角大于90°因此点积结果小于0。以此来判定太阳和摄像机的位置即可。
  • 方位判断2

Step4:【编写代码实现】

  1. 首先我们需要创建一个用来管理LensFlares的脚本命名为LensFlareManager。
  2. 逻辑脚本的类成员变量声明如下
    public Camera      _Camera;//主摄像机
    public Transform   Sun;//太阳
    public Transform[] LensFlares;//镜头光晕的数组
    public float[]     Rates;//各个对应光晕的位置比例



3. 回到检视面板,将脚本拖到LensFlaresManager的对象上去(注意:在这里名字虽然一样但是一个是脚本,另一个是对象别搞混了),然后将对应的Object拖动到脚本对应的变量槽位中去,并设置对应的值如下图。
这里写图片描述




4.完全代码编写如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LensFlareManager : MonoBehaviour {


    public Camera      _Camera;//主摄像机
    public Transform   Sun;//太阳
    public Transform[] LensFlares;//镜头光晕的数组
    public float[]     Rates;//各个对应光晕的位置比例
    // Use this for initialization
    void Start () {
    }

    // Update is called once per frame
    void Update () {

        float width  = Screen.width;//屏幕宽度
        float height = Screen.height;//屏幕高度

        Vector2 Sun_2D_Pos = _Camera.WorldToScreenPoint(Sun.position);//太阳2D位置
        Vector2 Mid_2D_Pos = new Vector2(width * 0.5f, height * 0.5f);//屏幕中心位置
        Vector2 LensDir    = Sun_2D_Pos - Mid_2D_Pos;//屏幕中心指向太阳2D位置的向量

        //摄像机到太阳3D位置的向量,用来判断3D空间中太阳是否处在摄像机的前面。
        Vector3 SunDir     = Sun.position - _Camera.transform.position;
        //判断太阳是否在摄像机的前面
        bool isFront       = Vector3.Dot(_Camera.transform.forward, SunDir) > 0 ? true : false;
        //屏幕空间中,屏幕中心点到太阳2D点的距离。用来计算各个光晕的位置
        float   dis        = Vector2.Distance(Sun_2D_Pos, Mid_2D_Pos);


        //循环遍历整个(这里是10个)光晕列表
        for (int i = 0; i < LensFlares.Length; i++)
        {
            //当太阳2D位置在屏幕范围中,并且在3D空间中也处在摄像机的前面
            if ((Sun_2D_Pos.x >= 0 && Sun_2D_Pos.x <= width 
            && Sun_2D_Pos.y >= 0 && Sun_2D_Pos.y <= height) && isFront)
            {
                //根据Rates[i]中的对应比例计算出当前光晕的位置
                LensFlares[i].position = Mid_2D_Pos + (dis * Rates[i] * LensDir.normalized);
                //开启光晕
                LensFlares[i].gameObject.SetActive(true);
            }
            else //不在2D屏幕范围中
            {
                //关闭光晕
                LensFlares[i].gameObject.SetActive(false);
            }
        }
    }
}

Step5:【最终特效】

为了更好的观看LensFlare你还需要一个摄像机的移动旋转控制脚本。不然摄像机在不移动的情况下很难观察到LensFlare做出来的最终结果。
结果1
结果2
结果3
结果4

Step6:【总结】

- 优点:

  • 灵活性高,实现简单,易于操作和维护。
  • 如果有好一点的光晕纹理可以实现接近真实光晕的效果
  • 不需要编写shader就可以实现

- 缺点

  • 太依赖美工的素材,如果素材不好做出来的特效会很蹩脚。
  • 等等

- PS

  • 这里为了测试才使用Point light作为光源,实际情况请根据自身需要来实现

  • 没有做Alpha过渡,例如一般情况下太阳越接近屏幕中心光晕越强,反之则会变弱。这里由于篇幅原因就不写了。最简单的Alpha过渡就是根据太阳2D位置到屏幕中心的距离跟一个常量数值做除法算比例然后让当前各个光晕的Alpha乘以这个比率即可。

  • 实际实现中计算光晕位置的方式有很多,这里只是其中一种,有些游戏的光晕是太阳越靠近屏幕中心点,则光晕会从两边聚起来,反之则散开。

Step7:【感谢】

如果你有什么好的意见或者建议,欢迎在评论区一起交流。最后谢谢大家来到我的博客^_^。

  • 15
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值