unity 一万个量具有相同动画的物体渲染

  • 先显示效果,帧率在70左右
  • 性能分析
  • 大量具有相同动画的物体,首先想到的是GPU Instancing
    • 首先Skin Mesh合并,一个物体下面的所有mesh合并,并且共用一个材质球。(这一步目前没做)
    • 设置一个结构体,这个结构体里面有物体的位置和角度信息。通过job system,转换为本地2世界坐标矩阵
    • 得到物体每帧动画的mesh,然后用GPU Instancing大批量渲染这些mesh。
  • C#代码
  • 调用代码,渲染一万个物体
  • shader代码

首先说明本人机器CPU是 i5-7400,GPU是GTX 1060 3G。模型三角面大约2K,分辨率1080P。

先显示效果,帧率在70左右

性能分析

渲染阴影大约用时2ms

渲染物体大约用时2.3ms

渲染一万个物体 cpu一共耗时2.3ms

BaseRenderStruct[] baseRenderStructs = new BaseRenderStruct[10000];
for (int i = 0; i < 100; i++)
{for (int j = 0; j < 100; j++){baseRenderStructs[i*100+j].position = new War.Vector2(j,i);baseRenderStructs[i*100+j].rotation = j * 36;}}Observable.EveryUpdate().Subscribe(_ =>{Profiler.BeginSample("CharacterRender");m_renderManger.DrawCharacterInstanced(baseRenderStructs);Profiler.EndSample();});

大量具有相同动画的物体,首先想到的是GPU Instancing

物体具有动画和Skinned Mesh,而且mesh不只有一个。物体可以投射阴影和接受阴影。思想如下:

首先Skin Mesh合并,一个物体下面的所有mesh合并,并且共用一个材质球。(这一步目前没做)

链接: SkinMesh合并

设置一个结构体,这个结构体里面有物体的位置和角度信息。通过job system,转换为本地2世界坐标矩阵

public struct BaseRenderStruct
{public Vector2 position;public float rotation;
}public struct MyParallelJob : IJobParallelFor
{[ReadOnly]public NativeArray<BaseRenderStruct> datas;[ReadOnly] public Matrix4x4 selfRotation;public NativeArray<Matrix4x4> result;public void Execute(int i){Matrix4x4 mat = Matrix4x4.identity;mat.m03 = (float) datas[i].position.x;mat.m13 = 0.0f;mat.m23 = (float) datas[i].position.y;result[i] = mat * Matrix4x4.Rotate(Quaternion.Euler(0, datas[i].rotation - 90, 0)) * selfRotation;}
}

得到物体每帧动画的mesh,然后用GPU Instancing大批量渲染这些mesh。

这里主要使用skinnedMeshRenderers.BakeMesh的方法烘培一个Mesh。
然后用Graphics.DrawMesh绘制大量Mesh。

C#代码

// 渲染结构体
public struct BaseRenderStruct
{public Vector2 position;public float rotation;
}public class Player3DCharacterRender
{private List<SkinnedMeshRenderer> m_skinnedMeshRenderers;private List<Mesh> m_animedMeshs;private int m_CurFrameCount = -1;private float m_StartTime;private Animator m_Animator;private GameObject m_go;private AnimationClip m_clip;private Matrix4x4 m_selfRotation;MyParallelJob myParallelJob;public void Init(GameObject go){m_go = go;m_StartTime = Time.time;m_skinnedMeshRenderers = new List<SkinnedMeshRenderer>();m_animedMeshs = new List<Mesh>();m_Animator = m_go.GetComponent<Animator>();m_selfRotation = Matrix4x4.Rotate(Quaternion.Euler(-90, 0, 0));SkinnedMeshRenderer[] skinnedMeshRenderers = m_go.GetComponentsInChildren<SkinnedMeshRenderer>();foreach (var skinnedMeshRenderer in skinnedMeshRenderers){m_skinnedMeshRenderers.Add(skinnedMeshRenderer);m_animedMeshs.Add(new Mesh());}foreach (var clip in m_Animator.runtimeAnimatorController.animationClips){if (clip.name.Equals("run(WeaponOneHand)")){m_clip = clip;break;}}myParallelJob = new MyParallelJob();myParallelJob.selfRotation = m_selfRotation;}public void Render(BaseRenderStruct data){if(m_go == null)return;if(m_CurFrameCount != Time.frameCount)BakeMesh();MaterialPropertyBlock properties = new MaterialPropertyBlock();Matrix4x4 m;CalculateWorldMatAndPlayIndex(data, out m);for (int i = 0; i < m_animedMeshs.Count; i++){Graphics.DrawMesh(m_animedMeshs[i] ,m,m_skinnedMeshRenderers[i].sharedMaterial,0,Camera.main,0,properties,ShadowCastingMode.On);}}public void Render(BaseRenderStruct[] data){if(m_go == null)return;if(m_CurFrameCount != Time.frameCount)BakeMesh();int group = data.Length / 1000;group += data.Length % 1000 > 0 ? 1 : 0;MaterialPropertyBlock properties = new MaterialPropertyBlock();/*for(int i = 0;i<data.Length;i++)CalculateWorldMatAndPlayIndex(data[i], out m[i]);*/myParallelJob.datas = new NativeArray<BaseRenderStruct>(data.Length, Allocator.TempJob);myParallelJob.datas.CopyFrom(data);NativeArray<Matrix4x4> result = new NativeArray<Matrix4x4>(data.Length, Allocator.TempJob);myParallelJob.result = result;JobHandle handle = myParallelJob.Schedule(data.Length, 1);handle.Complete();Matrix4x4[] m = result.ToArray();Matrix4x4[] subM = new Matrix4x4[1000];for (int n = 0; n < group; n++){int count = data.Length - n * 1000;count = count > 1000 ? 1000 : count;Array.Copy(m, n * 1000, subM, 0, count);for (int i = 0; i < m_animedMeshs.Count; i++){Graphics.DrawMeshInstanced(m_animedMeshs[i] ,0,m_skinnedMeshRenderers[i].sharedMaterial,subM,count,properties);}}myParallelJob.datas.Dispose();result.Dispose();}void CalculateWorldMatAndPlayIndex(BaseRenderStruct data ,out Matrix4x4 mat){mat = Matrix4x4.identity;mat.m03 = (float) data.position.x;mat.m13 = 0.0f;mat.m23 = (float) data.position.y;mat =  mat*Matrix4x4.Rotate(Quaternion.Euler(0,data.rotation - 90,0))*m_selfRotation;}public struct MyParallelJob : IJobParallelFor{[ReadOnly]public NativeArray<BaseRenderStruct> datas;[ReadOnly] public Matrix4x4 selfRotation;public NativeArray<Matrix4x4> result;public void Execute(int i){Matrix4x4 mat = Matrix4x4.identity;mat.m03 = (float) datas[i].position.x;mat.m13 = 0.0f;mat.m23 = (float) datas[i].position.y;result[i] = mat * Matrix4x4.Rotate(Quaternion.Euler(0, datas[i].rotation - 90, 0)) * selfRotation;}}void BakeMesh(){m_CurFrameCount = Time.frameCount;float time = (Time.time - m_StartTime) % m_clip.length;m_clip.SampleAnimation(m_go, time);for (int i = 0; i < m_skinnedMeshRenderers.Count; i++){m_animedMeshs[i].Clear();m_skinnedMeshRenderers[i].BakeMesh(m_animedMeshs[i]);}}
}

调用代码,渲染一万个物体

// A code block
BaseRenderStruct[] baseRenderStructs = new BaseRenderStruct[10000];
for (int i = 0; i < 100; i++)
{for (int j = 0; j < 100; j++){baseRenderStructs[i*100+j].position = new War.Vector2(j,i);baseRenderStructs[i*100+j].rotation = j * 36;}}Observable.EveryUpdate().Subscribe(_ =>{Profiler.BeginSample("CharacterRender");m_renderManger.DrawCharacterInstanced(baseRenderStructs);Profiler.EndSample();});

shader代码

Shader "Unlit/CharacterDefault"
{Properties{_BaseMap ("Base Texture",2D) = "white"{}_BaseColor("Base Color",Color)=(1,1,1,1)[Toggle]_IsSpecular("是否开启高光", Float) = 1}SubShader{Tags{"RenderPipeline"="UniversalPipeline""Queue"="Geometry""RenderType"="Opaque"}HLSLINCLUDE#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"CBUFFER_START(UnityPerMaterial)float4 _BaseMap_ST;half4 _BaseColor;half _IsSpecular;CBUFFER_ENDENDHLSLPass{Tags{"LightMode"="UniversalForward"}HLSLPROGRAM //CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_instancing#pragma multi_compile _ _MAIN_LIGHT_SHADOWS#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADEstruct Attributes{float4 positionOS : POSITION;float4 normalOS : NORMAL;float2 uv : TEXCOORD;UNITY_VERTEX_INPUT_INSTANCE_ID};struct Varings//这就是v2f{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD;float3 positionWS : TEXCOORD1;float3 viewDirWS : TEXCOORD2;float3 normalWS : TEXCOORD3;UNITY_VERTEX_INPUT_INSTANCE_ID};TEXTURE2D(_BaseMap);SAMPLER(sampler_BaseMap);Varings vert(Attributes IN){Varings OUT;UNITY_SETUP_INSTANCE_ID(IN);UNITY_TRANSFER_INSTANCE_ID(IN, OUT);VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz);VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS.xyz);OUT.positionCS = positionInputs.positionCS;OUT.uv=TRANSFORM_TEX(IN.uv,_BaseMap);OUT.positionWS = positionInputs.positionWS;OUT.viewDirWS = GetCameraPositionWS() - positionInputs.positionWS;OUT.normalWS = normalInputs.normalWS;return OUT;}float4 frag(Varings IN):SV_Target{UNITY_SETUP_INSTANCE_ID(IN);half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);float4 SHADOW_COORDS = TransformWorldToShadowCoord(IN.positionWS);Light light = GetMainLight(SHADOW_COORDS);half3 n = normalize(IN.normalWS);half3 v = normalize(IN.viewDirWS);half3 h = normalize(light.direction + v);half nl = max(0.0,dot(light.direction ,n));half nh = max(0.0,dot(h ,n));half atten = step(0.5, light.shadowAttenuation);half3 diffuse = atten * lerp(0.5*baseMap.xyz ,baseMap.xyz ,nl) + (1 - atten) * 0.4 * baseMap.xyz * light.color ;half3 specular = _IsSpecular * atten * light.color * step(0.8,pow(nh ,8));uint pixelLightCount = GetAdditionalLightsCount();for (uint lightIndex = 0; lightIndex < pixelLightCount; ++lightIndex){Light add_light = GetAdditionalLight(lightIndex, IN.positionWS);half3 add_h = normalize(add_light.direction + v);half add_nl = max(0.0,dot(add_light.direction ,n));half add_nh = max(0.0,dot(add_h ,n));diffuse += baseMap.xyz * add_nl* add_light.color * add_light.distanceAttenuation;specular += _IsSpecular * add_light.color * add_light.distanceAttenuation * step(0.8,pow(add_nh ,8));}half3 color=diffuse*_BaseColor.xyz;return half4(color ,1.0);}ENDHLSL  //ENDCG          }pass {Tags{ "LightMode" = "ShadowCaster" }HLSLPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_instancingstruct Attributes{float4 vertex : POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID};struct Varings{float4 pos : SV_POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID};sampler2D _MainTex;float4 _MainTex_ST;Varings vert(Attributes v){Varings o = (Varings)0;UNITY_SETUP_INSTANCE_ID(v);UNITY_TRANSFER_INSTANCE_ID(v, o);o.pos = mul(UNITY_MATRIX_MVP,v.vertex);return o;}float4 frag(Varings i) : SV_Target{UNITY_SETUP_INSTANCE_ID(i);return half4(0.0,0.0,0.0,1.0);}ENDHLSL}}
}

unity 一万个具有相同动画的物体渲染相关推荐

  1. unity 游戏内实现3连击动画(状态机)

    上节课讲了unity 游戏内实现3连击动画的实现,因为在游戏中我们的连击动画可能会有很多,为了避免使用较多的if  else 我们这节课开始引用状态机来更好的实现效果,那么接下来我们就开始状态机的讲解 ...

  2. Unity Shader学习记录(18) —— Shader动画

    纹理动画 纹理动画在游戏中的应用非常广泛.尤其在各种资源都比较局限的移动平台上,我们往往会使用纹理动画来代替复杂的粒子系统等模拟各种动画效果. 11.2.1序列帧动画 _Time是float4类型, ...

  3. Unity 角色朝向目标 / 动态转向动画

    Unity 角色朝向目标 / 动态转向动画 一.静态角色转向 1.1 C# 脚本 1.2 Unity 实现 1.3 修改 二.动态角色转向 2.1 C# 脚本 2.2 Unity 实现 写在最最最后 ...

  4. Unity使用FBX Exporter导入导出动画及FBX

    Unity使用FBX Exporter导入导出动画及FBX 引子 导入 导出 FBX Exporter Generic通用骨骼 Humanoid人形骨骼 BlendShapes动画 原文链接 引子 U ...

  5. unity怎么显示骨骼_Unity骨骼动画的总结

    欢迎参与讨论,转载请注明出处. 前言 恰逢假期,在家继续推进Demo,骨骼动画相关的调研算是告一段落了,遂以本文记录相关要点. 首先要明确一点,本文所说的骨骼动画皆是3D模型的骨骼动画,与2D精灵的骨 ...

  6. 利用Unity插件Anima2D创建2D骨骼动画

    利用Unity插件Anima2D创建2D骨骼动画 创建步骤 导入Anima2D插件 准备2D素材 配置2D骨骼 对2D角色应用IK(反向动力学) 利用Animation创建2D人物动画 创建步骤 导入 ...

  7. Unity实现3D模型自动分解拆解动画

    目录 效果:​ 代码: 源工程 Unity实现3D模型自动分解拆解动画 效果: 模型动画结束后位置不对的话可能需要修改原模型轴 代码: using System; using UnityEngine; ...

  8. unity协程实现多个动画连播

    unity协程实现多个动画连播 unity协程实现多个动画连播 协程的理解 协程实现多个动画连播 unity协程实现多个动画连播 协程的理解 协程不是进程,也不是线程,它就是一个函数,一个特殊的函数- ...

  9. [Unity][FairyGUI]场景中龙骨骨骼动画设置播放

    Spine和DragonBones都是FairyGUI可以显示的骨骼动画. DragonBones骨骼动画显示. 首先得在Unity项目中配置 DragonBones的SDK. 在FairyGUI编辑 ...

最新文章

  1. 水题/poj 1852 Ants
  2. python模块 init py_Python模块包中__init__.py文件的作用
  3. HTML/CSS学习笔记03【CSS概述、CSS选择器、CSS属性、CSS案例-注册页面】
  4. 如何用Java编写类似C的Sizeof函数
  5. leetcode486. 预测赢家(动态规划)
  6. 信息学奥赛一本通(1069:乘方计算)
  7. 当网络安全遇上大数据分析(1)
  8. TabLayout+ViewPager+Fragment(内部:TabLayout+ViewPager+ Fragment)需要注意!!
  9. 第6讲 Zend 整合数据库
  10. virtualbox设置了共享文件夹却无权限访问
  11. 输入起止坐标,返回途径网格。
  12. 云服务器进不了超星_超星自动答题搭建本地和云服务器题库(Java版)
  13. 谈谈在创业公司的几点感触
  14. 操作系统-移动操作系统-百科: iOS(苹果公司的移动操作系统)
  15. Adobe无法写入注册表值,请检查权限(错误代码:160)
  16. 线性规划中的人工变量与松弛变量
  17. 基于ensp的ospf-vlink实验
  18. sql语句,执行,实现没有这条数据就新增,如有这条数据就修改
  19. Bruno组件使用对比
  20. python excel模板 生成excel表格_python使用xlwt生成Excel表格

热门文章

  1. SQL创建-----表
  2. SpringBoot结合Quartz实现定时任务
  3. 成都大学计算机学院录取分数线,2016年成都大学艺术类专业录取分数线
  4. 自己在学习的基本java开发电子书(附百度网盘链接)
  5. 嵌入式系统主要应用哪些行业?
  6. Java NIO 基本原理以及三大核心组件
  7. AT指令返回错误代码: CMS errors CME errors 的区别!
  8. MessageQueue
  9. 【Python成长之路】制作口令保险箱GUI版
  10. uniapp中this.$forceUpdate()