美文网首页Unity3D
【Unity3D】花瓣特效

【Unity3D】花瓣特效

作者: LittleFatSheep | 来源:发表于2023-10-13 20:52 被阅读0次

    1 花瓣绘制原理

    如下图是实现的花瓣特效效果,为方便描述,我们将每个红色的扁状长条称为花瓣,每个花瓣中心的绿点称为花蕊,花朵的正中心称为花心。

    我们在 xOz 平面上绘制花朵,假设花心为 O 点,其世界坐标为 _Center, 花瓣个数为 _PetalNum,花瓣半长度和半宽度分别为 _PetalLength、_PetalWidth,背景、花心、花蕊、花瓣的颜色分别为 _BackgoundColor、_HeartColor、_StamenColor、_PetalColor;对于平面上任意一点 P, 其世界坐标为 worldPos,其最终着色的颜色为 color,下面将逐步求解 color 的计算过程。

    本文完整资源见→Unity3D花瓣特效

    1.1 花瓣坐标轴上投影坐标计算过程

    为方便顶点着色,我们需要知道与顶点 P 最近的花蕊坐标,将该花蕊记为 S 点,其坐标记为 stamen,然后再计算 SP 在该花瓣坐标轴上的投影坐标(即图中 SP 在 SM 和 SN 方向上的投影)。

    1)计算 OS 的旋转角度

    由于 Unity 世界坐标系是左手坐标系,旋转正方向的定义遵循左手法则(详见→空间和变换),即 xOz 平面上旋转正方向为顺时针方向。为简化计算,我们定义旋转零度方向为 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 函数(详见→Shader常量、变量、结构体、函数)。

    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 的旋转角度计算,第 1 个花瓣的旋转角度不再是 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
            }
        }
    }
    

    运行效果:

    声明:本文转自【Unity3D】花瓣特效

    相关文章

      网友评论

        本文标题:【Unity3D】花瓣特效

        本文链接:https://www.haomeiwen.com/subject/qfsabdtx.html