【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 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】花瓣特效的更多相关文章
- Unity3d粒子特效:制作火焰效果
效果 分析 真实的火焰效果,通常包括:火.火光.火星等组成部分,而火焰对周围环境的烘焙,可以通过灯光实现,如点光源. 针对火焰组成部分,我们可以创建对应的粒子系统组件,实现相应的效果,如下图所示: 1 ...
- html 背景花瓣特效--1
html背景樱花可以用js添加,将<script>标签复制到<body>标签下就可以,javascript脚本点击 <!DOCTYPE html> <html ...
- 【Unity3D】基于粒子系统实现烟花特效
1 需求实现 粒子系统ParticleSystem 中介绍了粒子初始化.粒子发射.发射器形状.渲染器.碰撞.子发射器.拖尾等粒子系统的基本用法,本节将基于粒子系统实现烟花特效. 实现需求如下( ...
- Unity塔防游戏开发
Unity3D塔防开发流程 配置环境及场景搭建编程语言:C#,略懂些许设计模式,如果不了解设计模式,BUG More开发工具:Unity3D编辑器.Visual Studio编译器开发建议:了解Uni ...
- Unity3D特效-场景淡入淡出
最近公司开始搞Unity3D..整个游戏..特效需求还是比较多的.关于UI部分的特效淡入淡出.看网上用的方法都是用个黑东东遮挡然后设置alpha这么搞....本大神感觉非常的low.而且很渣.故奋笔疾 ...
- unity3d 游戏插件 溶解特效插件 - Dissolve Shader
unity3d 游戏插件 溶解特效插件 - Dissolve Shader 链接: https://pan.baidu.com/s/1hr7w39U 密码: 3ed2
- Unity3D之挥动武器产生的剑痕特效
网维教程网 观看很多其它教程 眼下已知3种方法能够做这样的剑痕特效 1.尾随特效 2.程序实现动态面来处理剑痕动画. 3.美术实现剑痕动画,直接坐在模型动画里面 (由于我不会美术所以这个忽略 嘿嘿) ...
- [Unity3D]Unity3D游戏开发之刀光剑影特效的实现
大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 我实在不明确有的人为什么不喜欢武侠/仙侠类游戏,也许是因为武侠/仙侠类游戏身上被永远烙上的国 ...
- Unity3D学习笔记——组件之Effects(效果/特效)——Particle System(粒子系统)
Effects:效果/特效. Particle System:粒子系统.可用于创建烟雾.气流.火焰.涟漪等效果. 在Unity3D 3.5版本之后退出了新的shuriken粒子系统: 添加组件之后 ...
- unity3d笔记:控制特效的播放速度
一般在游戏中,主角或者怪物会受到减速效果,或者攻击速度减慢等类似的状态.本身动作减速的同时,衔接在角色上的特效也需要改变相应的播放速度.一般特效有三个游戏组件: 关键点就是改变Ani ...
随机推荐
- [转帖]能使 Oracle 索引失效的六大限制条件
Oracle 索引的目标是避免全表扫描,提高查询效率,但有些时候却适得其反. 例如一张表中有上百万条数据,对某个字段加了索引,但是查询时性能并没有什么提高,这可能是 oracle 索引失效造成的.or ...
- [转帖]mysql8 ALGORITHM=INSTANT 亿级数据秒速增加字段
一.概述 登录后复制 在线DDL之快速增加列(秒级别的),并不会造成业务抖动.该功能自 MySQL 8.0.12 版本引入,是由腾讯游戏DBA团队贡献,此功能只适用于 InnoDB 表.实际上MySQ ...
- [转帖]看看 Jmeter 是如何玩转 redis 数据库的
柠檬小欧 2021-08-31 20:06420 Jmeter 作为当前非常受欢迎的接口测试和性能测试的工具,在企业中得到非常广泛的使用,而 Redis 作为缓存数据库,也在企业中得到普遍使用,那如何 ...
- [转帖]浅谈Armv8-A处理器
https://www.elecfans.com/emb/dsp/202208291886182.html 众所周知,ARM是一家设计并授权处理器和相应IP(比如互连总线,中断处理器,图像处理器等等) ...
- [转帖]SQL Server JDBC – Set sendStringParametersAsUnicode to false
https://vladmihalcea.com/sql-server-jdbc-sendstringparametersasunicode/ https://learn.microsoft.com/ ...
- canvas操作图片像素点保证你看的明明白白
开场白 今天遇到一个场景:就是更改一个图片的颜色: 当听到这个.我直呼好家伙:这个是要上天了呀. 但是仔细一思考:借助canvas好像也能实现: 于是下来研究了一下,并不难: 我们下面来看看怎么实现的 ...
- 【JS 逆向百例】拉勾网爬虫,traceparent、__lg_stoken__、X-S-HEADER 等参数分析
关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...
- 数据挖掘[一]---汽车车交易价格预测(测评指标;EDA)
题目出自阿里天池赛题链接:零基础入门数据挖掘 - 二手车交易价格预测-天池大赛-阿里云天池 相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据 ...
- 数据挖掘机器学习[二]---汽车交易价格预测详细版本{EDA-数据探索性分析}
题目出自阿里天池赛题链接:零基础入门数据挖掘 - 二手车交易价格预测-天池大赛-阿里云天池 相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据 ...
- 探索C语言中的Shellcode从提取到执行
ShellCode是一种独立于应用程序的机器代码,通常用于实现特定任务,如执行远程命令.注入恶意软件或利用系统漏洞.在网络安全领域,研究Shellcode是理解恶意软件和提高系统安全性的关键一环.本文 ...