unity 一万个量具有相同动画的物体渲染
首先说明本人机器CPU是 i5-7400,GPU是GTX 1060 3G。模型三角面大约2K,分辨率1080P。

渲染阴影大约用时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();
});

物体具有动画和Skinned Mesh,而且mesh不只有一个。物体可以投射阴影和接受阴影。思想如下:
链接: SkinMesh合并
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;
}
}
这里主要使用skinnedMeshRenderers.BakeMesh的方法烘培一个Mesh。
然后用Graphics.DrawMesh绘制大量Mesh。
// 渲染结构体
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 "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
}
}
}
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这
我有一个这样的哈希数组:[{: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
我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里编辑 3.解析依赖到项目中
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u