ずいぶん前に作った FuwaParticle ですが
いろいろ使っていただいている割には解説ゼロでそろそろ申し訳なくなってきたので解説を書きます
別に見た目用に使ってほしかったので勉強用に使われることは最初は本気では想定していなかったのでゆるして
どばっといきます
その他
Shader "Unlit/fuwa_particle" { Properties { _Height ("Height", Float) = 0.05 _Size ("Particle Size", Float) = 1.0 _Fuwa ("Fuwa Rate", Range(0,1)) = 0.3 _FuwaDuration ("Fuwa Duration", Float) = 1.0 _FuwaTarget ("Fuwa Target", Vector) = (0,1,0,0) _Speed ("Randomize Speed", Float) = 1.0 _ScatterFactor ("Scatter Factor", Float) = 8 _ScatterDistance ("Scatter Distance", Float) = 1 _Color ("Color", Color) = (0.3,0.6,1.0,1.0) }
パラメータ宣言
Height
: これ意味わかんないんですけど、コードみた感じは「揺蕩う点の移動量」(なんでYに絡んでるの?)Particle Size
: パーティクルサイズFuwa Rate
: 地面に揺蕩わずに浮き続ける割合Fuwa Duration
: 浮くパーティクルが周期でrespawnするまでの時間Fuwa Target
: 浮く方向Randomize Speed
: 時間の速さ (意味がわからんけどそうとしか)Scatter Factor
: 外側に散らばる割合的なもの (謎)Scatter Distance
: 散らばりの距離Color
: 色
SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } LOD 100 Cull Off Blend SrcAlpha One ZWrite Off Pass { CGPROGRAM // だらだら ENDCG } } }
加算合成してるだけ
RenderType
はあんまり意味ないらしいけどつけてる、Queue
は所謂物体を描画した後に描画してもらうため (壁に隠れてほしい)LOD
: 頭を使ってないので100Cull Off
: 特に意味はないけどなんとなく両面描画Blend SrcAlpha One
: アルファ値込みで加算合成ZWrite Off
: 所謂物体じゃないので深度はいらん
諸々定義
#pragma vertex vert #pragma geometry geom #pragma fragment frag #include "UnityCG.cginc"
vert
→ geom
→ frag
を使います宣言
UnityCG.cginc
は使ってないので消してもいいです (だって最初からあるんだもん)
struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2g { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct g2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float3 color : TEXCOORD1; float d : TEXCOORD2; };
このシェーダは別のシェーダの改造なのでなぜか appdata
と v2g
をわざわざ分けてますが、今回分ける意味はありません。vert
みればわかる。
g2f
の内容は以下。
vertex
: はいuv
: 各パーティクルについて、中心を(0,0)
とした小さな座標系 (appdata/v2g
のuv
とは無関係)color
: はいd
: 歴史の名残です
実はこいつ、線が描けるんですが (geom
内で v
に値を入れる) そのための機能が結構そのまま残っております
sampler2D _MainTex; float4 _Color; float _Height; float _Fuwa; float _FuwaDuration; float4 _FuwaTarget; float _Size; float _Speed; float _ScatterFactor; float _ScatterDistance; float rand(float2 co){ return frac(sin(dot(co.xy, float2(12.9898,78.233))) * 43758.5453); }
_MainTex
使ってないですね。っていうか無いですね。rand
はいつものね。
v2g vert (appdata v) { v2g o; o.vertex = v.vertex; o.uv = v.uv.xy; return o; } float stepping(float t){ if(t<0.)return -1.+pow(1.+t,2.); else return 1.-pow(1.-t,2.); }
vert
は appdata
をただ v2g
にして投げてるだけなので無駄です。
stepping
はなんかいい感じの関数ですが使ってません。
メイン部
[maxvertexcount(4)] void geom (triangle v2g IN[3], inout TriangleStream<g2f> stream) { float2 uv = (IN[0].uv + IN[1].uv + IN[2].uv) / 3; uv.x = (floor(uv.x * 256) + 0.5) / 256.0; uv.y = (floor(uv.y * 128) + 0.5) / 128.0; // だらだら }
オリジナルのpolygonは128x128のグリッドです。32768ポリゴン。
uv
は重心で、縦方向は素直にそれをとり、横方向は適当に2倍して取ることで2つの三角形を区別できます。これはほぼ趣味です。
g2f o; o.color = float3(1,1,1); float size = 1.0; float r0 = rand(uv+0), r1 = rand(uv+1), r2 = rand(uv+2), r3 = rand(uv+3), r4 = rand(uv+4); float3 p = 0, v = 0;
出力用変数を定義、なぜか color
に白をつっこむ。あと r0~r4
は適当につかえる乱数です。p
が位置、v
はもう一つの端点への移動量ですが使ってません。
float t = _Time.y * _Speed; float fth = r0*3.1415926535*2; float fu = r1*2-1; float3 fpos = float3(sqrt(1-fu*fu)*float2(cos(fth),sin(fth)),0).xzy + float3(0,fu,0); float3 xpos = fpos; float fr = t / 2.0 + r0 * 500; fpos.xy = mul(fpos.xy, float2x2(cos(fr),-sin(fr),sin(fr),cos(fr))); fr = t / 3.0 + r1 * 500; fpos.yz = mul(fpos.yz, float2x2(cos(fr),-sin(fr),sin(fr),cos(fr)));
適当に球上にちらばる値をつくっておいた。時間に依存して回転するのでふわふわします。
if(true) { float th = r0 * 3.1415926535 * 2.0 + r2; float r = sqrt(r1 + 0.0008) * (1 + exp(-r2*_ScatterFactor) * _ScatterDistance); p = float3(cos(th), 0, sin(th)) * r * 2.0; p += fpos * _Height; p.y += _Height; o.color = _Color; if(r3 < _Fuwa) { float m = (exp(-r4*10) + 1.0) * 3.0; float hv = fmod((100+_Time.y)*(r3*1.0+1.0)*0.5/_FuwaDuration, m); p += hv * 0.1 * _FuwaTarget.xyz; o.color *= pow(sin(hv/m*3.1415926535),0.5); } float thv = r3 * 3.1415926535 * 2.0; o.color *= 0.5; o.color *= max(0, min(1, 1 + r3)); size = 2.0 + sin(r2*3.1415926535*2.0 + _Time.y*0.3*(1+r2)); size *= _Size; }
パーティクルの配置部。だからここをいじればいろいろいじれます。
th/r
で円版上に点を生成するんですが、概ねuniformにしつつScatter
系変数である程度ちらばらせるようにしてあります- とりあえず点を円盤上におく
- さっきつくったふわふわ値
fpos
で揺らす - なんとなく上にずらす (は?)
- 色を
Color
にする - 一定確率で「浮かぶパーティクル」にする
- ランダムに決まる移動速度みたいなのが
m
で、位置がhv
です (hover のhv
ですね…m
は多分意味はない) hv
の値はノリだと思うから考えてもしょうがないです- あとはそれでふわっと浮かびあらがらせる
- 最初と最後は薄くする
- ランダムに決まる移動速度みたいなのが
thv
使ってなし- 適当に色調節して
- 適当にサイズ調節して
- 全体のサイズ掛けて終わり
float4 vp1 = UnityObjectToClipPos(float4(p, 1)); float4 vp2 = UnityObjectToClipPos(float4(p + v, 1)); float2 vd = vp1.xy / vp1.w - vp2.xy / vp2.w; float aspectRatio = - UNITY_MATRIX_P[0][0] / UNITY_MATRIX_P[1][1]; vd.x /= aspectRatio; o.d = length(vd); if(length(vd) < 0.0001) vd = float2(1,0); else vd = normalize(vd); float2 vn = vd.yx * float2(-1,1); size *= 2; if(abs(UNITY_MATRIX_P[0][2]) < 0.01) size *= 2; float sz = 0.002 * size; vd *= sz, vn *= sz; vd.x *= aspectRatio, vn.x *= aspectRatio;
Clipping空間ビルボードをつくろうとしたやつ。なんか適当に書いたらこうなりました。それっぽく動いています。 線を表現するために変なことになっているだけなので本来はもっと単純です・・・
わけのわからない図を貼っておきます
v=0
の場合は vd
と vn
が直交することだけわかればいいはずです
o.uv = float2(-1,-1); o.vertex = vp1+float4(vd+vn,0,0); stream.Append(o); o.uv = float2(-1,1); o.vertex = vp1+float4(vd-vn,0,0); stream.Append(o); o.uv = float2(1,-1); o.vertex = vp2+float4(-vd+vn,0,0); stream.Append(o); o.uv = float2(1,1); o.vertex = vp2+float4(-vd-vn,0,0); stream.Append(o); stream.RestartStrip();
適当に面を貼る
色塗り
fixed4 frag (g2f i) : SV_Target { float l = length(i.uv); clip(1-l); float3 color = i.color; color *= pow(max(0, 0.5 - i.d) + 1 - l, 0.5) * 2; color = min(1, color); color = pow(color, 2.2); return float4(color,smoothstep(1,0.8,l)*_Color.a); }
geom
で吐いた uv
が [-1,1]
なので、length
が中心からの距離。端っこで 1
になるので clip
すれば円になる。
あとはノリで色をつける。終わりです。
おつかれさまでした
お疲れ様でした。
大体は勘で書いてるのであまり理屈付けしてもしょうがないです。直感でエモをつくっていこう。