多人同屏渲染例子——3、生成VAT资源

devtools/2024/9/23 1:58:03/

Unity引擎制作万人同屏效果


  大家好,我是阿赵。
  之前介绍了怎样去用Shader播放顶点动画VAT。但这里有一个前提,就是这张顶点动画的贴图,是怎样生成的呢?
  由于是先说明了Shader的写法,所以可能会有一种先入为主的想法,是不是VAT贴图一定就要按照这种方式去播放呢?其实并不是。应该是反过来,由生成的格式决定播放的方法。不过由于要先说明VAT的原理,才好说明怎样去生成这类型的资源,所以我的说明是反过来了。
在这里插入图片描述

  现在再回头来看看这张VAT的贴图。之前漏了说一个比较重要的地方。由于我们需要获取的是绝对像素颜色来转换成顶点的坐标,所以这个颜色值的含义其实只是一个Vector3,也就是Shader里面的Float3,所以,sRGB不要勾选,这样不会受色彩空间的影响。然后FilterMode要选择成Point,也就是说没有任何插值。
  接下来开始分析我们需要的数据了。
  从比较简单的例子开始,假如现在有一个模型,它只包含一个网格,然后有6个动作,分别是idle、attack、damage、dead、run、walk。
1、生成UV2的网格
  首先,我们要通过UV2来读取VAT,那么我们需要有一个地方去存储UV2。而且由于是需要能在Shader直接读取的,所以存储在Mesh的UV2信息就很合适。但我们不一定能修改原始模型的Mesh,所以我的做法是复制一份原始模型的Mesh出来,把UV2设置进去。
设置UV2的方式也很简单,直接按照顶点的顺序设置就行。

        private void CreateTempMesh(){Mesh shareMesh = render.sharedMesh;tempMesh = new Mesh();Vector3[] tempVerts = new Vector3[shareMesh.vertices.Length];for(int i = 0;i<tempVerts.Length;i++){tempVerts[i] = render.gameObject.transform.TransformPoint(shareMesh.vertices[i]);}tempMesh.vertices = tempVerts;tempMesh.uv = shareMesh.uv;tempMesh.triangles = shareMesh.triangles;tempMesh.normals = shareMesh.normals;tempMesh.tangents = shareMesh.tangents;Vector2[] uv2 = new Vector2[vertexCount];float maxSize = VertexAnimCommonTool.GetNearestTexSize(vertexCount);for(int i = 0;i<vertexCount;i++){float u = (float)i / maxSize;uv2[i] = new Vector2(u, 0);}tempMesh.uv2 = uv2;//这里可以把网格保存下来,具体怎样保存就看个人需要}

2、 逐个动作保存信息
  有了UV2之后,根据上一篇的Shader里面需要的数据,我们还需要保存的信息有:

  1. 顶点坐标的范围最大最小值
  2. 动作每一帧的顶点坐标相对于最大最小值的比例
  3. 动作的最大帧率
  4. 动作的播放速度
  5. 贴图的最大高度
      由于这些信息,在不同动作时是不一样的,所以到这里是需要分开逐个动作去生成。
      主要的思路很简单,就是通过固定的帧率时间间隔,让带有Animator的动画跳转到固定某一帧,然后bake一个mesh出来,把上面的顶点信息都记录下来而已。旧版的Animation也一样。
      跳转动画主要是靠animator.playbackTime来控制的,不过我发现一个问题,这个api在Unity的非播放阶段控制起来比较困难。所以我的选择是,在导出工具运行的时候,先自动进入Unity的运行状态,再去控制动画播放。
    这是切换动作时的方法:
        private void ChangeAnim(string str){float animLength = GetAnimLength(str);int frameCount = (int)(animLength * frameRate);animator.Rebind();animator.StopPlayback();animator.recorderStartTime = 0;animator.Play(str);// 开始记录指定的帧数animator.StartRecording(frameCount);for (var i = 0; i < frameCount - 1; i++){// 记录每一帧animator.Update(1.0f / frameRate);}// 完成记录animator.StopRecording();animator.StartPlayback();animator.playbackTime = 0;animator.Update(0);}
然后通过获得动作的长度,然后根据帧率来遍历每一帧,记录每一帧的动作:for (int i = 0;i<frameCount;i++){float curTime = i * frameTime;animator.playbackTime = curTime;animator.Update(0);foreach(KeyValuePair<string,SkinnedMeshRenderer>item in skinMeshDict){ParsePartFrameAnim(item.Key,animName);}
}

bake每一帧的mesh待用

        public void ParsePartFrameAnim(string animName){if(render == null){return;}if(anims == null||anims.ContainsKey(animName)==false){return;}Mesh mesh = new Mesh();render.BakeMesh(mesh);anims[animName].AddToFrameMeshList(mesh);}
创建空贴图:private void CreateTempTexture(){texWidth = VertexAnimCommonTool.GetNearestTexSize(shareMesh.vertexCount);texHeight = VertexAnimCommonTool.GetNearestTexSize(frameCount);speed = 1 / time;tempTex = new Texture2D(texWidth, texHeight);Color blackColor = new Color(0, 0, 0, 1);for (int i = 0;i< texWidth; i++){for(int j = 0;j< texHeight; j++){tempTex.SetPixel(i, j, blackColor);}}tempTex.Apply();}
最后记录信息:private void ParseFrameTex(){float minX = 10000;float minY = 10000;float minZ = 10000;float maxX = -10000;float maxY = -10000;float maxZ = -10000;int vertCount = shareMesh.vertexCount;if(frameMeshList == null){return;}int frameCount = frameMeshList.Count;for (int i = 0; i < frameMeshList.Count; i++){Vector3[] verts = frameMeshList[i].vertices;for (int j = 0; j < verts.Length; j++){Vector3 vert = verts[j];if (vert.x < minX){minX = vert.x;}if (vert.y < minY){minY = vert.y;}if (vert.z < minZ){minZ = vert.z;}if (vert.x > maxX){maxX = vert.x;}if (vert.y > maxY){maxY = vert.y;}if (vert.z > maxZ){maxZ = vert.z;}}}float offsetX = maxX - minX;float offsetY = maxY - minY;float offsetZ = maxZ - minZ;for (int i = 0; i < frameMeshList.Count; i++){Vector3[] verts = frameMeshList[i].vertices;for (int j = 0; j < vertCount; j++){Vector3 vert = verts[j];float r = (vert.x - minX) / offsetX;float g = (vert.y - minY) / offsetY;float b = (vert.z - minZ) / offsetZ;Color col = new Color(r, g, b);tempTex.SetPixel(j, i, col);}}tempTex.Apply();boundMin = new Vector3(minX, minY, minZ);boundMax = new Vector3(maxX, maxY, maxZ);}

3、 保存除了贴图以外的信息。
  每一帧的顶点数据,可以保存在贴图里面,但由于最大帧数、贴图高度、bound的最大最小值这些,是不适合保存在贴图里面的,所以,可以单独保存在一个txt文本里面,然后运行的时候再读取出来,赋予给对应的材质球。
  还有,如果原来的材质比较规范,我们可以通过复制原来的模型的材质球上面的_MainTex,这样就可以方便很多。
  最后,把导出的资源都放在一起,一个角色最终导出的资源会是这样:
在这里插入图片描述


http://www.ppmy.cn/devtools/90418.html

相关文章

【AI绘画】fal/AuraFlow-v0.2出现 delete the irrelevant ones 错误

由于AuraFlow模型比较大&#xff0c;我就下在本地/hf_hub,结果运行Huggingface上README.md的代码 from diffusers import AuraFlowPipeline import torchpipeline AuraFlowPipeline.from_pretrained("/hf_hub/fal/AuraFlow-v0.2",torch_dtypetorch.float16,variant…

C#中const和readonly区别

编译时常量 vs. 运行时常量: const 定义的是编译时常量&#xff0c;这意味着它的值在编译时就已经确定&#xff0c;并且在程序的整个生命周期内都不会改变。const 字段通常是静态的&#xff0c;并且必须在声明时初始化。readonly 定义的是运行时常量&#xff0c;它的值可以在运…

Unity强化工程 之 SpriteRender

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 1.SpriteRenderer是什么 渲染精灵用的&#xff0c;是渲染的核心组件&#xff0c;有许多重要参数所以要详细讲一讲 Spri…

哈尔滨等保测评——为工业网络安全保驾护航新航标

哈尔滨&#xff0c;这个以冰雪和美丽闻名世界的城市&#xff0c;现在又树立了一个全新的行业标准&#xff0c;那就是“等保”&#xff0c;正在掀起一场新的安全革命&#xff0c;保卫着这个智能时代&#xff01; ❄️【哈尔滨新视野】❄️ 哈尔滨是一块充满创新活力的土地&…

《清远折叠》,数智广东第一个SPN政务专网故事

今天&#xff0c;越来越多物理世界中的产业&#xff0c;正在与数字世界完成交汇&#xff0c;改变着我们习以为常的生活方式。 比如政务专网&#xff0c;就通过一张专用网络&#xff0c;将物理世界的政府部门与城市居民&#xff0c;在数字世界中“折叠”到一起&#xff0c;让人们…

亚信安全获国家信息安全服务(风险评估和安全工程类)二级资质

近日&#xff0c;亚信安全荣获由中国信息安全测评中心颁发的《国家信息安全测评信息安全服务资质证书—风险评估二级》和《国家信息安全测评信息安全服务资质证书—安全工程类二级》资质。亚信安全凭借综合实力和优秀的技术能力&#xff0c;成为为数不多的获得国家信息安全服务…

Python 管理依赖包(pip, virtualenv)

在Python编程中&#xff0c;管理依赖包是开发工作的重要组成部分。正确管理依赖包可以确保代码在不同环境中的一致性和可移植性&#xff0c;避免版本冲突和依赖地狱等问题。Python中常用的依赖包管理工具包括pip和virtualenv。 一、pip pip是Python官方推荐的包管理工具&…

Codeforces 888 div3 A-G

A. Escalator Conversations 分析 二者身高差为k的倍数且不超过m-1倍&#xff0c;身高差不能为0&#xff08;即不能在同一个阶梯&#xff09; C代码 #include<iostream> using namespace std; void solve(){int n,m,k,H,ans0;cin>>n>>m>>k>>H;…