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

发布于:2022-11-28 ⋅ 阅读:(754) ⋅ 点赞:(0)

首先说明本人机器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
		}
    }
}
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到