【Unity3D】花びらエフェクト

1 花びら描画の原理

        下の図は、実現された花びらの特殊効果を示しています。説明の便宜上、赤い平らな帯を花びらと呼び、各花びらの中心にある緑色の点を雄しべと呼び、花の中心を花中心と呼びます。

        花の中心を点O、世界座標を_Center、花びらの数を_PetalNum、花びらの半分の長さと半分の幅をそれぞれ_PetalLength、_PetalWidth、背景の色として、xOz平面上に花を描画します。花の中心、雄しべ、花びらそれらは、_BackgoundColor、_HeartColor、_StamenColor、_PetalColor です; 平面上の任意の点 P について、その世界座標は worldPos であり、その最終的な色は color です。色の計算プロセスは、以下で段階的に解決されます。 。

        この記事の完全なリソースについては、→ Unity3D 花びらエフェクトを参照してください。

1.1 花びら座標軸上の投影座標の計算処理

        頂点の色付けを容易にするために、頂点 P に最も近い雄しべの座標を知り、その雄しべを点 S として記録し、その座標を雄しべとしてマークしてから、花弁の座標軸上の SP の投影座標を計算する必要があります (つまり、図中のSPはSM内、SN方向に投影)。

        1) OSの回転角度を計算する

        Unity ワールド座標系は左手座標系であるため、正の回転方向の定義は左手の法則 (詳細は →空間と変換 を参照) に従います。つまり、xOz 平面上の正の回転方向です。は時計回りです。計算を簡素化するために、0 度の回転方向を Z 軸の正の方向として定義します。したがって、ベクトルOSの回転角度は次のように計算される。

float3 vertVec = worldPos - _Center; // 向量OP
float proj = dot(normalize(vertVec), float3(0, 0, 1)); // OP在OZ上的投影
float angle = acos(proj); // 向量OP的角度
if (vertVec.x < 0) { // OP的旋转角度大于180°
	angle = UNITY_TWO_PI - angle;
}
float delta = UNITY_TWO_PI / _PetalNum; // 每个花瓣的角度
float nearAngle = round(angle / delta) * delta; // 向量OS的角度

        2) 点 S の座標を計算します。

float3 vec = float3(sin(nearAngle), 0, cos(nearAngle)); // 向量OS的单位方向向量
float3 stamen = _Cenetr + vec * _PetalLength; // S点坐标(离P点最近的花蕊坐标)

        3) 花弁座標軸上の SP の投影座標を計算します。 

float vec1 = worldPos - stamen; // 向量SP
float vec2 = normalize(stamen - _Center); // 向量SM的单位方向向量
float x = abs(dot(vec1, vec2)); // SP在SM轴上的投影
float y = sqrt(dot(vec1, vec1) - x * x); // SP在SN轴上的投影
float2 proj = float2(x, y); // SP在花瓣坐标轴上的投影坐标

        花びらは対称であるため、計算の便宜上、投影の絶対値のみを取得します。 

1.2 頂点の色付け処理

        花びら、雌しべ、花の中心のエッジを滑らかに着色するために、smoothstep関数を使用します(詳細は→ シェーダ定数、変数、構造体、および関数を参照)。

        1) 花びらの着色

float rate1 = smoothstep(_PetalLength, 0, proj.x) * smoothstep(_PetalWidth, 0, proj.y); // 顶点属于花瓣的比例
fixed4 color1 = lerp(_BackgroundColor, _PetalColor, rate1); // 混合花瓣颜色

        2) 雄しべの着色

float stamenWidth = _PetalWidth * 0.25; // 花蕊宽度(花蕊占花瓣宽度的比例可以调整)
float len2 = length(worldPos - stamen); // 向量SP的模长(顶点离花蕊的长度)
float rate2 = smoothstep(stamenWidth, 0, len2); // 顶点属于花蕊的比例
fixed4 color2 = lerp(color1, _StamenColor, rate2); // 混合花蕊颜色

        3) 花芯の着色

float heartWidth = _PetalLength * 0.4; // 花心宽度(花心占花瓣长度的比例可以调整)
float len3 = length(worldPos - _Center); // 向量OP的模长(顶点离花心的长度)
float rate3 = smoothstep(heartWidth, 0, len3); // 顶点属于花蕊的比例
fixed4 color = lerp(color2, _HeartColor, rate3); // 混合花心颜色

2 咲く花びらのエフェクト

2.1 花びらが咲く原理

        次のように、時間の経過とともに周期的に変化するように花びらの長さと幅を調整して、花びらのブルーミング効果を実現します。

float time = _Time.y * _Speed;
_PetalWidth = fmod(time * 0.2, 0.9) + 0.1;
_PetalLength = fmod(time * 1.1111, 5) + 1;

        このうち_Speedは花びらの長さと幅の変化速度で、外部から調整することができます。

2.2 花びらの開花の実現

        FlowerEffect.shader

Shader "MyShader/FlowerEffect"  { // 绽放花瓣特效
    Properties{
        _BackgroundColor("BackgroundColor", Color) = (1, 1, 1, 1) // 地面颜色
        _PetalColor("PetalColor", Color) = (1, 0, 0, 1) // 花瓣颜色
        _StamenColor("StamenColor", Color) = (0, 1, 0, 1) // 花蕊颜色
        _HeartColor("_HeartColor", Color) = (1, 1, 0, 1) // 花心颜色
        _Center("Center", Vector) = (0, 0, 0, 0) // 花心坐标
        _PetalNum("PetalNum", Range(3, 30)) = 15 // 花瓣个数
        _PetalWidth("PetalWidth", Range(0.1, 1)) = 0.5 // 花瓣宽度
        _PetalLength("PetalLength", Range(1, 20)) = 2 // 花瓣长度
        _Speed("Speed", Range(0.2, 5)) = 1 // 花瓣宽度、长度的变化速度
    }

    SubShader{
        Pass {
            CGPROGRAM

            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag

            fixed4 _BackgroundColor; // 背景颜色
            fixed4 _PetalColor; // 花瓣颜色
            fixed4 _StamenColor; // 花蕊颜色
            fixed4 _HeartColor; // 花心颜色
            float4 _Center; // 花蕊中心
            int _PetalNum; // 花瓣个数
            float _PetalWidth; // 花瓣宽度
            float _PetalLength; // 花瓣长度
            float _Speed; // 花瓣宽度、长度的变化速度

            struct a2v {
                float4 vertex : POSITION; // 模型空间顶点坐标
            };

            struct v2f {
                float4 pos : SV_POSITION; // 裁剪空间顶点坐标
                float3 worldPos : TEXCOORD0; // 纹理uv坐标
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 世界空间顶点坐标
                return o;
            }

            void updateParams() { // 更新花瓣宽度、长度信息
                float time = _Time.y * _Speed;
                _PetalWidth = fmod(time * 0.2, 0.9) + 0.1;
                _PetalLength = fmod(time * 1.1111, 5) + 1;
            }

            float getNearAngle(float3 vec) { // 获取与vec向量方向最近的花瓣的角度
                float proj = dot(normalize(vec), float3(0, 0, 1));
                float angle = acos(proj);
                if (vec.x < 0) { // OP的旋转角度大于180°
                    angle = UNITY_TWO_PI - angle;
                }
                float delta = UNITY_TWO_PI / _PetalNum;
                return round(angle / delta) * delta;
            }

            float3 getStamen(float angle) { // 获取离顶点最近的花蕊位置
                float3 vec = float3(sin(angle), 0, cos(angle));
                return _Center.xyz + vec * _PetalLength;
            }

            float2 getProjXY(float3 vec1, float3 vec2) { // 获取vec1在vec2下的的投影坐标
                float x = abs(dot(vec1, normalize(vec2)));
                float y = sqrt(dot(vec1, vec1) - x * x);
                return float2(x, y);
            }

            fixed4 mixPetalColor(float2 pos) { // 混合花瓣颜色
                float rate = smoothstep(_PetalLength, 0, pos.x) * smoothstep(_PetalWidth, 0, pos.y); // 顶点属于花瓣的比例
                //float rate = smoothstep(sqrt(_PetalLength * _PetalLength + _PetalWidth * _PetalWidth), 0, length(pos));
                return lerp(_BackgroundColor, _PetalColor, rate);
            }

            fixed4 mixStamenColor(fixed4 color, float2 pos) { // 混合花蕊颜色
                float stamenWidth = _PetalWidth * 0.25; // 花蕊宽度
                float len = length(pos); // 顶点离花蕊的长度
                float rate = smoothstep(stamenWidth, 0, len); // 顶点属于花蕊的比例
                return lerp(color, _StamenColor, rate);
            }

            fixed4 mixHeartColor(fixed4 color, float3 pos) { // 混合花心颜色
                float heartWidth = _PetalLength * 0.4; // 花心宽度
                float len = length(pos); // 顶点离花心的长度
                float rate = smoothstep(heartWidth, 0, len); // 顶点属于花蕊的比例
                return lerp(color, _HeartColor, rate);
            }

            fixed4 frag(v2f i) : SV_Target {
                updateParams(); // 更新花瓣宽度、长度信息
                float3 vertVec = i.worldPos - _Center.xyz;
                float nearAngle = getNearAngle(vertVec); // 获取与顶点最近的花瓣角度
                float3 stamen = getStamen(nearAngle); // 获取离顶点最近的花蕊位置
                float2 pos = getProjXY(i.worldPos - stamen, stamen - _Center.xyz); // 顶点在花蕊坐标系下的投影坐标
                fixed4 color = mixPetalColor(pos); // 混合花瓣颜色
                color = mixStamenColor(color, pos); // 混合花蕊颜色
                color = mixHeartColor(color, vertVec); // 混合花心颜色
                return color;
            }

            ENDCG
        }
    }
}

        ランニング効果は以下の通りです。

3つの起動花びらエフェクト

        このセクションでは、花びらが周囲に定期的に特殊効果を放射し、放射中に継続的に回転します。

3.1 花びらの発光原理

        1) 回転原理

        セクション 1 に基づいて、OS の回転角度の計算を変更する必要があります。最初の花びらの回転角度は 0 ではなくなり、0 ~ (2π / _PetalNum) の間で周期的に変化します。具体的な計算は次のとおりです。が続きます。

float3 vertVec = worldPos - _Center; // 向量OP
float proj = dot(normalize(vertVec), float3(0, 0, 1)); // OP在OZ上的投影
float angle = acos(proj); // 向量OP的角度
if (vertVec.x < 0) { // OP的旋转角度大于180°
	angle = UNITY_TWO_PI - angle;
}
float delta = UNITY_TWO_PI / _PetalNum; // 每个花瓣的角度
float initAngle = fmod(_Time.y * _RotateSpeed, delta); // 第1个花瓣的旋转角度
float k = round((angle - initAngle) / delta); // 旋转的花瓣的个数
float nearAngle = k * delta + initAngle; // 向量OS的角度

        2) 発射原理

        セクション 1 に基づいて、S ポイントの計算を修正する必要があります. OS の長さはもはや _PetalLength ではなく、時間の経過とともに徐々に増加します. 具体的な計算は次のとおりです。

float time = fmod(_Time.y, _CastTime); // 当前发射周期中, 花瓣移动的时间
float dist = _MoveSpeed * time; // 当前发射周期中, 花瓣移动的距离
float len = length(worldPos - _Center); // 当前顶点距离花心的距离
if (len >= dist) { // 顶点在最外一环的花蕊外面
	return dist;
}
float petalLength = _PetalLength * 2; // 花瓣长度
float k = round((dist - len) / petalLength); // 顶点与最外一环的花蕊相隔的花瓣环数
float norm = dist - k * petalLength; // OS模长(与顶点最近一环的花蕊到花心的距离)

        点 S の座標は次のように計算されます。

float3 vec = float3(sin(nearAngle), 0, cos(nearAngle)); // 向量OS的单位方向向量
float3 stamen = _Center + vec * norm; // S点坐标(离P点最近的花蕊坐标)

3.3 花びら発光の実装

        FlowerEffect.shader

Shader "MyShader/FlowerEffect"  { // 发射花瓣特效
    Properties{
        _BackgroundColor("BackgroundColor", Color) = (1, 1, 1, 1) // 地面颜色
        _PetalColor("PetalColor", Color) = (1, 0, 0, 1) // 花瓣颜色
        _StamenColor("StamenColor", Color) = (0, 1, 0, 1) // 花蕊颜色
        _HeartColor("_HeartColor", Color) = (1, 1, 0, 1) // 花心颜色
        _Center("Center", Vector) = (0, 0, 0, 0) // 花心坐标
        _PetalNum("PetalNum", Range(3, 30)) = 15 // 花瓣个数
        _PetalWidth("PetalWidth", Range(0.1, 1)) = 0.5 // 花瓣宽度
        _PetalLength("PetalLength", Range(1, 20)) = 2 // 花瓣长度
        _RotateSpeed("RotateSpeed", Range(0.2, 5)) = 1 // 花瓣旋转速度
        _MoveSpeed("MoveSpeed", Range(0.2, 5)) = 1 // 花瓣移动速度
        _CastTime("_CastTime", Range(1, 10)) = 5 // 花瓣发射周期
    }

    SubShader{
        Pass {
            CGPROGRAM

            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag

            fixed4 _BackgroundColor; // 背景颜色
            fixed4 _PetalColor; // 花瓣颜色
            fixed4 _StamenColor; // 花蕊颜色
            fixed4 _HeartColor; // 花心颜色
            float4 _Center; // 花蕊中心
            int _PetalNum; // 花瓣个数
            float _PetalWidth; // 花瓣宽度
            float _PetalLength; // 花瓣长度
            float _RotateSpeed; // 花瓣旋转速度
            float _MoveSpeed; // 花瓣移动速度
            float _CastTime; // 花瓣发射周期

            struct a2v {
                float4 vertex : POSITION; // 模型空间顶点坐标
            };

            struct v2f {
                float4 pos : SV_POSITION; // 裁剪空间顶点坐标
                float3 worldPos : TEXCOORD0; // 纹理uv坐标
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 世界空间顶点坐标
                return o;
            }

            float getLen(float3 vertVec) { // 获取顶点最近的花蕊到花心的距离
                float time = fmod(_Time.y, _CastTime); // 当前发射周期中, 花瓣移动的时间
                float dist = _MoveSpeed * time; // 当前发射周期中, 花瓣移动的距离
                float len = length(vertVec); // 当前顶点距离花心的距离
                if (len >= dist) {
                    return dist;
                }
                float petalLength = _PetalLength * 2;
                return dist - round((dist - len) / petalLength) * petalLength;
            }

            float getNearAngle(float3 vec) { // 获取与vec向量方向最近的花瓣的角度
                float proj = dot(normalize(vec), float3(0, 0, 1));
                float angle = acos(proj);
                if (vec.x < 0) { // OP的旋转角度大于180°
                    angle = UNITY_TWO_PI - angle;
                }
                float delta = UNITY_TWO_PI / _PetalNum;
                float initAngle = fmod(_Time.y * _RotateSpeed, delta);
                float k = round((angle - initAngle) / delta);
                return k * delta + initAngle;
            }

            float3 getStamen(float angle, float len) { // 获取离顶点最近的花蕊位置
                float3 vec = float3(sin(angle), 0, cos(angle));
                return _Center.xyz + vec * len;
            }

            float2 getProjXY(float3 vec1, float3 vec2) { // 获取vec1在vec2下的的投影坐标
                float x = abs(dot(vec1, normalize(vec2)));
                float y = sqrt(dot(vec1, vec1) - x * x);
                return float2(x, y);
            }

            fixed4 mixPetalColor(float2 pos) { // 混合花瓣颜色
                float rate = smoothstep(_PetalLength, 0, pos.x) * smoothstep(_PetalWidth, 0, pos.y); // 顶点属于花瓣的比例
                //float rate = smoothstep(sqrt(_PetalLength * _PetalLength + _PetalWidth * _PetalWidth), 0, length(pos));
                return lerp(_BackgroundColor, _PetalColor, rate);
            }

            fixed4 mixStamenColor(fixed4 color, float2 pos) { // 混合花蕊颜色
                float stamenWidth = _PetalWidth * 0.25; // 花蕊宽度
                float len = length(pos); // 顶点离花蕊的长度
                float rate = smoothstep(stamenWidth, 0, len); // 顶点属于花蕊的比例
                return lerp(color, _StamenColor, rate);
            }

            fixed4 mixHeartColor(fixed4 color, float3 pos) { // 混合花心颜色
                float heartWidth = _PetalLength * 0.4; // 花心宽度
                float len = length(pos); // 顶点离花心的长度
                float rate = smoothstep(heartWidth, 0, len); // 顶点属于花蕊的比例
                return lerp(color, _HeartColor, rate);
            }

            fixed4 frag(v2f i) : SV_Target {
                float3 vertVec = i.worldPos - _Center.xyz;
                float len = getLen(vertVec); // 获取顶点距离最近的内环花蕊的距离
                float nearAngle = getNearAngle(vertVec); // 获取与顶点最近的花瓣角度
                float3 stamen = getStamen(nearAngle, len); // 获取离顶点最近的花蕊位置
                float2 pos = getProjXY(i.worldPos - stamen, stamen - _Center.xyz); // 顶点在花蕊坐标系下的投影坐标
                fixed4 color = mixPetalColor(pos); // 混合花瓣颜色
                color = mixStamenColor(color, pos); // 混合花蕊颜色
                color = mixHeartColor(color, vertVec); // 混合花心颜色
                return color;
            }

            ENDCG
        }
    }
}

        実行結果:

おすすめ

転載: blog.csdn.net/m0_37602827/article/details/132116197