【UnityShader】雪を降らせる【1】#38
前回の成果
雪を積もらせた。
今回やること
雪を降らせます。
ソースコード
Script
using UnityEngine; [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class Snow : MonoBehaviour { private const int SNOW_NUM = 16000; private Vector3[] _vertices; private int[] _triangles; private Vector2[] _uvs; private float _range; private float _rangeR; private Vector3 _move; void Start() { _range = 16.0f; _rangeR = 1.0f / _range; _vertices = new Vector3[SNOW_NUM * 4]; for (int i = 0; i < SNOW_NUM; i++) { float x = Random.Range(-_range, _range); float y = Random.Range(-_range, _range); float z = Random.Range(-_range, _range); var point = new Vector3(x, y, z); _vertices[i * 4 + 0] = point; _vertices[i * 4 + 1] = point; _vertices[i * 4 + 2] = point; _vertices[i * 4 + 3] = point; } _triangles = new int[SNOW_NUM * 6]; for (int i = 0; i < SNOW_NUM; i++) { _triangles[i * 6 + 0] = i * 4 + 0; _triangles[i * 6 + 1] = i * 4 + 1; _triangles[i * 6 + 2] = i * 4 + 2; _triangles[i * 6 + 3] = i * 4 + 2; _triangles[i * 6 + 4] = i * 4 + 1; _triangles[i * 6 + 5] = i * 4 + 3; } _uvs = new Vector2[SNOW_NUM * 4]; for (int i = 0; i < SNOW_NUM; i++) { _uvs[i * 4 + 0] = new Vector2(0.0f, 0.0f); _uvs[i * 4 + 1] = new Vector2(1.0f, 0.0f); _uvs[i * 4 + 2] = new Vector2(0.0f, 1.0f); _uvs[i * 4 + 3] = new Vector2(1.0f, 1.0f); } Mesh mesh = new Mesh(); mesh.name = "MeshSnowFlakes"; mesh.vertices = _vertices; mesh.triangles = _triangles; mesh.uv = _uvs; mesh.bounds = new Bounds(Vector3.zero, Vector3.one * 99999999); var mf = GetComponent<MeshFilter>(); mf.sharedMesh = mesh; } void LateUpdate() { var targetPosition = Camera.main.transform.TransformPoint(Vector3.forward * _range); var renderer = GetComponent<Renderer>(); renderer.material.SetFloat("_Range", _range); renderer.material.SetFloat("_RangeR", _rangeR); renderer.material.SetFloat("_Size", 0.1f); renderer.material.SetVector("_MoveTotal", _move); renderer.material.SetVector("_CamUp", Camera.main.transform.up); renderer.material.SetVector("_TargetPosition", targetPosition); float x = (Mathf.PerlinNoise(0f, Time.time * 0.1f) - 0.5f) * 10f; float y = -2f; float z = (Mathf.PerlinNoise(Time.time*0.1f, 0f)-0.5f) * 10f; _move += new Vector3(x, y, z) * Time.deltaTime; _move.x = Mathf.Repeat(_move.x, _range * 2.0f); _move.y = Mathf.Repeat(_move.y, _range * 2.0f); _move.z = Mathf.Repeat(_move.z, _range * 2.0f); } }
Shader
Shader "Unlit/fallSnow" { Properties { _MainTex("MainTex", 2D) = "white" {} } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } ZWrite Off Cull Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" uniform sampler2D _MainTex; struct appdata_custom { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; float4x4 _PrevInvMatrix; float3 _TargetPosition; float _Range; float _RangeR; float _Size; float3 _MoveTotal; float3 _CamUp; v2f vert(appdata_custom v) { float3 target = _TargetPosition; float3 trip; float3 mv = v.vertex.xyz; mv += _MoveTotal; trip = floor(((target - mv) * _RangeR + 1) * 0.5f); trip *= (_Range * 2); mv += trip; float3 diff = _CamUp * _Size; float3 finalPosition; float3 tv0 = mv; tv0.x += sin(mv.x*0.2) * sin(mv.y*0.3) * sin(mv.x*0.9) * sin(mv.y*0.8); tv0.z += sin(mv.x*0.1) * sin(mv.y*0.2) * sin(mv.x*0.8) * sin(mv.y*1.2); float3 eyeVector = ObjSpaceViewDir(float4(tv0, 0)); float3 sideVector = normalize(cross(eyeVector, diff)); tv0 += (v.texcoord.x - 0.5f) * sideVector * _Size; tv0 += (v.texcoord.y - 0.5f) * diff; finalPosition = tv0; v2f o; o.pos = UnityObjectToClipPos(finalPosition); o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord); return o; } fixed4 frag(v2f i) :SV_TARGET { return tex2D(_MainTex, i.uv); } ENDCG } } }
結構量が多いので、少しずつ理解していきます。
まずはScriptの解説からしていきます。
SNOW_NUM
private const int SNOW_NUM = 16000;
これが16,000なのには理由があります。
Unityでは1回のドローコールで64,000の頂点と64,000のindexを扱うことができます。
※ OpenGLESでは48,000のindex、macOSでは32,000のindex
そして、今回の降らせる雪は1つあたり4頂点なので、64,000に4を除算して16,000となります。
ドローコール
噛み砕くと、描画フローが呼ばれた回数のことです。
描画では、
のフローを行っています。
雪の位置
_range = 16.0f; _rangeR = 1.0f / _range; _vertices = new Vector3[SNOW_NUM * 4]; for (int i = 0; i < SNOW_NUM; i++) { float x = Random.Range(-_range, _range); float y = Random.Range(-_range, _range); float z = Random.Range(-_range, _range); var point = new Vector3(x, y, z); _vertices[i * 4 + 0] = point; _vertices[i * 4 + 1] = point; _vertices[i * 4 + 2] = point; _vertices[i * 4 + 3] = point; }
この部分に関して説明していきます。
変数の説明
まずはここで使用している変数の意味です。
// 雪を降らせる範囲 _range = 16.0f; // _rangeの逆数 _rangeR = 1.0f / _range; // 雪の頂点 _vertices = new Vector3[SNOW_NUM * 4];
_range
赤枠で囲っているのが雪が降っている範囲です。
_rangeが5.0f
_rangeが50.0f
逆数
逆数とは、その値に対して乗算をすると1になる数のことです。
3の逆数はですし、16の逆数はです。
乗算と除算では、圧倒的に乗算の方が速いので逆数にしています。
Random.Range
指定された範囲の乱数を返すメソッドです。
intとfloatで返す範囲が異なるので気をつけてください。
// 1.0以上 ~ 10.0以下の乱数 Random.Range(1.0f, 10.0f); // 1以上 ~ 10未満の乱数 Random.Range(1, 10);
_vertices
var point = new Vector3(x, y, z); _vertices[i * 4 + 0] = point; _vertices[i * 4 + 1] = point; _vertices[i * 4 + 2] = point; _vertices[i * 4 + 3] = point;
これは頂点のことです。
下記の画像の0~3が各頂点で、この頂点を結ぶことにより今回の雪を描画することができます。
一般的には、各頂点をズラすのですが今回はそれをshader側で行っているので、同じVector3の値を入れています。
各頂点を結ぶ
_triangles = new int[SNOW_NUM * 6]; for (int i = 0; i < SNOW_NUM; i++) { _triangles[i * 6 + 0] = i * 4 + 0; _triangles[i * 6 + 1] = i * 4 + 1; _triangles[i * 6 + 2] = i * 4 + 2; _triangles[i * 6 + 3] = i * 4 + 2; _triangles[i * 6 + 4] = i * 4 + 1; _triangles[i * 6 + 5] = i * 4 + 3; }
頂点の位置を定義したら、次にその頂点をどのような順番で結ぶかを決めます。
四角形は、2つの三角形から描画されています。
下記のソースコードの一番最後に足している数字が、頂点の番号です。
// 1個目の三角形 _triangles[i * 6 + 0] = i * 4 + 0; _triangles[i * 6 + 1] = i * 4 + 1; _triangles[i * 6 + 2] = i * 4 + 2; // 2個目の三角形 _triangles[i * 6 + 3] = i * 4 + 2; _triangles[i * 6 + 4] = i * 4 + 1; _triangles[i * 6 + 5] = i * 4 + 3;
注意してほしいことは、各頂点の順番は時計回りにしてください。
これを半時計周りにすると逆向きに描画されてしまいます。
UV値を決める
_uvs = new Vector2[SNOW_NUM * 4]; for (int i = 0; i < SNOW_NUM; i++) { _uvs[i * 4 + 0] = new Vector2(0.0f, 0.0f); _uvs[i * 4 + 1] = new Vector2(1.0f, 0.0f); _uvs[i * 4 + 2] = new Vector2(0.0f, 1.0f); _uvs[i * 4 + 3] = new Vector2(1.0f, 1.0f); }
雪のTextureを貼るので、必要となってきます。
Meshに登録する
Mesh mesh = new Mesh(); mesh.name = "MeshSnowFlakes"; mesh.vertices = _vertices; mesh.triangles = _triangles; mesh.uv = _uvs; mesh.bounds = new Bounds(Vector3.zero, Vector3.one * 99999999); var mf = GetComponent<MeshFilter>(); mf.sharedMesh = mesh;
頂点の座標と順番、UV値を決めたのでそれを登録します。
name
Meshに名前をつけることができます。
InspecterのMeshFilterに表示されます。
vertices, triangles, uv
先ほど制作した、頂点(vertices)、朝tんの順番(triangles)、UV値(uv)を登録しています。
bounds
これはMeshの大きさを表しています。
定義は以下のようになっています。
// 中央の位置、大きさ
Bounds(Vector3 center, Vector3 size)
sizeに大きな値を乗算しているのは、視錐台カリングを無効にするためです。
sharedMesh
これは共有のMeshです。
今回の場合は、MeshFilterに制作したMeshを渡しているだけです。
ソースコードにコメントを付与
using UnityEngine; [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class Snow : MonoBehaviour { // ドローコールの限界/4 private const int SNOW_NUM = 16000; private Vector3[] _vertices; private int[] _triangles; private Vector2[] _uvs; private float _range; private float _rangeR; private Vector3 _move; void Start() { // 雪を降らせる範囲 _range = 16.0f; // _rangeの逆数 _rangeR = 1.0f / _range; // 雪の頂点 _vertices = new Vector3[SNOW_NUM * 4]; // 頂点の位置 for (int i = 0; i < SNOW_NUM; i++) { float x = Random.Range(-_range, _range); float y = Random.Range(-_range, _range); float z = Random.Range(-_range, _range); var point = new Vector3(x, y, z); _vertices[i * 4 + 0] = point; _vertices[i * 4 + 1] = point; _vertices[i * 4 + 2] = point; _vertices[i * 4 + 3] = point; } // 頂点を結ぶ順番 _triangles = new int[SNOW_NUM * 6]; for (int i = 0; i < SNOW_NUM; i++) { _triangles[i * 6 + 0] = i * 4 + 0; _triangles[i * 6 + 1] = i * 4 + 1; _triangles[i * 6 + 2] = i * 4 + 2; // ここで分割 _triangles[i * 6 + 3] = i * 4 + 2; _triangles[i * 6 + 4] = i * 4 + 1; _triangles[i * 6 + 5] = i * 4 + 3; } // UV値 _uvs = new Vector2[SNOW_NUM * 4]; for (int i = 0; i < SNOW_NUM; i++) { _uvs[i * 4 + 0] = new Vector2(0.0f, 0.0f); _uvs[i * 4 + 1] = new Vector2(1.0f, 0.0f); _uvs[i * 4 + 2] = new Vector2(0.0f, 1.0f); _uvs[i * 4 + 3] = new Vector2(1.0f, 1.0f); } // Meshの生成 Mesh mesh = new Mesh(); mesh.name = "MeshSnowFlakes"; mesh.vertices = _vertices; mesh.triangles = _triangles; mesh.uv = _uvs; // 視錐台カリングを無効化 mesh.bounds = new Bounds(Vector3.zero, Vector3.one * 99999999); var mf = GetComponent<MeshFilter>(); mf.sharedMesh = mesh; }
今回はここまでとなります。
ここまでご視聴ありがとうございました!