当前位置: 首页 > news >正文

《大规模动画优化(一):GPU 顶点动画的生成》

GPU 顶点动画(Vertex Animation Texture, VAT)

GPU 顶点动画(Vertex Animation Texture, VAT)烘焙的核心思想是: 在 CPU
端预先计算动画顶点数据,并存储到纹理(Texture2D)中,在 GPU 端通过 Shader 读取纹理来播放动画。

📌 1. 烘焙的核心步骤

  1. 采样动画帧(AnimationClip)
  2. 获取 SkinnedMeshRenderer 在每帧的顶点数据
  3. 存储到 Texture2D作为动画纹理
  4. 在 GPU 端使用 Shader 读取并应用动画

🎯 Step 1: 获取角色的 SkinnedMeshRenderer

角色网格 = (SkinnedMeshRenderer)EditorGUILayout.ObjectField("角色网格", 角色网格, typeof(SkinnedMeshRenderer), true);

这里 角色网格 是 SkinnedMeshRenderer,它是 Unity 蒙皮网格(Skinned Mesh)动画系统的核心组件。
为什么要用 SkinnedMeshRenderer?
Unity 角色动画一般都是骨骼动画,蒙皮网格的顶点由骨骼驱动,因此我们要从SkinnedMeshRenderer 获取最终变形后的顶点数据,而不能直接用 MeshFilter.mesh。

🎯 Step 2: 采样动画帧

在 角色动画烘焙类.cs 里,我们通过 AnimationMode.SampleAnimationClip 让角色处于特定动画帧。

AnimationMode.StartAnimationMode();
AnimationMode.BeginSampling();

AnimationMode.StartAnimationMode()
让 Unity 进入动画采样模式,这样可以在不播放动画的情况下手动设定帧数并获取角色姿态。
AnimationMode.SampleAnimationClip(角色网格.gameObject, 动画片段, 归一化时间);
让角色静态停留在某个时间点的动画帧。

🎯 Step 3: 获取烘焙网格数据

角色网格.BakeMesh(烘焙网格);
Vector3[] 顶点数据 = 烘焙网格.vertices;

BakeMesh():
让 SkinnedMeshRenderer 计算当前帧的最终顶点位置,存入 Mesh 对象中。
这个方法会把蒙皮骨骼变换后的顶点复制出来,避免骨骼动画影响原始 MeshFilter.mesh。
烘焙网格.vertices:
获取角色在当前动画帧的所有变形后的顶点位置。

🎯 Step 4: 存储动画数据到 Texture2D

如果需要存储发现数据和切线数据,则可以另外存储

动画纹理.SetPixel(i, j, new Color(顶点.x, 顶点.y, 顶点.z, 1));
动画纹理.SetPixel(i, animLength + j + previewAnimationLength, normalsData); // 存储法线数据 (偏移 animLength 行)
// 处理切线数据 (直接存储)
Color tangentsData = bakeMeshTangents[j]; 
动画纹理.SetPixel(i, animLength * 2 + j + previewAnimationLength, tangentsData); // 存储切线数据 (偏移 2 * animLength 行)

🎯 Step 5: 处理所有帧

for (int i = 0; i < 总帧数; i++)
{float 归一化时间 = (float)i / (float)(总帧数 - 1) * 动画片段.length;AnimationMode.SampleAnimationClip(角色网格.gameObject, 动画片段, 归一化时间);角色网格.BakeMesh(烘焙网格);Vector3[] 顶点数据 = 烘焙网格.vertices;for (int j = 0; j < 顶点数据.Length; j++){Vector3 顶点 = 顶点数据[j];动画纹理.SetPixel(i, j, new Color(顶点.x, 顶点.y, 顶点.z, 1));}
}
动画纹理.Apply();

遍历每一帧,采样动画数据
总帧数 由 动画片段长度 × 采样帧率 确定
归一化时间 归一化时间 = i / (总帧数 - 1) * 动画片段.length
每帧都 BakeMesh(),然后 SetPixel() 存入纹理
🎯 Step 6: 导出并保存 .png

byte[] 纹理数据 = 动画纹理.EncodeToPNG();
File.WriteAllBytes(完整路径, 纹理数据);
AssetDatabase.Refresh();

EncodeToPNG():把 Texture2D 转换为 PNG 格式
File.WriteAllBytes():将数据保存到硬盘
AssetDatabase.Refresh():通知 Unity 刷新资源库,🌟 最终结果
✅ 我们获得了一张 动画数据纹理,类似下面这样:

动画帧(宽度) → 顶点 1 顶点 2 顶点 3 …
帧 1 (x,y,z) (x,y,z) (x,y,z) …
帧 2 (x,y,z) (x,y,z) (x,y,z) …
… … … … …
🔹 横向 代表动画帧,🔹 纵向 代表模型的每个顶点。

在 GPU 端,我们可以 直接用 Shader 采样这个纹理,快速应用动画,而无需再让 CPU 计算骨骼动画!让 .png 文件显示在 Project 视图中

🎯 总结

步骤代码操作作用
获取角色网格SkinnedMeshRenderer选择要烘焙的角色
启动动画模式AnimationMode.StartAnimationMode()让 Unity 进入动画编辑模式
逐帧采样动画AnimationMode.SampleAnimationClip()让角色停留在特定动画帧
获取顶点数据BakeMesh().vertices采集当前帧的蒙皮网格数据
存储到纹理SetPixel()将动画帧存入 Texture2D
保存文件EncodeToPNG()导出动画数据纹理

✅ 烘焙后的动画纹理可以直接用于 GPU 渲染,实现海量动画角色的高效渲染!

附上完整代码

using UnityEngine;
using UnityEditor;
using System.IO;/// <summary>
/// GPU 顶点动画烘焙窗口
/// </summary>
public class 角色动画烘焙窗口类 : EditorWindow
{private SkinnedMeshRenderer 角色网格;private AnimationClip 动画片段;private int 采样帧数 = 30;private string 存储路径 = "Assets/GPUAnimations/";[MenuItem("工具/GPU 顶点动画烘焙")]public static void 打开窗口方法(){角色动画烘焙窗口类 窗口 = (角色动画烘焙窗口类)GetWindow(typeof(角色动画烘焙窗口类));窗口.titleContent = new GUIContent("GPU 顶点动画烘焙");窗口.Show();}private void OnGUI(){GUILayout.Label("GPU 顶点动画烘焙", EditorStyles.boldLabel);角色网格 = (SkinnedMeshRenderer)EditorGUILayout.ObjectField("角色网格", 角色网格, typeof(SkinnedMeshRenderer), true);动画片段 = (AnimationClip)EditorGUILayout.ObjectField("动画片段", 动画片段, typeof(AnimationClip), false);采样帧数 = EditorGUILayout.IntField("采样帧数", 采样帧数);存储路径 = EditorGUILayout.TextField("存储路径", 存储路径);if (GUILayout.Button("开始烘焙")){if (角色网格 == null || 动画片段 == null){EditorUtility.DisplayDialog("错误", "请指定角色网格和动画片段!", "确定");return;}开始烘焙方法();}}private void 开始烘焙方法(){角色动画烘焙类.烘焙动画方法(角色网格, 动画片段, 采样帧数, 存储路径);}
}
/// <summary>
/// 角色 GPU 顶点动画烘焙
/// </summary>
public class 角色动画烘焙类
{public static void 烘焙动画方法(SkinnedMeshRenderer 角色网格, AnimationClip 动画片段, int 采样帧数, string 存储路径){Mesh 烘焙网格 = new Mesh();int 总帧数 = 采样帧数;int 顶点数 = 角色网格.sharedMesh.vertexCount;string 文件名 = 角色网格.gameObject.name + "_" + 动画片段.name + ".png";string 完整路径 = Path.Combine(存储路径, 文件名);// 创建纹理Texture2D 动画纹理 = new Texture2D(总帧数, 顶点数, TextureFormat.RGBAFloat, false);Animator 角色动画器 = 角色网格.GetComponentInParent<Animator>();if (角色动画器 == null){Debug.LogError("角色缺少 Animator 组件!");return;}// 预览播放动画//让 Unity 进入动画采样模式,这样可以在不播放动画的情况下手动设定帧数并获取角色姿态。AnimationMode.StartAnimationMode();AnimationMode.BeginSampling();//让角色静态停留在某个时间点的动画帧。AnimationMode.SampleAnimationClip(角色网格.gameObject, 动画片段, 0);for (int i = 0; i < 总帧数; i++){float 归一化时间 = (float)i / (float)(总帧数 - 1) * 动画片段.length;AnimationMode.SampleAnimationClip(角色网格.gameObject, 动画片段, 归一化时间);角色网格.BakeMesh(烘焙网格);Vector3[] 顶点数据 = 烘焙网格.vertices;Vector3[] 法线数据 = 烘焙网格.normals; // 获取法线Vector4[] 切线数据 = 烘焙网格.tangents; // 获取切线// 存储顶点位置、法线和切线for (int j = 0; j < 顶点数据.Length; j++){Vector3 顶点 = 顶点数据[j];Vector3 法线 = 法线数据[j];Vector4 切线 = 切线数据[j];// 使用 RGBA 通道存储数据// R: 顶点位置 X// G: 顶点位置 Y// B: 顶点位置 Z// A: 法线 X// 将法线的 Y、Z、W 存储到 RGB 通道中Color pixelData = new Color(顶点.x, 顶点.y, 顶点.z, 法线.x); // 顶点位置和法线X// 存储到纹理中动画纹理.SetPixel(i, j, pixelData);// 将法线Y、Z以及切线的X、Y、Z存储到同一纹理的其他通道pixelData = new Color(法线.y, 法线.z, 切线.x, 切线.y); // 法线 Y, Z 和 切线 X, Y动画纹理.SetPixel(i, j + 顶点数, pixelData);// 存储切线的 W 分量pixelData = new Color(切线.z, 切线.w, 0, 0);动画纹理.SetPixel(i, j + 2 * 顶点数, pixelData);}}动画纹理.Apply();// 保存到本地byte[] 纹理数据 = 动画纹理.EncodeToPNG();File.WriteAllBytes(完整路径, 纹理数据);AssetDatabase.Refresh();AnimationMode.EndSampling();AnimationMode.StopAnimationMode();Debug.Log($"GPU 顶点动画烘焙完成!文件保存至 {完整路径}");}
}

🚀 下一步:使用 Shader 读取纹理,在 GPU 端播放动画!

相关文章:

《大规模动画优化(一):GPU 顶点动画的生成》

GPU 顶点动画&#xff08;Vertex Animation Texture, VAT&#xff09; GPU 顶点动画&#xff08;Vertex Animation Texture, VAT&#xff09;烘焙的核心思想是&#xff1a; 在 CPU 端预先计算动画顶点数据&#xff0c;并存储到纹理&#xff08;Texture2D&#xff09;中&#xf…...

【前端】几种常见的跨域解决方案

在前端开发中&#xff0c;跨域问题是常见的挑战。以下是几种常见的跨域解决方案&#xff1a; 1. Nginx反向代理 使用 Nginx 进行反向代理是解决跨域问题的一种常见方式。Nginx 会充当一个中间代理服务器&#xff0c;接收来自前端的请求并将其转发到实际的后端 API 服务&#…...

如何在WinForms应用程序中读取和写入App.config文件

如何在WinForms应用程序中读取和写入App.config文件 1. 添加App.config文件2. 配置App.config3. 读取App.config4. 写入App.config 在WinForms应用程序中&#xff0c; App.config文件是用于存储配置数据的标准方式。通过使用.NET框架提供的类库&#xff0c;我们可以方便地对 …...

【分布式理论7】分布式调用之:服务间的(RPC)远程调用

文章目录 一、RPC 调用过程二、RPC 动态代理&#xff1a;屏蔽远程通讯细节1. 动态代理示例2. 如何将动态代理应用于 RPC 三、RPC序列化与协议编码1. RPC 序列化2. RPC 协议编码2.1. 协议编码的作用2.2. RPC 协议消息组成 四、RPC 网络传输1. 网络传输流程2. 关键优化点 一、RPC…...

人工智能应用-智能驾驶精确的目标检测和更高级的路径规划

实现更精确的目标检测和更高级的路径规划策略是自动驾驶领域的核心任务。以下是一个简化的示例&#xff0c;展示如何使用Python和常见的AI库&#xff08;如TensorFlow、OpenCV和A*算法&#xff09;来实现这些功能。 1. 环境准备 首先&#xff0c;确保安装了以下库&#xff1a;…...

dynamic_cast和static_cast和const_cast

dynamic_cast 在 C 中的作用 dynamic_cast 是 C 运行时类型转换&#xff08;RTTI, Run-Time Type Identification&#xff09;的一部分&#xff0c;主要用于&#xff1a; 安全的多态类型转换检查类型的有效性向下转换&#xff08;Downcasting&#xff09;跨类层次的指针或引用…...

DEEPSEEK与GPT等AI技术在机床数据采集与数字化转型中的应用与影响

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;深度学习、自然语言处理等先进技术开始广泛应用于各行各业。在制造业尤其是机床行业&#xff0c;AI技术的融合带来了巨大的变革&#xff0c;尤其在机床数据采集与机床数字化方面的应用。本文将探讨DEEPSEEK、…...

高速存储文章目录

《zynq tcp万兆网和ftp协议分析-CSDN博客》 《国产fpga nvme ip高速存储方案设计_fpga 高速存储-CSDN博客》 《国微pcie switch 8748高速存储方案设计_国产pcie switch-CSDN博客》 《FPGA SATA高速存储设计-CSDN博客》 《FPGA NVME高速存储设计_690t fpga-CSDN博客》 《zy…...

车载测试工具 --- CANoe VH6501 进行Not Acknowledge (NAck) 测试

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…...

【清晰教程】通过Docker为本地DeepSeek-r1部署WebUI界面

【清晰教程】本地部署DeepSeek-r1模型-CSDN博客 目录 安装Docker 配置&检查 Open WebUI 部署Open WebUI 安装Docker 完成本地DeepSeek-r1的部署后【清晰教程】本地部署DeepSeek-r1模型-CSDN博客&#xff0c;通过Docker为本地DeepSeek-r1部署WebUI界面。 访问Docker官…...

Linux运维——用户管理

Linux用户管理 一、Linux用户管理要点二、常用命令2.1、groupadd2.2、groupdel2.3、groupmod2.4、groups2.5、useradd2.6、userdel2.7、passwd2.9、su2.10、sudo2.10.1、给普通用户授权 sudo2.10.2、 免密码授权 sudo 一、Linux用户管理要点 创建用户组 - 使用 groupadd删除用…...

mac下dify+deepseek部署,实现私人知识库

目前deepseek 十分火爆&#xff0c;本地部署实现私有知识库&#xff0c;帮助自己日常工作&#xff0c;上一篇使用工具cherry studio可以做到私人知识库。今天学习了一下&#xff0c;使用Dify链接deepseek&#xff0c;实现私人知识库&#xff0c;也非常不错&#xff0c;这里分享…...

Linux中设置开机运行指令

系统&#xff1a;Debian 12 使用systemd来设置开机自启动脚本或命令是一个更加现代且推荐的方法。下面是具体的步骤&#xff1a; 创建守护脚本 首先&#xff0c;你需要创建一个Shell脚本文件&#xff0c;比如mydaemon.sh&#xff0c;并在其中编写你的守护脚本逻辑。确保这个脚…...

IDEA中列举的是否是SpringBoot的依赖项的全部?在哪里能查到所有依赖项,如何开发自己的依赖项让别人使用

在 IntelliJ IDEA 中列举的依赖项并不一定是 Spring Boot 项目的全部依赖项。IDEA 通常只显示你在 pom.xml&#xff08;Maven&#xff09;或 build.gradle&#xff08;Gradle&#xff09;中显式声明的依赖项&#xff0c;而这些依赖项本身可能还会引入其他传递性依赖。 1. 如何…...

Ollama命令使用指南

Ollama 命令使用指南 Ollama 命令使用指南1. Ollama 命令概览2. Ollama 命令详解2.1 启动 Ollama2.2 创建模型2.3 查看模型信息2.4 运行模型2.5 停止运行的模型2.6 从注册表拉取模型2.7 推送模型到注册表2.8 列出本地模型2.9 查看正在运行的模型2.10 复制模型2.11 删除模型 3. …...

LIMO:上海交大的工作 “少即是多” LLM 推理

25年2月来自上海交大、SII 和 GAIR 的论文“LIMO: Less is More for Reasoning”。 一个挑战是在大语言模型&#xff08;LLM&#xff09;中的复杂推理。虽然传统观点认为复杂的推理任务需要大量的训练数据&#xff08;通常超过 100,000 个示例&#xff09;&#xff0c;但本文展…...

Android studio怎么创建assets目录

在Android Studio中创建assets文件夹是一个简单的步骤&#xff0c;通常用于存储不需要编译的资源文件&#xff0c;如文本文件、图片、音频等 main文件夹&#xff0c;邮件new->folder-assets folder...

常见的前端框架和库有哪些

1. React 描述&#xff1a;由 Facebook 开发的一个 JavaScript 库&#xff0c;用于构建用户界面&#xff0c;尤其是单页面应用&#xff08;SPA&#xff09;。特点&#xff1a; 基于组件的架构&#xff0c;便于重用 UI 组件。使用虚拟 DOM 提升性能。容易与其他库和框架集成。 …...

【批量获取图片信息】批量获取图片尺寸、海拔、分辨率、GPS经纬度、面积、位深度、等图片属性里的详细信息,提取出来后导出表格,基于WPF的详细解决方案

摄影工作室通常会有大量的图片素材&#xff0c;在进行图片整理和分类时&#xff0c;需要知道每张图片的尺寸、分辨率、GPS 经纬度&#xff08;如果拍摄时记录了&#xff09;等信息&#xff0c;以便更好地管理图片资源&#xff0c;比如根据图片尺寸和分辨率决定哪些图片适合用于…...

数据结构与算法(test3)

七、查找 1. 看图填空 查找表是由同一类型的数据元素&#xff08;或记录&#xff09;构成的集合。例如上图就是一个查找表。 期中&#xff08;1&#xff09;是______________. (2)是______________(3)是_____关键字_______。 2. 查找(Searching) 就是根据给定的某个值, 在查…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...