零基础也能学会的绘制笑脸方块——Unity Shader基础之Prefabs(二)上

559 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情。你可以简单浏览一下目录,有需要的阅读,写文章不易,阅读之前请给我点个赞吧~

本文主要内容

  • 创建一个预制(prefab)
  • 实例化多个立方体(cubes)
  • C# 代码的一些精简
  • 用立方体展现一个数学函数,通过计算绘制一个你喜欢的图案吧,这里绘制了一个笑脸,祝我的女朋友天天开心,也祝每一个看到这篇博客的人天天都有好心情

下的主要内容

  • 创建一个表面着色器(surface shader)和着色器图(shader graph)
  • 让曲线动起来

一、创建一个立方体的笑脸(任意路径)

根据上节提到的内容,创建一个新的 Unity 工程。

1.1 Prefabs

  • 向场景中添加一个 cube 把它命名为 Point

    image.png

  • 移除它的 BoxCollider 组件, 因为我们不使用物理(physics)。

  • 我们将使用一个自定义组件来创建这个 cube 的许多实例,并正确地放置它们。

  • 为了做到这一点,我们将把立方体变成一个游戏对象模板。

  • 将 cube 从层次结构窗口拖到项目窗口中。

    image.png

  • 这将创建一个新的资产(asset),称为预制件(prefab)。

  • 选中 Point

    image.png

  • 选中工程目录中的 Assets 中的 Point ,你将看到

    image.png

  • 点击 Open Prefab

    image.png

  • 然后你将看到这个预制件(prefab)场景的背景是深蓝色的(天空盒在预制场景中是默认禁用的)

    image.png

  • 如果你改变预制资产,它在任何场景中的所有实例都会以同样的方式改变;

  • 改变预制的缩放比例也会改变场景中立方体的比例。但是,每个实例都使用自己的位置和旋转。此外,游戏对象实例可以被修改,这将覆盖预制件的值。

  • 使用脚本创建预制实例,意味着我们不再需要当前场景中的预制实例

  • 通过 Edit --> Delete 删除场景中的实例 point

image.png

1.2 Graph 组件

  • 创建 C# 脚本,命名为 Graph

    image.png

  • 给它一个可序列化的字段来保存对实例化点的预制的引用,命名为 pointPrefab。
    using UnityEngine;
    
    public class Graph : MonoBehaviour {
    
            [SerializeField]
            Transform pointPrefab;
    }
    
  • 上述定义的可序列化的字段,我们需要访问 Transform 组件来定位这些点,因此要将其作为字段的类型。
  • 在场景中添加一个空的游戏对象并命名为 Graph
  • 将我们的 Graph 组件添加到这个对象中,然后将我们的预制件资产 point 拖到图的 pointPrefab 字段上,并设置如下参数

    image.png

1.3 实例化 Prefabs

  • 实例化一个游戏对象是通过 Object.Instantiate 方法完成的

  • Instantiate 方法克隆任何作为参数传递给它的 Unity 对象

  • 让我们在 Graph 组件唤醒时执行此操作,代码如下

    public class Graph : MonoBehaviour {
    
            [SerializeField]
            Transform pointPrefab;
    
            void Awake () {
                    Instantiate(pointPrefab);
            }
    }
    
  • 扩展关系:MonoBehaviour 是 Behaviour 的扩展,Behaviour是 Component 的扩展,  ComponentObject 的扩展,所以上述使用 Instantiate,只需要继承 MonoBehaviour 即可。

  • 如果我们现在进入游戏模式,Point 预制(prefab)的一个实例将在世界坐标原点处生成。它的名字和预制(prefab)的是相同的,是用克隆添加的。

    image.png

  • 为了将点放置在其他地方,我们需要调整实例的位置。

  • Instantiate 方法为我们提供了它所创建的任何东西的引用。

  • 因为我们给了它一个 Transform 组件的引用,这就是我们得到的返回值。

  • 我们用一个变量来记录它。

    void Awake () {
        Transform point = Instantiate(pointPrefab);
    <img src="}" alt="" width="100%" />
    
  • 在之前的教程中,我们通过将一个四元数分配给枢轴(pivot)的变换(Transform)的localRotation 属性来旋转时钟臂。

  • 改变位置的工作原理与此相同,只是我们必须将3D向量赋值给 localPosition 属性。

  • 在 Unity 中用 Vector3 创建了一个 3D 向量,使用 Vector3.right 表示向量(1,0,0)

    Transform point = Instantiate(pointPrefab);
    point.localPosition = Vector3.right;
    
  • 游戏模式下,你将看到立方体右移了一个单位。

    image.png

  • 让我们实例化第二个,并将其置于更右侧的地方。

    void Awake () {
        Transform point = Instantiate(pointPrefab);
        point.localPosition = Vector3.right;
    
        point = Instantiate(pointPrefab);
        point.localPosition = Vector3.right * 3f;
    }
    
  • 游戏模式下,你将看到下图的样子

    image.png

1.4 代码循环

int i = 0;
while (i < 10) {
    Transform point = Instantiate(pointPrefab);
    point.localPosition = Vector3.right * i;
    i = i + 1;
}

沿着X轴排成一排的10个立方体

image.png

1.5 简洁的语法

int i = 0;
while (i++ < 10) {
    Transform point = Instantiate(pointPrefab);
    point.localPosition = Vector3.right * i;
}
for (int i = 0; i < 10; i++) {
    Transform point = Instantiate(pointPrefab);
    point.localPosition = Vector3.right * i;
}

1.6 改变作用域

目前,我们的点的 X 坐标在 0 到 9 之间。在处理函数时,这不是一个方便的范围。通常,x 的取值范围为[0,1]。当处理以0为中心的函数时,取值范围为[−1,1]。让我们相应地重新定位这些点。

上面的效果可以看到,沿着两个单位长的线段放置十个立方体会导致它们重叠,所以我们把每个点的缩放比例改成 210=15\frac{2}{10}=\frac{1}{5}

for (int i = 0; i < 10; i++) {
    Transform point = Instantiate(pointPrefab);
    point.localPosition = Vector3.right * i;
    point.localScale = Vector3.one / 5f;
}

游戏模式下,你将看到下图的样子

image.png

把这些点对称的放到 [-1,1] 里面,C# 中不会取整,保留实际运算结果

point.localPosition = Vector3.right * ((i + 0.5f) / 5f - 1f);

游戏模式下,Points 就填充了[-1,1]

image.png

简化代码

using UnityEngine;

public class Graph : MonoBehaviour
{
    [SerializeField]
    Transform pointPrefab;

    void Awake()
    {
        var position = Vector3.zero;
        var scale = Vector3.one / 5f;
        for (int i = 0; i < 10; i++)
        {
            Transform point = Instantiate(pointPrefab);
            position.x = (i + 0.5f) / 5f - 1f;
            point.localPosition = position;
            point.localScale = scale;
        }
    }
}

1.7 用X定义Y

  1. 实现 y=xy = x的直线效果

    for (int i = 0; i < 10; i++) {
        Transform point = Instantiate(pointPrefab);
        position.x = (i + 0.5f) / 5f - 1f;
        position.y = position.x;
        point.localPosition = position;
        point.localScale = scale;
    }
    

    image.png

  2. 实现 y=x2y = x^2 的直线效果

    for (int i = 0; i < 10; i++) {
        Transform point = Instantiate(pointPrefab);
        position.x = (i + 0.5f) / 5f - 1f;
        position.y = position.x * position.x;
        point.localPosition = position;
        point.localScale = scale;
    }
    

    image.png

  3. 计算一个笑脸玩玩~

    image.png

    附上代码,大家自行尝试吧~
    using UnityEngine;
    
    public class Graph : MonoBehaviour
    {
        [SerializeField]
        Transform pointPrefab;
    
        void Awake()
        {
            var position = Vector3.zero;
            var position2 = Vector3.zero;
            var position3 = Vector3.zero;
    
            var scale = Vector3.one / 5f;
            for (int i = 0; i < 10; i++)
            {
                Transform point = Instantiate(pointPrefab);
                position.x = (i + 0.5f) / 5f - 1f;
                position.y = position.x * position.x;
                point.localPosition = position;
                point.localScale = scale;
    
                Transform point2 = Instantiate(pointPrefab);
                position2.x = (i + 0.5f) / 5f - 2f;
                position2.y = -1 * position2.x * position2.x - 2 * position2.x + 1;
                point2.localPosition = position2;
                point2.localScale = scale;
    
                Transform point3 = Instantiate(pointPrefab);
                position3.x = (i + 0.5f) / 5f;
                position3.y = -1 * position3.x * position3.x + 2 * position3.x + 1;
                point3.localPosition = position3;
                point3.localScale = scale;
            }  
        }
    }
    ```