Unity光照学习笔记

之前看了几篇教程,感觉Unity似乎没多么难,但自从接触到了光照后,真的是看不明白,知识实在是太多太庞杂了。Unity很多分支真的非常需要深入研究。

本篇从有限的学习整理出来,不一定是正确的。

首先是一篇https://www.jianshu.com/p/7594b044e6dc ,看发布日期是17年,太久远了,很多技术都更新了,看个大概吧。

游戏场景中灯光照明的构成

光照渲染计算方案,总体上分为这样几类:

  1. 直接模拟光线从被光源发出到最终被物体完全吸收的正向过程,也就是常说的GI(Global Illumination);
  2. 不直接模拟光线,而是反向搜集物体表面特定点的受光照强度来模拟现实照明效果,也就是常说的FG(Final Gathering);
  3. 完全不考虑光线的行为,单纯基于“物体上与其他物体越接近的区域,受到反射光线的照明越弱”这一现象来模拟模拟现实照明(的一部分)效果,也就是常说的AO(Ambient Occlusion);
  4. 将场景光照结果完全烘焙到模型贴图上,从而完完全全的假冒现实光照效果,也就是我们所说的Lightmap。

GI、FG,计算量都非常大。

光照贴图需要事先烘焙(baking)出来,且仅支持静态物体(Static Object)。

游戏场景中几乎不可能全都是静态物体,所以通常游戏场景中的灯光照明是多种照明方式的混合作用。

  1. 对于静态物体来说,大多使用光照贴图来模拟间接光的照明效果,然后加上直接光源的动态照明效果;
  2. 对于运动物体来说,则仅用直接光源的动态照明效果,或者使用光照探针来模拟间接光的照明效果。

1.直接照明(Direct Lighting)

Unity3D中的直接照明主要来源于各种灯光物体,而灯光物体本质上是空物体加上灯光组件。直接照明可以产生阴影,但光线不会反射、也不会折射,但可以穿透半透明材质物体。

1.1 灯光类型(Lights)

Unity3D中默认可以创建这么几种灯光:聚光灯、点光源、平行光、面积光,另外还可以创建两种探针(Probe):反射探针(Reflection Probe)和光照探针组(Light Probe Group)。

不行了先暂停了,太难了,慢慢理解。

Unity 3D坦克大战 学习笔记

炮弹发射位置

可以给坦克Prefab增加一个子empty object,位置是坦克炮筒的位置,方向是炮筒方向,这样在发射炮弹脚本处,可以使用这个object的position、rotation。

炮弹碰撞播放粒子特效并销毁炮弹、销毁特效

通过 粒子动画.transform.parent = null 取消粒子与父控件(炮弹)的绑定关系;

Destroy(粒子动画.gameObject, 粒子动画.main.duration) 使得在播放完粒子动画后销毁粒子动画;

Destroy(this.gameObject) 销毁炮弹;

炮弹爆炸播放声音问题

一开始是将播放audio source放在炮弹object中,但是有问题,炮弹碰撞后销毁,声音也随之销毁了。导致无法播放爆炸声音。

正确的做法是将audio source放在爆炸粒子上,随着爆炸粒子销毁。想想也是,因为声音是在爆炸点发出的,如果是跟随炮弹的话,显然声音的位置是不对的。

获取范围内Collider

Collider[] colliders = Physics.OverlapSphere(transform.position, m_ExplosionRadius, m_TankMask);

其中第一个参数是球中心点,第二个参数是半径,第三个参数是要获取的Collider的Layer。

Lerp与SmoothDamp

Lerp是线性的。

private float t1;

t1 += Time.deltaTime;
transform.position = Vector3.Lerp(startPos, target, t1);

t的值是在0-1范围内。

Lerp有时候被使用在平滑场景:

transform.position = Vector3.Lerp(transform.position, target, 0.1f);

个人认为这种使用方法是不对的,理论上position越来越接近目标位置,但是永远无法达到。

SmoothDamp是平滑的。

public float m_DampTime = 0.2f;
private Vector3 m_MoveVelocity;

transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);

https://www.jianshu.com/p/8a5341c6d5a6

TransformPoint和InverseTransformPoint

将自身坐标转换成世界坐标,和 将世界坐标转换成自身坐标

用法:transform.InverseTransformPoint(Vector3)

Camera.aspect

宽÷高

Camera.orthographicSize

The viewport size of the Camera when set to Orthographic. 当Camera选择正交模式时的视窗大小。

也就是说Camera是Orthographic模式时,可以通过设置orthographicSize以实现镜头拉近、拉远的效果。

注意这个size = 视窗高度的一半

while循环中yield return null;

    private IEnumerator RoundPlaying ()
    {
        // While there is not one tank left...
        while (!OneTankLeft())
        {
            // ... return on the next frame.
            yield return null;
        }
    }

yield return null; 的作用是暂缓一帧,和java中的sleep作用是类似的,sleep是指定暂停多少毫秒,yield return null,是暂停一帧,这一帧过后继续while循环,直到判断条件为false,跳出循环,继续。

外层调用为

    private IEnumerator GameLoop ()
    {
        yield return StartCoroutine (RoundStarting ());

        yield return StartCoroutine (RoundPlaying());

        yield return StartCoroutine (RoundEnding());

        if (m_GameWinner != null)
        {
            Application.LoadLevel (Application.loadedLevel);
        }
        else
        {
            StartCoroutine (GameLoop ());
        }
    }

联合起来就是直到round playing结束后,继续后面的代码。

感觉很有用,可以用在游戏关卡通关控制中。

Transform.Translate与Rigidbody.MovePosition

Transform.Translate是单纯的位移,如果碰撞体遇到障碍物,会有一定的问题,例如抖动、穿透等;如果使用Rigidbody.MovePosition则不会有这些问题。

Awake、OnEnable和Start的执行顺序

在同一个脚本中他们的执行顺序是Awake() —>  Enable() —>  Start()

GameObject的Activity为true,脚本的enable为true时,其先后顺序为:Awake、OnEnable、Start;

GameObject的Activity为true,脚本的enable为false时,只运行Awake;

GameObject的Activity为false时,以上都不调用,OnDisable()被调用;

DustTrail不显示问题

修改粒子属性中”Emiter Velocity”. Change it from Rigidbody to Transform

Slider转圈Progress

正常的Slider的进度是水平的,虽然图片选择成了圆圈,但是如果不设置,还是从左到右。

点击Slider中的Fill,Image Type选择Filled,Fill Method选择Radial 360。

使用InputSystem后,引入UI,导致报错问题

引入UI后,运行报错:

InvalidOperationException: You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings.

解决办法是创建EventSystem,然后再属性中会看到红色叹号,点击下面的Replace with InputSystemUIInputModule。

slider的fill图片变形问题

按照视频里的讲解,给坦克添加蓄力指示箭头。添加完后,发现slider填满后,箭头拉抻变形了,本应是类似Android的9patch,只拉抻一部分。找不到什么原因。

经过不断修改参数尝试,发现修改一些参数可以解决问题,但是不知道所以然。

修改的参数如下:

分别是Sprite和Slider的Fill。

Unity Package Manager下载路径

Windows – User>AppData>Roaming>UnityAsset Store-5.x>’publishername’

Mac – User>Library>Unity>Asset Store-5.x>’publishername’

https://support.unity.com/hc/zh-cn/articles/210112873-%E5%A6%82%E4%BD%95%E4%B8%8B%E8%BD%BD%E5%B7%B2%E8%B4%AD%E4%B9%B0%E7%9A%84%E8%B5%84%E6%BA%90-

Unity C#初级编程 学习笔记

https://learn.u3d.cn/tutorial/beginner-gameplay-scripting

矢量数学

向量的点积和叉积的几何意义

点积:一个向量在另一个向量的投影长度乘以另一个向量的长度。当向量夹角小于90度时,为正;90度时为0;大于90度时,为负。

叉积:垂直于两个向量的向量,向量长度是两个向量为边的平行四边形的面积。

https://www.bilibili.com/video/BV1BT4y157Sa

视频直观地感受一下。

在Unity中,向量点积可以使用Vector3.Dot(Vector3,Vector3):float来计算;叉积可以使用Vector3.Cross(Vector3,Vector3):Vector3来计算。

Active Object激活对象

gameObject.SetActive(true);

停用对象后,对象消失,在Hierarchy中呈灰色。

如果停用对象的父对象,则父对象、子对象都消失。此时myObject.activeSelf返回True,myObject.activeInHierarchy返回False。

enable和active的区别

active是针对对象的,enable是针对组件的(例如对象身上的光组件)。

active、enable和destroy的区别

个人理解active、enable设为false后,该对象、组件还能被重新设为true并显示;Destroy(xxx)后,则被销毁了。

Destroy(gameObject, 1.5f)意为在1.5秒后销毁对象。

Instantiate

Instantiate用于创建Prefab的克隆体。可以设置放置位置、角度。如果想要对其施加力,可以如下方法:

            Rigidbody rocketInstance;
            rocketInstance = Instantiate(rocketPrefab, barrelEnd.position, barrelEnd.rotation) as Rigidbody;
            rocketInstance.AddForce(barrelEnd.forward * 5000);

Invoke、InvokeRepeating

Invoke作用是多少秒后调用某个方法,InvokeRepeating作用是在Invoke基础上,间隔多少秒调一次。

调用方法如下:

Invoke ("SpawnObject", 2);

InvokeRepeating("SpawnObject", 2, 1);  // 2秒后调用,并每隔1秒重复调用

void SpawnObject()
{
    float x = Random.Range(-2.0f, 2.0f);
    float z = Random.Range(-2.0f, 2.0f);
    Instantiate(target, new Vector3(x, 2, z), Quaternion.identity);
}

使用AE制作出DV风格的时间戳

目标:做出DV风格的时间戳,且时分秒随着时间而变化。

最开始打算用Pr做,但是在网上搜索了一番,似乎没有好的解决办法,遂打算使用AE做。然鹅从没使用过AE,所以在B站上快速学习了一下。

https://www.bilibili.com/video/BV1ZA411g7Sb

字体

首先需要时间戳的字体。

https://www.dafont.com/vcr-osd-mono.font

看起来还不错,下载下来安装到电脑中。

字体加粗,黑色描边。

时间戳色散效果

虽然字体很像,但是看起来太清晰了,没有DV那种模拟信号的感觉。所以下一步是做一下图像处理,使得看起来更真实一些。

首先建立Timecode的合成,将要显示的时间日期展示出来。

再建立一个Chromatic的合成,将Timecode合成引入进来,三份。三份分别只显示R、G、B三个颜色,混合模式为相加。这样三个合成分别只显示红绿蓝三种颜色,他们叠加起来是正常的颜色,如果想要色散的效果,可以微微移动三个合成的位置,效果如下。

可以看出来,文字边界有了色散的效果,更有内味儿了。

时间戳随着时间变动

录的DV是一段一段的,时间是不同的,在切换段后,时间也要变化到新的时间,并依然一秒一秒变动。

能想象到的比较优雅的实现方式是在主合成中,在关键帧上给时间戳赋新值。

如何跨合成获取到一个值呢?可以使用slider。通过类似下面的方法获取到slider的值:

comp("Final output").layer("Chromatic").effect("month")("ADBE Slider Control-0001")

在Final output合成中,Chromatic图层创建年月日时分秒的slider,分别代表年月日时分秒的数字,这样,在Timecode合成中,只需要格式化显示这些数字即可。

在Final output中,需要将second的slider随着时间增加而增加秒数。代码如下:

effect("second")(1).value += time;

time是个浮点型,单位是秒。这样在Timecode这边需要在代码中对second取整,代码比较简单不再赘述。

在Final output中,每当录像切换到另一段时,对几个变化的slider增加关键帧,并修改其数值。需要注意的是,每当增加关键帧,其数值会在连个关键帧间线性变化,这不是我们想要的,我们想要的是直接变化,要修改补间动画的图表,变成这样的:

最终效果

看起来还不戳。

磁带DV通过1394导入电脑数字化50FPS视频文件踩坑

在咸鱼淘了一个几乎全新的松下NV-GS28GK mini DV,打算录磁带DV然后导入电脑数字化为视频文件。

经过网上搜索资料得知,可以通过1394将DV连接到电脑,再使用Pr将DV信号捕捉下来,生成视频文件。遂在京东买了个1394 – PCI-E采集卡,插到台式机上。1394线一端连采集卡,一端连DV。打开Pr,点击文件->捕捉,选择DV,设置一番,就可以开始录制了。

录制完成后,发现生成的avi文件是25FPS,不知何故。如果只有25FPS视频播放起来会觉得比较卡,按说DV录制的视频帧率应该是50FPS才对。难道是采集卡不支持50帧率?

在网上查找资料,得知PAL制式是25帧率,但是由于是隔行扫描,每秒可以有50幅。看得雨里雾里,遂新建了一个50帧率的序列,将刚才的avi文件拖了进来,播放了一下,非常流畅,确实是50FPS的。成功!

使用1394导入电脑的视频是没有时间戳的,如果使用AV采集,是可以采集到时间戳的,但是视频质量不如1394。所以如果想要DV那种复古的时间戳,要么使用AV采集,要么后期加时间戳,下一篇文章说说AE加时间戳的踩坑叭。

Unity镜头跟随Player

想让镜头跟随Player移动,并且在Player正后方,随Player转动,始终看向Player。

在Player正后方

gameObject.transform.forward是该game object的正前方Vector3,所以可以使用gameObject.transform.position – gameObject.transform.forward * a来得到game object的正后方a单位远的Vector3。

看向Player

transform.LookAt(player.transform);

Unity InputSystem学习笔记

只是了解了皮毛,记录一下。

引入包

Windows -> Package Manager -> Input System引入包。

创建按键 – Action映射

对player Add Component -> Player Input,Create Actions创建input action文件。在input action文件内创建按键 – Action的映射。

按键可以是键盘、手柄、鼠标、屏幕点击等,在此对输入差异做了整理,代码端只会收到Action,而不必关注是什么输入设备输入的。

可以创建Button、Value等类型。如果是Button类型,则不会传递值,按下键则出发回调方法;如果是Value类型,可以设置传递的值的类型,比如float、Vector2等;例如移动就是Vector2类型,键盘或手柄等设备方向键传过来的值是个二维数组。Xbox手柄的LT、RT键是带有梯度的,可以设为传float值,根据不同的按压深浅,传递的值在0 – 1.0之间。

代码端

代码端实现创建的Action方法,方法名是OnXxx,如果是Value类型的,参数则是InputValue,可以通过inputValue.Get<T>()获取参数值。

更新—-

上述是在Player Input中Behavior选择Send Messages情况下,经过这段时间摸索,选择Invoke Unity Events更好一些。

点击Events -> Player,可以看到在input action中创建的事件,在此都有显示。点击+,选择Runtime,下面的文件将接收控制的GameObject拖入,如果GameObject下有Script,则在右侧可以选择Script中的方法。

具体实现可以参考官方Demo中的Player Input。

如何多手柄控制?

例如本地游戏,键盘控制1p,手柄控制2p。

是一个值得深入研究的问题。从有限的大致了解来看,似乎可以通过PlayerInputManager的Joining来实现,待后续进一步研究。

长按蓄力

在input action文件内创建Button类型,Interactions创建Press,Trigger Behavior选择Release Only。

在代码段:

    public void OnFire(InputAction.CallbackContext context)
    {
        //Debug.Log("OnFire:" + context.ToString());
        switch (context.phase)
        {
            case InputActionPhase.Performed:
                Fire();
                break;

            case InputActionPhase.Started:
                m_Fired = false;
                m_Charging = true;
                break;
            case InputActionPhase.Canceled:
                break;
        }
    }

可以再input action内选择不同的Interactions、Trigger Behavior,在代码端打印日志,摸着石头过河。

Create with Code学习笔记

一些基本操作

按下鼠标右键时,wasd可以操控视角前后左右移动,qe可以操控上下移动。

选中某个object时,按下alt/option,并且按下鼠标左键,拖动鼠标,可以以该object为中心转动视角。

小车碰撞箱子后车飞了

Edit -> Project Settings -> Physics

修改Default Max Depenetration Velocity修改到100。不知道为什么,暂时能解决。

感觉这里设置值得仔细研究。

使用Instantiate创建实例

使用Instantiate(prefab, position, rotation)创建在某个位置上创建一个prefab实例。(例如发射子弹)

Mesh Collider和Box Collider

个人理解:Mesh Collider是以模型的边界作为碰撞计算边界,由许多三角形构成。如果模型非常复杂, 则计算量较大;Box Collider是以设置的长方体边界作为碰撞计算边界,可以通过EditCollider修改碰撞边界。

一个Object可以绑定多个script

都生效

修改后,点击Overrides应用到prefab

rt

Nested Prefabs与Prefab Variant

https://learn.unity.com/tutorial/introduction-to-nested-prefabs

https://www.bilibili.com/video/BV1ut411e7RQ

可以翻译成 嵌套Prefab 与 Prefab变体。

Nested Prefabs:考虑如下需求——枪+瞄准镜。枪和瞄准镜都是Prefab。如果没有嵌套Prefab,那么想修改瞄准镜,需要把每一种枪的瞄准镜都修改了;如果有嵌套Prefab,只需修改瞄准镜的Prefab即可,修改完成后,所有的枪+瞄准镜Prefab中的瞄准镜都发生改变。

Prefab Variant:考虑如下需求——一群机器人,长得都一样,有的移动快有的慢,有的尺寸大有的小。可以使用Prefab Variant,对机器人Prefab生成变种(右击Prefab – Create – Prefab Variant),可以修改变种的属性,例如移动速度、尺寸等。如果修改Base Prefab,例如换个头,则会应用到所有Prefab变种上。

理论听得一知半解。实际应用中这两个需求应该是刚需,会方便许多。

Vector3.normalized

可以获得该向量的单位长度的向量。

ForceMode

给Rigidbody施加力,调用AddForce函数,ForceMode可以有4种选择。

Force – 连续施力,质量有关

Impulse – 瞬时力,质量有关

Acceleration – 连续加速,质量无关

VelocityChange – 瞬时加速,质量无关

动画

playerAnim = GetComponent<Animator>(); 获取到animator,

playerAnim.SetTrigger(“Jump_trig”); 触发动画条件。

playerAnim.SetBool(“Death_b”, true); 设置变量状态

Rigidbody设置质量

可以在Rigidbody中设置mass改变质量。看起来改变质量不影响降落速度。

找到某个Object,从其身上获取某个脚本

GameObject.Find("Player").GetComponent<Player>();

获取某个类型的Object数组

FindObjectsOfType<Enemy>()

对比Tag

gameObject.CompareTag("Ground")

播放粒子动画

explosionParticleSystem.Play(); 播放

explosionParticleSystem.Stop(); 停止

声音播放

无论播放音乐还是音效, 都是通过AudioSource播放AudioClip。

背景音乐播放可以在Camera下添加AudioSource,将音频asset拖拽到AudioClip上。

音效可以再代码中,通过GetComponent<AudioSource>()获取到AudioSource,再调用audioSource.PlayOneShot(crashSound, 1.0f);播放(crashSound为AudioClip)

带有方向的音效

将场景中的物体添加AudioSource,拖动Spatial Blend到3D。还可以选择多普勒效应等级。

IEnumerator,yield,协程

    void Start()
    {
        StartCoroutine(Count3sec());
    }

    IEnumerator Count3sec()
    {
        yield return new WaitForSeconds(3);
        Debug.Log("This happens after 3 seconds");
    }

在返回IEnumerator的方法内,可以多次yield return,相当于协程暂停。

挺复杂,不好理解,详细参见:

Unity协程到底是个什么玩意?

物理材质Physic Material

Create > Physic Material 创建物理材质;拖拽到Collider的Material上生效。

物理材质的几个属性功能如下:

https://blog.csdn.net/qq_21397217/article/details/80277857

Unity Roll-a-ball 学习笔记 2

B站Unity官方放出了Roll a ball新版教学视频,中文字幕。有一些和之前版本不一样的变动,记录一下遇到的问题。

InputSystem

原输入系统 UnityEngine.Input 在多设备和多平台输入处理时显得力不从心且不够优雅,甚至连游戏中热插拔手柄这种操作都显得十分臃肿和复杂。Unity Tec 从 2016 年起开始逐步开发新一代输入系统 UnityEngine.InputSystem 。

https://www.jianshu.com/p/a14ac96e6384

感觉InputSystem是个比较值得深入研究的东西,后续慢慢研究叭。

首先需要添加InputSystem包,windows -> Package Manager 搜索InputSystem添加并重启。

选中Player,Add Component -> Player Input -> Create Actions创建input actions。

选中Player,Add Component -> New Script

在cs端的void OnMove(InputValue movementValue)方法内可以收到用户操作的移动信息。

但是使用过程中遇到问题,报Unity type or namespace name ‘InputSystem’ does not exist in the namespace ‘UnityEngine’。解决办法是在Unity端Preferences -> External Tools,External Script Editor选择Visual Studio,在下方勾选Registry packages,并点击Regenerate project files。

Unity type or namespace name ‘InputSystem’ does not exist in the namespace ‘UnityEngine’

Time.deltaTime

The interval in seconds from the last frame to the current one (Read Only)

这帧距离上一帧的秒数。所以在计算pick up应该旋转多少角度时,应该用速度乘以此秒数,这样就不会因为卡阵而造成旋转卡顿。

    transform.Rotate(new Vector3(15, 30, 45) * Time.deltaTime);

在编辑器中切换某个object的拖拽方向

在旋转了pick up之后,发现无法在全局坐标系下拖拽,只能在旋转后的坐标系下拖拽。google没搜索到结果,于是瞎点,发现左上方第二组有个按钮,点击可以在Global和Local之间切换。点击切换到Global即可在全局坐标系下拖拽。