资讯动态

Unity心电图波形模拟:速度颜色振幅可自定义的线波效果实现

发布时间:2026/10/17 16:26:32 来源:尧图企业网站定制
简介这是一份面向Unity开发者与图形编程学习者的心电图线波模拟项目demo基于Unity引擎与C#脚本实现可用于游戏视觉特效、医疗模拟演示或互动装置等场景。项目支持对波线速度、颜色与上下振幅进行自定义调整并通过键盘输入触发效果变化适合具备一定Unity基础、希望掌握动态线波渲染与参数化控制的开发者参考实践。压缩包共6688个文件以cs脚本、meta配置、png贴图、md说明文档为主另含prefab预制体、unity场景、shader着色器、dll库与json配置等完整保留了工程资源与依赖结构包体约39.19MB。目前已有331人学习下载。通过解构该demo读者可了解Mesh与Renderer绘制线波、Input输入响应、Time.deltaTime与协程控制动画速率、Material颜色动态修改以及Transform缩放调节振幅等实现思路并借鉴其组件化脚本设计与资源组织方式快速迁移到自身项目中。1. 从一条会呼吸的线说起Unity 心电图波形模拟到底在做什么如果你做过医疗类、科幻 UI 或者数据监控面板大概率遇到过这种需求屏幕上需要一条持续滚动的波形线上下起伏像心电图一样有节奏地跳动而且速度、颜色、振幅都得能随时调。听起来简单真动手写的时候才发现坑不少——线怎么画才不卡、振幅怎么控制才自然、滚动速度跟刷新率怎么解耦每一个都能让人调半天。这个 Unity 线波模拟心电图上下振幅效果项目 demo解决的就是这件事。它把一条可自定义的波形线封装成了一个能直接跑、直接改参数的完整场景波线速度、颜色、上下振幅都暴露成了可调项亲测在常规分辨率下运行流畅。适合两类人一类是刚接触 Unity 想找个能跑起来的小项目练手的新手另一类是在做医疗可视化、数据监控、科幻 HUD 界面需要快速集成一条动态波形的从业者。你不需要从零推导正弦函数也不用纠结 LineRenderer 的顶点更新逻辑拿到手改几个参数就能看到效果。我拆这个 demo 的时候第一反应是“这不就是个正弦波吗”但实际跑起来发现它把“像心电图”这件事拆成了三层基础波形生成、滚动位移、振幅调制。三层叠在一起才有了那种上下跳动的呼吸感。接下来我会按“怎么跑起来 → 参数怎么调 → 坑在哪 → 怎么改造成自己的东西”这条线把整个项目拆开讲一遍。2. 把 demo 跑起来场景结构、核心脚本与第一帧波形2.1 场景里有什么三个关键对象拿到项目后先别急着改代码把场景层级看清楚。这个 demo 的场景结构很干净核心就三个对象对象名作用关键组件WaveManager波形逻辑控制中心挂载 WaveController 脚本WaveLine实际渲染的线LineRendererMain Camera视角Camera正交或透视均可WaveManager 负责每帧计算波形数据并推给 WaveLineWaveLine 只负责把点画出来。这种拆分的好处是你换渲染方式比如换成 Mesh 或者 UI 的 VertexHelper时只需要改 WaveLine逻辑层不用动。常见做法是直接把脚本挂在 LineRenderer 所在的对象上但这个 demo 做了分离后面调参数的时候会舒服很多——你调振幅不会影响到渲染组件的配置。2.2 核心脚本逻辑每帧在算什么打开 WaveController核心逻辑集中在 Update 里。我把它简化成一段能看懂的伪代码// WaveController.cs 核心逻辑简化版 void Update() { // 1. 时间累加乘以速度系数 _time Time.deltaTime * waveSpeed; // 2. 遍历每个采样点计算 Y 值 for (int i 0; i pointCount; i) { float x i * pointSpacing; // 基础正弦波 振幅控制 float y Mathf.Sin(x * frequency _time) * amplitude; // 可选叠加一个高频小振幅模拟心电图的尖峰 y Mathf.Sin(x * frequency * 3f _time * 2f) * (amplitude * 0.3f); positions[i] new Vector3(x, y, 0); } // 3. 把计算好的点推给 LineRenderer lineRenderer.positionCount pointCount; lineRenderer.SetPositions(positions); }这段代码里_time是相位累加器waveSpeed控制滚动快慢amplitude控制上下振幅frequency控制波的密集程度。pointSpacing决定采样点之间的水平距离点越多线越平滑但性能开销也越大。参数说明waveSpeed典型值在 1 到 5 之间太小会显得呆滞太大就糊成一片amplitude按世界单位算正交相机下 0.5 到 2 比较合适pointCount建议 100 到 300低于 50 会有明显折角。2.3 第一帧就能看到的三个可调项跑起来之后Inspector 面板上会暴露三个最直观的参数Wave Speed、Line Color、Amplitude。这三个就是标题里说的“速度、颜色、振幅可自定义”。颜色直接改 LineRenderer 的材质或 Gradient速度改waveSpeed振幅改amplitude。改完不用重启运行时就能看到变化。我一般会先把速度调到 2 左右振幅调到 1颜色设成绿色或青色这样最接近常见的心电监护仪观感。提示如果运行时改参数没反应检查一下脚本里的变量是不是被[SerializeField]暴露了或者有没有在 Start 里缓存了初始值导致 Update 里用的是旧数据。3. 参数背后的数学振幅、频率、速度到底怎么互相影响3.1 正弦波三要素在代码里的映射很多人调参数是凭感觉拖拖到看着顺眼就停。但如果你想精确控制波形形态得知道每个参数对应正弦函数的哪个部分。标准正弦波是y A * sin(ωx φ)。在这个 demo 里amplitude对应 A直接乘在 sin 外面控制上下最大位移。frequency对应 ω乘在 x 上控制波在水平方向的密集程度。_time对应 φ 的一部分每帧累加让波形产生水平滚动的视觉效果。这里有个容易混淆的点waveSpeed和frequency都能让波形“动起来”但动的方式不一样。waveSpeed改变的是相位累加速度视觉上是整条波在水平方向平移frequency改变的是波的疏密视觉上是波峰波谷变多或变少。调的时候要分开调别混在一起。3.2 振幅调制的两种做法全局缩放 vs 逐点包络demo 里默认用的是全局振幅就是所有点乘同一个amplitude。但真实心电图不是等幅的R 峰高、T 峰低有包络变化。如果你想模拟得更像有两种改法第一种是逐点包络给每个采样点算一个独立的振幅系数// 逐点包络用另一个低频正弦控制振幅 float envelope 0.5f 0.5f * Mathf.Sin(x * 0.5f _time * 0.3f); float y Mathf.Sin(x * frequency _time) * amplitude * envelope;第二种是分段调制把 x 轴分成若干段每段用不同的振幅系数。这种做法更接近真实心电图的 P-QRS-T 形态但代码量会大一些需要维护一个振幅表。我一般先用全局振幅把整体效果调稳再叠加一层低频包络让波形有“呼吸感”。包络频率不要太高否则会显得杂乱。3.3 速度与帧率的解耦为什么用 Time.deltaTime_time Time.deltaTime * waveSpeed这行是关键。如果直接写_time waveSpeed那波形滚动速度就跟帧率绑死了——高帧率机器上飞快低帧率机器上慢吞吞。乘以Time.deltaTime之后每秒钟累加的相位量是恒定的跟帧率无关。这个坑我在早期项目里踩过当时在一台高刷显示器上调好的速度换到普通显示器上直接慢了一半排查了半天才发现是没乘 deltaTime。从那以后凡是跟时间相关的累加我都强制走一遍 deltaTime。注意如果你在 FixedUpdate 里做波形计算要用Time.fixedDeltaTime别混用。Update 和 FixedUpdate 的频率不一样混用会导致速度不一致。4. 避坑与排查线波模拟里最容易翻车的五个地方4.1 波形抖动或闪烁现象、原因、解决现象运行起来线在抖尤其是振幅调大之后波峰波谷位置不稳定。原因最常见的是在 Update 里重新分配了数组或 List导致 GC 频繁触发帧时间不稳定。另一个原因是pointSpacing太小采样点过于密集浮点精度误差被放大。解决把positions数组在 Start 里一次性分配好Update 里只改值不重新 new。pointSpacing不要低于 0.05除非你的相机拉得非常近。4.2 线宽不生效或线看起来太细现象改了 LineRenderer 的 width 曲线但场景里线还是细得像头发丝。原因LineRenderer 的宽度是受相机距离影响的。透视相机下线离相机越远越细正交相机下则跟 width 值直接对应。另外如果材质用的不是支持线宽的 shader宽度也可能不生效。解决先确认相机类型。正交相机下直接把 width 调到 0.1 到 0.3 试试。透视相机下要么把线拉近要么换用世界坐标下的宽度补偿。材质建议用 Sprites/Default 或 Unlit/Color别用 StandardStandard 不支持 LineRenderer 的宽度曲线。4.3 颜色改了但线还是白色现象在 Inspector 里改了 LineRenderer 的材质颜色运行时线还是白的。原因LineRenderer 的颜色由两个地方控制——材质本身的颜色和 LineRenderer 的 Color Gradient。如果 Gradient 是纯白且不透明材质颜色会被覆盖。另外如果材质 shader 不支持顶点色Gradient 也不生效。解决优先改 LineRenderer 的 Color Gradient把两端颜色都设成目标色。如果还不生效检查材质 shader 是否支持顶点色。常见做法是直接用 Sprites/Default它同时支持材质颜色和顶点色。4.4 波形滚动方向反了现象想让波从左往右滚结果是从右往左。原因_time的符号问题。sin(x t)和sin(x - t)的滚动方向是相反的。解决把_time Time.deltaTime * waveSpeed改成_time - Time.deltaTime * waveSpeed或者在计算 y 的时候把 _time改成- _time。改一个地方就行别两处都改否则又转回来了。4.5 采样点多了之后帧率下降现象把pointCount从 100 调到 500 之后帧率从 60 掉到 30 以下。原因每帧要计算 500 个点的位置并推给 LineRendererCPU 到 GPU 的数据传输量变大。如果同时还有多个波形线开销会成倍增加。解决优先用SetPositions而不是逐个SetPosition前者是一次性传输后者是多次调用。如果还不行考虑降低pointCount或者用 Job System 把计算并行化。一般监控面板场景200 个点足够平滑了。5. 改造成自己的波形从心电图到自定义数据驱动5.1 把正弦波换成真实数据序列demo 默认是数学正弦波但实际项目里你可能有真实的心电数据、传感器数据或者日志数据。改法很简单把 Update 里计算 y 的那行换成从数据数组里取值。// 数据驱动模式从预录数据里取 Y 值 void Update() { _time Time.deltaTime * waveSpeed; int offset Mathf.FloorToInt(_time * dataSampleRate) % dataLength; for (int i 0; i pointCount; i) { int dataIndex (offset i) % dataLength; float y dataArray[dataIndex] * amplitude; positions[i] new Vector3(i * pointSpacing, y, 0); } lineRenderer.SetPositions(positions); }这里dataArray是你的数据序列dataSampleRate控制每秒消耗多少个数据点。offset负责循环滚动取模保证不越界。这种写法适合数据回放场景比如把一段录制好的心电信号循环播放。5.2 用 Gradient 做颜色动态变化静态颜色太单调可以用 Gradient 让波峰和波谷颜色不同或者让颜色随时间渐变。// 根据 Y 值动态设置顶点颜色 Gradient gradient new Gradient(); GradientColorKey[] colorKeys new GradientColorKey[2]; colorKeys[0] new GradientColorKey(Color.green, 0f); colorKeys[1] new GradientColorKey(Color.red, 1f); GradientAlphaKey[] alphaKeys new GradientAlphaKey[2]; alphaKeys[0] new GradientAlphaKey(1f, 0f); alphaKeys[1] new GradientAlphaKey(1f, 1f); gradient.SetKeys(colorKeys, alphaKeys); lineRenderer.colorGradient gradient;这段代码把波谷设成绿色、波峰设成红色中间自动插值。适合做告警可视化——振幅超过阈值时颜色变红一眼就能看出来。5.3 多线叠加与性能边界如果你需要同时显示多条波形比如多导联心电直接复制多个 WaveLine 对象就行。但要注意性能边界每条线 200 个点10 条线就是 2000 个点每帧。在移动端上这个量级已经开始吃紧了。我一般会做两件事一是把不活跃的线禁用或降低pointCount二是把波形计算放到 Job System 里主线程只负责推数据。如果项目允许还可以用 GPU Instancing 把多条线合并成一个 Draw Call。提示多线场景下尽量共用同一个材质实例避免每个 LineRenderer 都生成独立材质导致 Draw Call 暴涨。5.4 一个我常用的调试习惯调波形参数的时候我习惯在 Scene 视图里把 Gizmos 打开把每个采样点画成小球。这样能直观看到点的分布和振幅变化比盯着 Game 视图猜要快得多。具体做法是在脚本里加一个OnDrawGizmos遍历positions画球。void OnDrawGizmos() { if (positions null) return; Gizmos.color Color.yellow; for (int i 0; i positions.Length; i) { Gizmos.DrawSphere(positions[i], 0.02f); } }这个习惯帮我省了很多来回改参数的时间。从那以后凡是跟曲线、路径、波形相关的调试我都强制走一遍 Gizmos 可视化先看清楚点的位置再调数值。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑