Imaginantia

思ったことを書きます

FuwaParticle 解説

ずいぶん前に作った 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: 頭を使ってないので100
  • Cull Off: 特に意味はないけどなんとなく両面描画
  • Blend SrcAlpha One: アルファ値込みで加算合成
  • ZWrite Off: 所謂物体じゃないので深度はいらん

諸々定義

#pragma vertex vert
#pragma geometry geom
#pragma fragment frag

#include "UnityCG.cginc"

vertgeomfrag を使います宣言

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;
};

このシェーダは別のシェーダの改造なのでなぜか appdatav2g をわざわざ分けてますが、今回分ける意味はありません。vert みればわかる。

g2f の内容は以下。

  • vertex: はい
  • uv: 各パーティクルについて、中心を(0,0) とした小さな座標系 (appdata/v2guv とは無関係)
  • 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.);
}

vertappdata をただ 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つの三角形を区別できます。これはほぼ趣味です。

f:id:phi16_ind:20181003221943p:plain

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 の値はノリだと思うから考えてもしょうがないです
    • あとはそれでふわっと浮かびあらがらせる
    • 最初と最後は薄くする f:id:phi16_ind:20181003222757p:plain
  • 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空間ビルボードをつくろうとしたやつ。なんか適当に書いたらこうなりました。それっぽく動いています。 線を表現するために変なことになっているだけなので本来はもっと単純です・・・

わけのわからない図を貼っておきます f:id:phi16_ind:20181003223350p:plain

v=0 の場合は vdvn が直交することだけわかればいいはずです

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 すれば円になる。

あとはノリで色をつける。終わりです。

おつかれさまでした

お疲れ様でした。

大体は勘で書いてるのであまり理屈付けしてもしょうがないです。直感でエモをつくっていこう。