Unity Shader - GrabPass 实现武器热扭曲拖尾效果
文章目录
- 先来看看效果
- 实现思路
- Unity带的TrailRender组件
- 编写脚本实现
- CSharp
- Shader
- 参数
- 注意性能
- 还可以优化
- 总结
- Project
以前龙之谷喜欢选战士,帅气。
战士的武器在甩动过程中会有扭曲拖尾。
自己测试项目中想给武器也添加这效果,所以顺便学习以下。
先来看看效果
还可以给拖尾添加着色、亮度、和一些扭曲强度的参数设置。
实现思路
- 按三个挂点的坐标来生成网格。
- 但生成网格是需要判断上次分段的位置,与现在移动位置差距是否到达某个值而生成一段网格。
- 生成分段是,设置好分段对应的颜色:
RGB
控制头尾颜色,A
控制扭曲强度与颜色alpha
。 - 在Shader中,先通过
GrabPass
获取ColorBuffer
的内容,然后在frag shader
中使用noiseTex
来偏移uv
,使用偏移后的UV来采样ColorBuffer
的内容即可到达扭曲效果。
Unity带的TrailRender组件
在此前,我用过unity的TrailRenderer
但是对我来说不好用,因为我需要将拖尾的头部可以与我的武器的位置吻合,如果用TrailRenderer挂到武器上,就算怎么调好起始对好的位置,运行后,你就会发现各种对不准,因为TrailRenderer是只一个坐标为对准拖尾头部的。
看看unity自带的制作效果
这效果不理想啊,所以啊,没办法下,就自己动手写个实时生成拖尾网格的脚本。
编写脚本实现
只要对齐三个坐标位置即可,原来只要两个坐标的,但是不好做拖尾边缘平滑过渡处理,所以调整为三个坐标。
生成网格,就是根据三个点,移动超过一定距离,就记录三个点的位置到一个段列表中。
段列表:每一段数据都记录点,与过去时间的信息。
// 拖尾段数据public class TrailSegment{public Vector3 pos1; // 三个坐标记录public Vector3 pos2;public Vector3 pos3;public float distortion1; // 三个坐标扭曲程度public float distortion2;public float distortion3;public float elapsedTime; // 保持不变时长,记录:已用时(秒)public float fadeTime; // 当elapsedTime >= duration 时,将开始记录fade time,就是淡出的时间已用时(秒)}
然后update中遍历位置段列表的每一项。
根据每一段的前后数据,生成网格。
再根据时间更新网格位置,颜色,等参数即可。
线框模式查看过程
放慢一些时间,方便观察
CSharp
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
#endif/// <summary>
/// authro : jave.lin
/// date : 2020.03.06
/// 拖尾脚本
/// 建议放在一个空的GO上去使用,并且这个GO不会再调整Transform,不然会影响到MeshFilter的transform,
/// 如果你要放到一个容器下面,就不许保证这个容器不会有transform变化
/// (递归的父级都不能有改变,所以最好放在一个TrailRoot的容器,就好了,不要修改它的Transform,然后所有的TrailScript附加到的GO,都放它下面)
/// </summary>
public class TrailScript : MonoBehaviour
{// 拖尾段数据public class TrailSegment{public Vector3 pos1; // 三个坐标记录public Vector3 pos2;public Vector3 pos3;public float distortion1; // 三个坐标扭曲程度public float distortion2;public float distortion3;public float elapsedTime; // 保持不变时长,记录:已用时(秒)public float fadeTime; // 当elapsedTime >= duration 时,将开始记录fade time,就是淡出的时间已用时(秒)}#if UNITY_EDITORpublic bool debug;[Space(10)]
#endifpublic Transform trans1; // 拖尾头的三个挂点public Transform trans2;public Transform trans3;public float duration = 1; // 拖尾段保持不变的时长(秒)public float fadeOut = 1; // 拖尾段保持不变时长到时后,开始淡出的时长(秒)public Color startColor; // 拖尾头部的颜色public Color endColor; // 拖尾尾部颜色public Material mat; // 拖尾的材质public bool emit; // 是否发射拖尾public float emitDistance = 0.1f; // 出发生成拖尾分段的最小距离private List<TrailSegment> segmentList; // 扭曲分段的列表private Vector3[] vertics; // 顶点的 world space positionprivate int[] indices; // 顶点的 索引//private Vector2[] uvs;private Color[] colors; // 顶点的颜色private MeshRenderer meshRender;private MeshFilter meshFilter;private Mesh mesh;private Vector3 lastPos1;private Vector3 lastPos2;private Vector3 lastPos3;private float lastTime;private void Start(){segmentList = new List<TrailSegment>();mesh = new Mesh();meshRender = gameObject.AddComponent<MeshRenderer>();meshFilter = gameObject.AddComponent<MeshFilter>();meshFilter.mesh = mesh;meshRender.material = mat;mesh.MarkDynamic(); // 文档说是底层会加速处理那些频繁更新网格信息时使用 https://docs.unity3d.com/ScriptReference/Mesh.MarkDynamic.htmllastPos1 = trans1.position;lastPos2 = trans2.position;lastPos3 = trans3.position;}private void Update(){if (emitDistance < 0) emitDistance = 0;if (emit){var deltaPos1 = trans1.position - lastPos1;var deltaPos2 = trans2.position - lastPos2;var deltaPos3 = trans3.position - lastPos3;if ((!IsZero(deltaPos1) && deltaPos1.magnitude > emitDistance) ||(!IsZero(deltaPos2) && deltaPos2.magnitude > emitDistance) ||(!IsZero(deltaPos3) && deltaPos3.magnitude > emitDistance)){float invEmitDistance = emitDistance == 0 ? 0 : 1f / emitDistance;segmentList.Add(new TrailSegment { pos1 = trans1.position, pos2 = trans2.position, pos3 = trans3.position,distortion1 = deltaPos1.magnitude * invEmitDistance,distortion2 = deltaPos2.magnitude * invEmitDistance,distortion3 = deltaPos3.magnitude * invEmitDistance,});lastPos1 = trans1.position;lastPos2 = trans2.position;lastPos3 = trans3.position;}}var count = segmentList.Count;var offset = 0;TrailSegment curSeg = null;TrailSegment nextSeg = null;if (segmentList.Count > 1){// 更新追后一个段的位置为:当前最新的拖尾头的位置var lastOne = segmentList[segmentList.Count - 1];lastOne.pos1 = trans1.position;lastOne.pos2 = trans2.position;lastOne.pos3 = trans3.position;}if (segmentList.Count > 0){vertics = new Vector3[count * 3]; // 顶点indices = new int[(count) * (4 * 3)]; // 索引//uvs = new Vector2[count * 3]; // uv,暂时不用colors = new Color[count * 3]; // 颜色var w2lMatrix = transform.worldToLocalMatrix; // 世界坐标转本地坐标的矩阵do{curSeg = segmentList[offset];var etT = duration == 0 ? 0 : Mathf.Clamp01(curSeg.elapsedTime / duration);var fadeT = fadeOut == 0 ? 0 : Mathf.Clamp01(curSeg.fadeTime / fadeOut);var reverseFadeT = 1 - fadeT;// 要靠近的左边var closeToPos = (offset < (segmentList.Count - 1) ? segmentList[offset + 1].pos2 : trans2.position);vertics[offset * 3 + 0] = w2lMatrix.MultiplyPoint(Vector3.Lerp(curSeg.pos1, closeToPos, fadeT));vertics[offset * 3 + 1] = w2lMatrix.MultiplyPoint(Vector3.Lerp(curSeg.pos2, closeToPos, fadeT));vertics[offset * 3 + 2] = w2lMatrix.MultiplyPoint(Vector3.Lerp(curSeg.pos3, closeToPos, fadeT));nextSeg = (offset + 1) < segmentList.Count ? segmentList[offset] : null;if (nextSeg != null){indices[offset * 4 * 3 + 0] = offset * 3 + 0;indices[offset * 4 * 3 + 1] = offset * 3 + 1;indices[offset * 4 * 3 + 2] = (offset + 1) * 3 + 1; // next segindices[offset * 4 * 3 + 3] = offset * 3 + 0;indices[offset * 4 * 3 + 4] = (offset + 1) * 3 + 1; // next segindices[offset * 4 * 3 + 5] = (offset + 1) * 3 + 0; // next segindices[offset * 4 * 3 + 6] = offset * 3 + 1;indices[offset * 4 * 3 + 7] = offset * 3 + 2;indices[offset * 4 * 3 + 8] = (offset + 1) * 3 + 2; // next segindices[offset * 4 * 3 + 9] = offset * 3 + 1;indices[offset * 4 * 3 + 10] = (offset + 1) * 3 + 2; // next segindices[offset * 4 * 3 + 11] = (offset + 1) * 3 + 1; // next seg}//uvs[offset * 3 + 0] = new Vector2(etT, 0.0f);//uvs[offset * 3 + 1] = new Vector2(etT, 0.5f);//uvs[offset * 3 + 2] = new Vector2(etT, 1.0f);// rgb作为过渡颜色,a作为控制扭曲强度Color c1 = startColor;Color c2 = endColor;Color c3 = Color.Lerp(c1, c2, etT);c3.a *= Mathf.Clamp01(curSeg.distortion1 * reverseFadeT);colors[offset * 3 + 0] = c3;c3.a *= Mathf.Clamp01(curSeg.distortion2 * reverseFadeT);colors[offset * 3 + 1] = c3;c3.a *= Mathf.Clamp01(curSeg.distortion3 * reverseFadeT);colors[offset * 3 + 2] = c3;} while (++offset < count);mesh.Clear();mesh.vertices = vertics;mesh.triangles = indices;//mesh.uv = uvs;mesh.colors = colors;int fadeOutCount = 0;offset = 0;while (offset < count){curSeg = segmentList[offset];var dt = Time.deltaTime;curSeg.elapsedTime += dt;if (curSeg.elapsedTime >= duration){curSeg.fadeTime = curSeg.elapsedTime - duration;if (curSeg.fadeTime >= fadeOut) fadeOutCount++;}offset++;}segmentList.RemoveRange(0, fadeOutCount);}}private bool IsZero(Vector3 v) => v.x == 0 && v.y == 0 && v.z == 0;#if UNITY_EDITORprivate void OnDrawGizmos(){if (debug == false) return;float r = 0.5f;Gizmos.color = Color.red;Gizmos.DrawSphere(trans1.position, r);Gizmos.color = Color.green;Gizmos.DrawSphere(trans2.position, r);Gizmos.color = Color.blue;Gizmos.DrawSphere(trans3.position, r);}
#endif
}
Shader
// jave.lin 2020.03.06
Shader "Custom/DistortionTrail" {Properties {[MaterialToggle(DISTORTION)] DISTORTION("Distortion", Float) = 1 // 是否开始深度的,浅水透视效果_NoiseTex ("NoiseTex", 2D) = "white" {} // 噪点图_Brightness ("Brightness", Range(0, 4)) = 2 // 拖尾亮度_DistortionIntensity ("DistortionIntensity", Range(0, 10)) = .5 // 扭曲强度_DistortionFrequency ("DistortionFrequency", Range(0, 100)) = 1 // 扭曲变化频率}SubShader {Tags { "RenderType"="Opaque" "Queue"="Transparent" } LOD 100GrabPass { "_wpTrailTexture" }Pass {ZWrite Off Cull Off Lighting Off Fog { Mode Off }//Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile _ DISTORTION#include "UnityCG.cginc"struct appdata {float4 vertex : POSITION;float2 uv : TEXCOORD0;fixed4 color : COLOR;};struct v2f {float4 vertex : SV_POSITION;float2 uv : TEXCOORD0;float4 grabPos : TEXCOORD1;fixed4 color : COLOR0;};sampler2D _wpTrailTexture;sampler2D _NoiseTex;float4 _NoiseTex_ST;fixed _Brightness;fixed _DistortionIntensity;fixed _DistortionFrequency;v2f vert (appdata v) {v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _NoiseTex);o.grabPos = ComputeGrabScreenPos(o.vertex);o.color = v.color;return o;}fixed4 frag (v2f i) : SV_Target {i.grabPos.xy /= i.grabPos.w;#if DISTORTIONfloat intensity = tex2D(_NoiseTex, i.grabPos.xy).r * sin(_Time.yw * _DistortionFrequency);i.grabPos.x += intensity * i.color.a * _DistortionIntensity;#endiffixed4 col = tex2D(_wpTrailTexture, i.grabPos.xy);col.rgb = lerp(col.rgb, col.rgb * i.color.rgb + col.rgb * _Brightness, i.color.a);col.a = i.color.a;return col;}ENDCG}}
}
参数
TrailScript的参数
Debug:是决定Scene视图下是否显示Trans1, Trans2, Trans3的position的Gizmo。
Trans1~3:是拖尾挂点的位置。
Duration:是拖尾分段的持续时长。
Fade Out:是拖尾分段消失时需要的时长。
Start Color:拖尾头部颜色,alpha可以控制颜色强弱。
End Color:拖尾尾部颜色,alpha可以控制颜色强弱。
Mat:拖尾网格渲染的材质(材质使用的shader就是上面分享的shader)。
Emit:是否持续发射分段。
Emit Distance:持续发射分段的最小移动距离。
DistortionTrail材质参数Distortion:是否开启扭曲。
NoiseTex:对ColorBuffer扭曲的uv的噪点图。
Brightness:对ColorBuffer的加亮强度。
DistortionIntensity:扭曲强度。
DistortionFrequency:扭曲的频率。
注意性能
之前写过一篇 GrabPass 注意的问题。
手机上的话,还是得用另一种方式来实现。
还可以优化
可能是终于有人吐槽 Mesh
类中,无 GC 更新 Vertex Buffer Data 的方式
今天 2021/08/02 偶然 在 Unity 官方发现:Mesh.SetVertexBufferData
该文档备注了:Publication Date :2021-07-25
Copyright © 2020 Unity Technologies. Publication Date: 2021-07-25.
有了这个 API,我们就可以先申请足够大的 NativeArray,然后再更新 NativeArray 并 Mesh.SetVertexBufferData 上传到 buffer,起码 GC 问题可以得到解决
总结
整体思路很简单,就是绘制热扭曲拖尾前,先拿到ColorBuffer
的内容,然后回去拖尾是,偏移当前片段对应的uv
来采样ColorBuffer
的内容,即可达到效果。
我这个例子,还有很多优化空间:
ColorBuffer
不使用GrabPass
,改用Camera.Render()
,或是CommandBuffer
来优化输出到RT。(已实现:Unity Shader - 实现武器热扭曲拖尾效果(不需要GrabPass))- 热扭曲可以实现带方向的扭曲,从轨迹看出更明显的移动方向,与移动幅度。
Project
backup : UnityShader_WeaponDirtortionTrailTesting_2018.3.0f2
Unity Shader - GrabPass 实现武器热扭曲拖尾效果相关推荐
- shader拖尾_插件(一)武器拖尾效果
工作交接的空余时间,刚好有空,就有兴趣看了看U3D的武器拖尾功能,没想到有插件.下载地址: Pocket+RPG+Weapon+Trails.unitypackage PocketRPG Trails ...
- Unity初级案例-愤怒的小鸟:四:11解决重复划线和小鸟轮换速度突然变大的问题+12添加小鸟飞出的拖尾效果+13整合场景和解决无法显示划线弹弓的问题
目录 一.目的 1.想知道:愤怒的小鸟:如何制作 2.做好学习笔记,方便下次查看 二.参考 1.SIKI学院 三.注意 1.版本 操作:1:11解决重复划线和小鸟轮换速度突然变大的问题 1.GameM ...
- Unity 愤怒的小鸟拖尾效果——基于Pocket RPG Weapon Trails插件
Pocket RPG Weapon Trails 武器拖尾插件 前言 导入插件 给小鸟加入子物体并添加拖尾WeaponTrail脚本 接下来给小鸟加入TestMyTrail脚本控制拖尾的开始和结束时间 ...
- Unity3D 武器拖尾效果(刀光) 使用PocketRPG Trails
PocketRPG的示例下载:http://download.csdn.net/detail/xv_ly15/4999885 ------------------------------------- ...
- Unity中实现刀光剑影的方法之---拖尾asset store 插件-Melee Weapon Trails
Unity中实现刀光剑影的方法之--- 拖尾 & asset store 插件 首先是拖尾: 加入Unity自带的Trail Renderer代码 属性什么的就自己去弄懂吧,我也不太懂写这个就 ...
- Unity挥剑拖尾效果
角色挥剑时让挥剑动作有拖尾效果,如下所示 第一步:找到武器模型的根节点,创建一个空的对象"aaa",挂在武器模型下面,Transform的Position和Rotation根据情况 ...
- cocos2dx 关于拖影(类似dnf传承武器的拖尾效果)的快速实现
序: 当初看到dnf 中有些 拖影的效果,感觉很炫,所以到后面自己做游戏的时候自然的就想到了 那种炫 的拖尾效果~, 于是 我就想 要将效果 的实现写下来,以后 自己做游戏 也可以 用的了啊~~~~, ...
- Cocos Creator 的实现拖尾效果
在游戏中,有时会需要在某个游戏对象上加上移动后的轨迹若隐若现的效果.使得游戏的效果较好,比如游戏大招,刀光,法术,流星划痕之类. Cocos Creator提供了一种内置的拖尾渐隐效果的实现方法:组件 ...
- HTML5 canvas 拖尾效果(或尾巴 或方向标 或留痕。。。)
[canvas]带拖尾效果的旋转扩散 使用 HTML5 canvas 制作一个圆形的拖尾,以及附加上拖尾的扩散效果 我说说的拖尾,就是拖着一个尾巴,其他简短的名词暂时想不到了.下图效果弄出来,着实费劲 ...
最新文章
- AI每日看点 | 吉利收购飞行汽车公司;传华为暂停向高通支付专利费;高通拒绝博通收购邀约
- Spring中bean标签的属性和值:
- 复合数据类型,英文词频统计
- C指针原理(22)-C指针基础-att汇编-快速排序
- ant如何形成时间轴和图库_高中历史时间轴来了(电子高清版),最新最完整的复习时间线整理,这里都有!...
- 处理数字_9_计算标准差
- [转]memset用法详解
- java jpa 异步编程_异步处理时的JPA
- 国科大高级人工智能2-人工神经网络(MLP、Hopfield)
- axios文件上传 formdata_基于业务场景下的图片/文件上传方案总结
- koa mysql mongodb_koa如何连接MongoDB
- 工业级嵌入式串口转wifi模块
- 网络工程毕业设计之--校园网络规划与设计
- linux ip添加secondary ip,linux 添加secondary ip
- sails mysql_Sails+MVC+Mysql+Node+学习笔记一
- python和arduino哪个好_基于Python和Arduino的智能花盆系统
- 华为P40的软文营销文案
- SEO页面优化方案指南
- JAVA Swing + Jdbc 实现宿舍管理系统
- Java实现多级目录文件下载压缩包