草庐IT

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

ロ畏蓅ㄧ氓 2023-10-23 原文

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

首先说明本人机器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_END
        ENDHLSL

        Pass
        {
            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_CASCADE

            struct 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_instancing
 
			struct 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. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  2. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  3. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  4. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  5. ruby-on-rails - Rails 3.1 中具有相同形式的多个模型? - 2

    我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  8. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  9. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  10. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

随机推荐