Posted at

# はじめに

BOOTHはここからどうぞ→Galaxy Shader - Voxel Gummi - BOOTH

ぶっちゃけShurikenとか使えば良さそうな気がしますがテッセレーションの勉強がてら座標操作の練習がしたかったんでゴニョゴニョした次第です。

まずはコード全文をペタリ

```// Copyright (c) 2019 @Feyris77
// Released under the MIT license
{
Properties
{
[IntRange]_Tessellation ("Particle Amount", Range(1, 32)) = 8
_Size("Particle Size", float) = 0.1
_Speed("Speed", float) = 1
}
{
Tags { "RenderType"="Opaque" "Queue" = "Transparent-1"}
LOD 100
Blend One One
ZWrite off

Pass
{
CGPROGRAM
#pragma target 5.0
#pragma vertex vert
#pragma hull Hull
#pragma domain Domain
#pragma geometry geom
#pragma fragment frag

#include "UnityCG.cginc"

struct v2h {
float4 pos : SV_POSITION;
float2 uv  : TEXCOORD0;
};

struct h2d
{
float4 pos : SV_POSITION;
float2 uv  : TEXCOORD0;
};

struct h2dc
{
float Edges[3] : SV_TessFactor;
float Inside   : SV_InsideTessFactor;
};

struct d2g
{
float4 pos : SV_POSITION;
float2 uv  : TEXCOORD0;
};

struct g2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 col : TEXCOORD1;
};

uniform float _Tessellation, _Size, _Speed;

o.uv = float2(u, v); \
o.pos = vp + float4(u*ar, v, 0, 0)*_Size; \
TriStream.Append(o);

float2 rot(float2 p, float r)
{
float c = cos(r);
float s = sin(r);
return mul(p, float2x2(c, -s, s, c));
}

float3 rand3D(float3 p)
{
p = float3( dot(p,float3(127.1, 311.7, 74.7)),
dot(p,float3(269.5, 183.3,246.1)),
dot(p,float3(113.5,271.9,124.6)));
return frac(sin(p)*43758.5453123);
}

v2h vert (appdata_base  v)
{
v2h o;
o.pos = v.vertex;
o.uv = v.texcoord;
return o;
}

h2dc HullConst(InputPatch<v2h, 3> i)
{
h2dc o;
float3 retf;
float  ritf, uitf;
ProcessTriTessFactorsAvg(_Tessellation.xxx, 1, retf, ritf, uitf );
o.Edges[0] = retf.x;
o.Edges[1] = retf.y;
o.Edges[2] = retf.z;
o.Inside = ritf;
return o;
}

[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("HullConst")]
h2d Hull(InputPatch<v2h, 3> IN, uint id : SV_OutputControlPointID)
{
h2d o;
o.pos = IN[id].pos;
o.uv  = IN[id].uv;
return o;
}

[domain("tri")]
d2g Domain(h2dc hs_const_data,  OutputPatch<h2d, 3> i, float3 bary: SV_DomainLocation)
{
d2g o;
o.pos = i[0].pos * bary.x + i[1].pos * bary.y + i[2].pos * bary.z;
o.uv  = i[0].uv  * bary.x + i[1].uv  * bary.y + i[2].uv  * bary.z;
return o;
}

[maxvertexcount(3)]
void geom(point d2g IN[1],inout TriangleStream<g2f> TriStream)
{
g2f o;
float3 pos = rand3D(IN[0].pos.xyz)*2-1;
float3 rand0 = rand3D(pos.yxz)*2-1;
float3 rand1 = rand3D(pos.zyx)*2-1;
float3 rand3 = rand3D(pos.xyz);
pos = rand0 *rand1;
float t = _Time.y*.05*_Speed;

o.col = float4((sin(abs(pos)*10) * 0.57 + 0.6)*.001, 1);

pos.z *= length(1-abs(pos.x))*.1;
pos.x *= length(1-abs(pos.z))*.4;

pos.xy = rot(pos.xy , t+(1-length(pos))*10);
pos.xy = rot(pos.xy , t*2*smoothstep(.75, 1, rand3.x)*abs(1-length(pos)));

float ar = - UNITY_MATRIX_P[0][0] / UNITY_MATRIX_P[1][1]; //Aspect Ratio
float4 vp = UnityObjectToClipPos(float4(pos, 1));
TriStream.RestartStrip();
}

float4 frag (g2f i) : SV_Target
{
return saturate(.5-length(i.uv)) * clamp(i.col / pow(length(i.uv), 2), 0, 2);
}
ENDCG
}
}
}
```

～110行目くらいまでは単純にテッセレーションでポリゴン分割しているだけなので特に説明はしません。

# パーティクルの座標計算

```float3 pos = rand3D(IN[0].pos.xyz)*2-1;
float3 rand0 = rand3D(pos.yxz)*2-1;
float3 rand1 = rand3D(pos.zyx)*2-1;
float3 rand3 = rand3D(pos.xyz);
pos = rand0 *rand1;
float t = _Time.y*.05*_Speed;

//o.col = float4((sin(abs(pos)*10) * 0.57 + 0.6)*.001, 1);

pos.z *= length(1-abs(pos.x))*.1;
pos.x *= length(1-abs(pos.z))*.4;

pos.xy = rot(pos.xy , t+(1-length(pos))*10);
pos.xy = rot(pos.xy , t*2*smoothstep(.75, 1, rand3.x)*abs(1-length(pos)));
```

ざっくりいうと

1. 入力されたモデル座標を中心寄りな分布にする。

2. X、Z軸方向に潰す。（このとき中心をﾓｯｺﾘさせる）

3. 中心から外側にかけてネジネジ&回転させる。

```float3 pos = rand3D(IN[0].pos.xyz)*2-1;
```

```float3 rand0 = rand3D(pos.yxz)*2-1;
float3 rand1 = rand3D(pos.zyx)*2-1;
pos = rand0 * rand1;
```

モデル座標のZ軸方向に潰します。このとき中心から離れるほど潰す強さを大きくするのがポイントです。

```pos.z *= length(1-abs(pos.x))*.1;  //Z軸方向に圧縮圧縮ッ！
pos.x *= length(1-abs(pos.z))*.4;  //X軸方向に圧縮圧縮ッ！
```

その後同様にX軸方向にも潰します

`t+(1-length(pos))*10`で中心からの距離に応じて回転をオフセットしています。

ちなみに、`*10`というのがねじり具合です。

`t*2*smoothstep(.75, 1, rand3.x)*abs(1-length(pos))`は一部の粒子を更に回転させてまばらにすることでいい感じの”味付け”を行っています。こちらでも中心からの距離に応じて回転速度を変えています。

ありふれた二次元回転関数

```float2 rot(float2 p, float r)
{
float c = cos(r);
float s = sin(r);
return mul(p, float2x2(c, -s, s, c));
}
```

```pos.xy = rot(pos.xy , t+(1-length(pos))*10);  //ネジネジ
pos.xy = rot(pos.xy , t*2*smoothstep(.75, 1, rand3.x)*abs(1-length(pos)));  //味付け
```

# パーティクルの表現方法について

パーティクルというと単に小さな粒子なのでその形状や構造について深く考える機会は少ないですがちょっとした工夫で格段に「粒子感」をアップさせることができます。

`1-step(.5, length(uv))`で円を書いて終わりだとなんだか味気ないです。

パーティクルの一つを拡大表示した様子

グローの幅はコアの大きさよりも大きくするのがポイントです。

```saturate(.5-length(i.uv)) * clamp(i.col / pow(length(i.uv), 2), 0, 2);
```

※このコードはUV座標の原点がコアの中心になることが前提です。

ちなみに`clamp`で0～2の範囲に収めているのはPostEffectをかけた際にちらつきが発生するためです。

# 参考資料

http://compojigoku.blog.fc2.com/blog-entry-28.html