【Unity3D】花瓣特效

2023-10-15 09:30
文章标签 特效 unity3d 花瓣

本文主要是介绍【Unity3D】花瓣特效,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 fragfixed4 _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 fragfixed4 _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】花瓣特效的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/216831

相关文章

Unity3D Entity_CacheService实现详解

Unity3D是一款广泛使用的游戏开发引擎,它提供了丰富的功能和工具来帮助开发者创建高质量的游戏和互动体验。在Unity开发过程中,资源管理是一个重要的环节,特别是当项目规模逐渐增大,资源数量变多时。为了优化资源的加载和管理,Unity提供了CacheServer这一工具。本文将详细解析Unity3D中的Entity_CacheService(这里我们假设你指的是一个自定义的缓存服务,因为Unit

ImageFlow相片特效插件-参数设定说明

參數設定說明: aspectRatio: 1.964, /* ImageFlow的高度 */buttons: false, /* 上下張按鈕 */captions: true, /* 標題顯示 */imageCursor: 'default', /

《Unity3D高级编程之进阶主程》第一章 C#要点技术(三) 浮点数的精度问题

浮点数计算在我们大多数项目中并没有使用到特别的科学计算部分,所以float基本都够用。double也同样有精度问题,无论怎么样都是无法避免精度导致的在逻辑中的不一致的问题。 根据 IEEE 754 标准,任意一个二进制浮点数 F 均可表示为: F = (-1 ^ s) * (1.M) * (2 ^ e)   s:符号位, 0/1M:尾数部分,具体的小数用二进制表示e:比例因子

会声会影2024 视频编辑创作利器 #AI智能剪辑 #特效资源库 #共创共享 #视频创作爱好者

🌟【全新升级!会声会影2024,视频编辑的革新之作】🌟 嗨,CSDN的朋友们,今天要跟大家分享一款让我彻底震撼的视频编辑软件——会声会影2024最新版本🎬!作为一位热爱创作的内容创作者,我对视频编辑工具的选择一直非常挑剔。而当我接触到会声会影2024后,它的强大功能和便利性让我完全沉醉其中,忍不住要安利给你们了! 会声会影全版本绿色安装包获取链接:(抓紧保存以防失效) https:

unity3D粒子系统--Particle System

看到两篇还可以的文章,学习了: Unity3d-Particle System系统的学习(一) Unity3d-Particle System系统的学习(二)

Unity3d 游戏暂停(timeScale=0)引起的deltaTime关联的系列问题解决

问题描述 游戏暂停的功能是通过设置timeScale=0实现的,不过在暂停游戏的时候,需要对角色进行预览和设置,为了实现这个功能,是通过鼠标控制相机的操作,为了使相机的操作丝滑,获取鼠标操作系数乘以Time.deltaTime 了,同时对数值进行了平滑操作(Mathf.SmoothDamp和Mathf.SmoothDampAngle),采用了具体代码可以参考https://blog.csdn.n

电影美学复古胶片特效视频转场模板 | Premiere Pro 项目工程文件

这个Premiere Pro项目工程文件是一个电影美学胶片特效视频转场模板,每个过渡效果都散发出一种有机的怀旧魅力,让人回忆起经典电影卷轴和模拟摄影的独特美感。 项目特点: 胶片烧伤过渡效果:包括从微妙的闪烁到大胆的爆发,各种各样的效果。 兼容性:适用于Premiere Pro 2021及更高版本,不需要任何第三方插件。 多用途:既可用于照片,也可用于视频项目。 灵活性:适用于任何帧率(FPS

《Unity3D高级编程之进阶主程》第一章 C#要点技术(二) - Dictionary 底层源码剖析

Dictionary 底层结构         与Java中的HashMap结构类似。(Java工作者应该很熟悉)Dictionary底层数据结构是一个存放指针的数组。(数组 + 链表)         Dictionary 字典型数据结构,是以关键字Key 和 值Value 进行一一映射的。这种映射关系是用一个Hash函数来建立的。解决Hash冲突的方法同样是拉链法。

HTML星空特效

目录 写在前面 完整代码 代码分析 运行效果 系列文章 写在后面 写在前面 100行代码实现HTML星空特效。 完整代码 全部代码如下。 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head

Unity3D - 详解Quaternion类(二)

转自:http://www.cnblogs.com/tgycoder/p/5106463.html 四、Quaternion类静态方法 Quaternion中的静态方法有9个即:Angle方法、Dot方法、Euler方法、FromToRotation方法、Inverse方法、Lerp方法、LookRotation方法、RotateToWards方法和Slerp方法。关于静态的方法的使