1 Creating a Line of Cubes
新建一个 Cube,将其做成 prefab。
新建脚本 Graph.cs,附在空物体 Graph 上。代码如下:
using UnityEngine;
public class Graph : MonoBehaviour
{
[SerializeField] // 在编辑器中拖动 prefab 为其赋值
Transform pointPrefab = default;
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; // 控制10个方块在x方向上填满x轴上 [-1, 1] 的空间
position.y = position.x * position.x; // y 关于 x 的函数
point.localPosition = position;
point.localScale = scale;
}
}
}
2 Creating More Cubes
修改代码,使得方块数量在编辑器中可调整,而不是固定 10 个。
知识点:
-
[Range(10, 100)]
-
Transform.SetParent
:public void SetParent (Transform p);
public void SetParent (Transform parent, bool worldPositionStays);
Graph.cs 代码如下:
using UnityEngine;
public class Graph : MonoBehaviour
{
[SerializeField] // 在编辑器中拖动 prefab 为其赋值
Transform pointPrefab = default;
// Range 属性使得该变量在编辑器中变成滑条,并且可以控制范围
// 注意该范围只是限制了编辑器中的修改,除此之外对其大小不做限制
[SerializeField, Range(10, 100)]
int resolution = 10;
void Awake ()
{
float step = 2f / resolution; // 方块的边长
var position = Vector3.zero;
var scale = Vector3.one * step;
for (int i = 0; i < resolution; ++i)
{
Transform point = Instantiate(pointPrefab);
position.x = (i + 0.5f) * step - 1f; // 控制方块在x方向上填满x轴上 [-1, 1] 的空间
position.y = position.x * position.x; // y 关于 x 的函数
point.localPosition = position;
point.localScale = scale;
// 将新实例化的方块作为 Graph 的子物体
// 第 2 个参数决定是否保持与子物体之前相同的世界空间位置、旋转和缩放
point.SetParent(transform, false);
}
}
}
3 Coloring the Graph
创建一个自定义 shader:
Create one via Assets / Create / Shader / Standard Surface Shader and name it Point Surface.
shader 的代码如下:
Shader "Graph/Point Surface"
{
Properties{
// 让 Smoothness 出现在编辑器的 Material 中
_Smoothness("Smoothness", Range(0,1)) = 0.5
}
SubShader
{
CGPROGRAM
#pragma surface ConfigureSurface Standard fullforwardshadows
#pragma target 3.0
struct Input
{
float3 worldPos;
};
float _Smoothness;
void ConfigureSurface(Input input, inout SurfaceOutputStandard surface)
{
// 颜色中的 r g 由世界坐标确定
surface.Albedo.rg = input.worldPos.xy * 0.5 + 0.5;
// 光滑度
surface.Smoothness = _Smoothness;
}
ENDCG
}
Fallback "Diffuse"
}
3.3 Universal Render Pipeline
上面的 shader 适用于 default render pipeline
导入 Universal RP 包即可使用 URP。We first have to create an asset for it, via Assets / Create / Rendering / Universal Render Pipeline / Pipeline Asset (Forward Renderer).This will also automatically create another asset for a renderer, in my case named URP_Renderer.
Next, go to the Graphics section of the project settings and assign the URP asset to the Scriptable Renderer Pipeline Settings field.
新建 shader graph:via Assets / Create / Shader / PBR Graph and name it Point URP. PBR stands for physically-based rendering.
在 shader graph 中通过构造节点可以达到与上一节的 shader 代码类似的效果,具体操作参考原文。
4 Animating the Graph
修改 Graph.cs,使得方块展示一条随时间移动的正弦曲线。
知识点:
- 数组的声明与实例化
代码如下:
using UnityEngine;
public class Graph : MonoBehaviour
{
[SerializeField] // 在编辑器中拖动 prefab 为其赋值
Transform pointPrefab = default;
// Range 属性使得该变量在编辑器中变成滑条,并且可以控制范围
// 注意该范围只是限制了编辑器中的修改,除此之外对其大小不做限制
[SerializeField, Range(10, 100)]
int resolution = 10;
Transform[] points; // 声明数组
private void Awake ()
{
float step = 2f / resolution; // 方块的边长
var position = Vector3.zero;
var scale = Vector3.one * step;
points = new Transform[resolution]; // 实例化数组
for (int i = 0; i < points.Length; ++i)
{
Transform point = Instantiate(pointPrefab);
position.x = (i + 0.5f) * step - 1f; // 控制方块在x方向上填满x轴上 [-1, 1] 的空间
point.localPosition = position;
point.localScale = scale;
// 将新实例化的方块作为 Graph 的子物体
// 第 2 个参数决定是否保持与子物体之前相同的世界空间位置、旋转和缩放
point.SetParent(transform, false);
points[i] = point; // 把方块放进数组
}
}
private void Update()
{
float time = Time.time; // 优化代码,防止每一轮循环都调用一次
for (int i = 0; i < points.Length; ++i)
{
Transform point = points[i];
Vector3 position = point.localPosition;
position.y = Mathf.Sin(Mathf.PI * (position.x + time)); // y 关于 x 的函数
point.localPosition = position;
}
}
}