環境
Unity2021.2.18f1
burst1.6.5
jobs0.50.0-preview.8
概要
DrawProcedualの実験です。 多数のスキニングモデルを1マテリアルで一括描画してみるテストです。
AnimationInstancingのように1つのモデルを大量に出すのではなく、複数種類のモデルをたくさん出す為のテストです。
面倒だったので使用するスキニングモデルは1スキンモデルのみにしています。
アニメーション毎にボーン行列をGraphicsBufferに保存して、シェーダでスキニング処理をしています。
テクスチャはTexture2DArrayに纏めています。(モデルのテクスチャはベーステクスチャのみで全部同じサイズ)
また、無駄にComputeShaderやJobSystemを使用しています。
左上の数値はFPSです。オブジェクトは全部で1000体出しています。
※モデルはオプティマイズオプションを無効にしないと実機でちゃんと表示されませんでした。
ModelManager.cs
using System; using System.Linq; using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using Unity.Jobs; using Unity.Burst; public class ModelManager: MonoBehaviour { [SerializeField] private ComputeShader _computeShader = null; [SerializeField] private Material _material = null; [SerializeField] private GameObject[] _prefabs = null; private class ModelCreateParam { //Vertices public int vertexOffset = 0; public GraphicsBuffer[] gbVertexStreams; public int vertexCount = 0; //Indices public int indexOffset = 0; public GraphicsBuffer gbIndices; //Textures public int textureOffset = 0; public Texture[] textures = null; //Animations public int animationClipOffset = 0; public int boneCount = 0; public AnimationClipCreateParam[] animationClipCreateParams = null; } private List<ModelCreateParam> _modelCreateParams = new List<ModelCreateParam>(); private struct ModelParam { public int vertexOffset; public int indexOffset; public int textureOffset; public int animationClipOffset; public int boneCount; } private NativeArray<ModelParam> _modelParams; private int _vertexStride = 0; private GraphicsBuffer _gbVertices = null; private class AnimationClipCreateParam { public int nameHash = 0; public int frameCount = 0; public int boneMatrixOffset = 0; public Matrix4x4[] boneMatrices = null; } private struct AnimationClipParam { public int nameHash; public int frameCount; public int boneMatrixOffset; } NativeArray<AnimationClipParam> _animationClipParams; private GraphicsBuffer _gbBoneMatrices = null; private class TextureParam { public int spTextureId; public Texture2DArray textureArray = null; } private static TextureParam[] _textureParams = { new TextureParam(){spTextureId = Shader.PropertyToID("_MainTex") }, }; private static int _spSrcVertices = Shader.PropertyToID("SrcVertices"); private static int _spSrcVertexCount = Shader.PropertyToID("SrcVertexCount"); private static int _spSrcStride = Shader.PropertyToID("SrcStride"); private static int _spDestVertices = Shader.PropertyToID("DestVertices"); private static int _spDestStride = Shader.PropertyToID("DestStride"); private static int _spDestOffsetVertex = Shader.PropertyToID("DestOffsetVertex"); private static int _spDestOffsetInStride = Shader.PropertyToID("DestOffsetInStride"); private static int _spDestIndices = Shader.PropertyToID("DestIndices"); private static int _spDestOffsetIndex = Shader.PropertyToID("DestOffsetIndex"); private static int _spSrcIndexCount = Shader.PropertyToID("SrcIndexCount"); private static int _spSrcUnitIndex = Shader.PropertyToID("SrcUnitIndex"); private static int _spSrcVertexOffset = Shader.PropertyToID("SrcVertexOffset"); private static int _spSrcIndices = Shader.PropertyToID("SrcIndices"); private static int _spVerticesId = Shader.PropertyToID("_Vertices"); private static int _spBoneMatricesId = Shader.PropertyToID("_BoneMatrices"); private static int _spUnitDescsId = Shader.PropertyToID("_UnitDescs"); private static int _spUnitUpdateDescsId = Shader.PropertyToID("_UnitUpdateDescs"); private bool _modelCreateParamDirty = false; [BurstCompile(CompileSynchronously = true)] private struct UnitParam { public int modelId; public int animationId; public Vector3 position; public Quaternion rotation; public Vector3 scale; public float animationTime; private enum State { Wait, Walk, } private State state; private Vector3 basePosition; private float waitTime; private Vector3 destPosition; private Unity.Mathematics.Random random; public void Setup() { random = new Unity.Mathematics.Random((uint)UnityEngine.Random.Range(1, 65535)); basePosition = position; state = State.Wait; waitTime = UnityEngine.Random.Range(0.0f, 3.0f); } [BurstCompile(CompileSynchronously = true)] public unsafe void Update(float deltaTime) { if(state == State.Wait) { if(waitTime > 0) { waitTime -= deltaTime; return; } state = State.Walk; var q = Quaternion.Euler(0.0f, random.NextFloat(0, 360), 0.0f); var v = q * new Vector3(0, 0, 10); destPosition = basePosition + v; } else if(state == State.Walk) { var v = destPosition - position; const float minDist = 0.5f; if(v.sqrMagnitude < minDist * minDist) { state = State.Wait; waitTime = 1.0f; return; } var destRot = Quaternion.LookRotation(v); const float rotSpeed = 3.0f; rotation = Quaternion.SlerpUnclamped(rotation, destRot, rotSpeed * deltaTime); var forward = rotation * Vector3.forward; const float moveSpeed = 1.0f; position += forward * moveSpeed * deltaTime; } } } private NativeList<UnitParam> _units; [BurstCompile(CompileSynchronously = true)] struct JobUnits : IJobParallelFor { public NativeArray<UnitParam> units; public NativeArray<UnitUpdateDesc> unitUpdateDescs; [ReadOnly] public NativeArray<ModelParam> modelParams; [ReadOnly] public NativeArray<AnimationClipParam> animationClipParams; [ReadOnly] public float deltaTime; public unsafe void Execute(int index) { var pUnits = (UnitParam*)NativeArrayUnsafeUtility.GetUnsafePtr(units); var pUnit = &pUnits[index]; pUnit->Update(deltaTime); UpdateUnitDesc(pUnit, index); } private unsafe void UpdateUnitDesc(UnitParam* pUnit, int index) { var pUnitUpdateDesc = (UnitUpdateDesc*)NativeArrayUnsafeUtility.GetUnsafePtr(unitUpdateDescs); var modelParam = modelParams[pUnit->modelId]; var animParam = animationClipParams[modelParam.animationClipOffset + pUnit->animationId]; var animFrame = (int)(pUnit->animationTime * _animationFrameRate); pUnit->animationTime = Mathf.Repeat(pUnit->animationTime + deltaTime, (float)animParam.frameCount / (float)_animationFrameRate); pUnitUpdateDesc[index].boneMatrixId = animParam.boneMatrixOffset + animFrame * modelParam.boneCount; pUnitUpdateDesc[index].worldMatrix = Matrix4x4.TRS(pUnit->position, pUnit->rotation, pUnit->scale); } } private JobHandle _jhUnits; private bool _unitDirty = false; private GraphicsBuffer _gbIndices = null; private GraphicsBuffer _gbArgs = null; private struct UnitDesc { public int modelId;//textureId } private GraphicsBuffer _gbUnitDescs = null; private struct UnitUpdateDesc { public Matrix4x4 worldMatrix; public int boneMatrixId; } private NativeArray<UnitUpdateDesc> _unitUpdateDescs; private GraphicsBuffer _gbUnitUpdateDescs = null; private Bounds _bounds; private const int _animationFrameRate = 30; private void Start() { for(int i = 0; i < _prefabs.Length; i++) { CreateModelCreateParam(_prefabs[i]); } CreateTextureArrays(); _units = new NativeList<UnitParam>(Allocator.Persistent); for(int i = 0; i < 1000; i++) { var x = (i % 25 - 12) * 2.0f; var z = (i / 25 - 12) * 2.0f; var modelId = i % _modelCreateParams.Count; var animationId = Animator.StringToHash("idle"); CreateUnit(modelId, animationId, new Vector3(x, 0.0f, z), Quaternion.identity, Vector3.one); } _bounds = new Bounds(Vector3.zero, new Vector3(1000.0f, 1000.0f, 1000.0f));// mesh.bounds; } private void OnDestroy() { _jhUnits.Complete(); for(int i = 0; i < _modelCreateParams.Count; i++) { DestroyModelCreateParam(_modelCreateParams[i]); } if(_gbVertices != null) _gbVertices.Dispose(); if(_gbIndices != null) _gbIndices.Dispose(); if(_gbArgs != null) _gbArgs.Dispose(); if(_gbBoneMatrices != null) _gbBoneMatrices.Dispose(); if(_gbUnitDescs != null) _gbUnitDescs.Dispose(); if(_gbUnitUpdateDescs != null) _gbUnitUpdateDescs.Dispose(); if(_unitUpdateDescs.IsCreated == true) _unitUpdateDescs.Dispose(); if(_units.IsCreated == true) _units.Dispose(); if(_modelParams.IsCreated == true) _modelParams.Dispose(); if(_animationClipParams.IsCreated == true) _animationClipParams.Dispose(); } private unsafe void CreateModelCreateParam(GameObject prefab) { _modelCreateParamDirty = true; var vertexOffset = 0; var indexOffset = 0; var animationClipOffset = 0; if(_modelCreateParams.Count > 0) { var lastModelCreateParam = _modelCreateParams[_modelCreateParams.Count - 1]; vertexOffset = lastModelCreateParam.vertexOffset + lastModelCreateParam.vertexCount; indexOffset = lastModelCreateParam.indexOffset + lastModelCreateParam.gbIndices.count; animationClipOffset = lastModelCreateParam.animationClipOffset + lastModelCreateParam.animationClipCreateParams.Length; } Mesh mesh; Renderer renderer; var skinnedMeshRenrerer = prefab.GetComponentInChildren<SkinnedMeshRenderer>(); if(skinnedMeshRenrerer != null) { mesh = skinnedMeshRenrerer.sharedMesh; renderer = skinnedMeshRenrerer; } else { var meshFilter = prefab.GetComponentInChildren<MeshFilter>(); mesh = meshFilter.sharedMesh; renderer = meshFilter.GetComponent<Renderer>(); } var vertexStreamNum = 0; var attrs = mesh.GetVertexAttributes(); vertexStreamNum = attrs[attrs.Length - 1].stream + 1; var gbVertexStreams = new GraphicsBuffer[vertexStreamNum]; for(int i = 0; i < vertexStreamNum; i++) { gbVertexStreams[i] = mesh.GetVertexBuffer(i); if((gbVertexStreams[i].target & GraphicsBuffer.Target.Raw) == 0) { var count = gbVertexStreams[i].count; var stride = gbVertexStreams[i].stride; var temp = new byte[count, stride]; gbVertexStreams[i].GetData(temp); gbVertexStreams[i].Dispose(); gbVertexStreams[i] = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, count, stride); gbVertexStreams[i].SetData(temp); } } var vertexCount = gbVertexStreams[0].count; var vertexStride = 0; for(int i = 0; i < vertexStreamNum; i++) vertexStride += gbVertexStreams[i].stride; if(_vertexStride == 0) _vertexStride = vertexStride; if(_vertexStride != vertexStride) Debug.LogError($"_vertexStride != vertexStride prefab:{prefab.name}"); var gbIndices = mesh.GetIndexBuffer(); var sourceIndices = new UInt16[gbIndices.count]; gbIndices.GetData(sourceIndices); fixed(UInt16* pSrcIndices = sourceIndices) using(var destIndices = new NativeArray<UInt32>(gbIndices.count, Allocator.Temp)) { var pDestIndices = (UInt32*)NativeArrayUnsafeUtility.GetUnsafePtr(destIndices); #if true UnsafeUtility.MemCpyStride(pDestIndices, sizeof(UInt32), pSrcIndices, sizeof(UInt16), sizeof(UInt16), gbIndices.count); #else for(int i = 0; i< sourceIndices.Length; i++) pDestIndices[i] = /*(UInt32)vertexOffset + */(UInt32)pSrcIndices[i]; #endif gbIndices.Dispose(); gbIndices = new GraphicsBuffer(GraphicsBuffer.Target.Index | GraphicsBuffer.Target.Raw, destIndices.Length, sizeof(UInt32)); gbIndices.SetData(destIndices); } var material = renderer.sharedMaterial; var textures = new Texture[_textureParams.Length]; for(int i = 0; i < _textureParams.Length; i++) { var textureParam = _textureParams[i]; textures[i] = material.GetTexture(textureParam.spTextureId); } var modelCreateParam = new ModelCreateParam(); modelCreateParam.vertexOffset = vertexOffset; modelCreateParam.gbVertexStreams = gbVertexStreams; modelCreateParam.vertexCount = vertexCount; modelCreateParam.indexOffset = indexOffset; modelCreateParam.gbIndices = gbIndices; modelCreateParam.textureOffset = _modelCreateParams.Count - 1; modelCreateParam.textures = textures; modelCreateParam.animationClipOffset = animationClipOffset; modelCreateParam.boneCount = skinnedMeshRenrerer.bones.Length; if(skinnedMeshRenrerer != null) CreateAnimations(modelCreateParam, prefab, skinnedMeshRenrerer); _modelCreateParams.Add(modelCreateParam); } private void CreateAnimations(ModelCreateParam ModelCreateParam, GameObject prefab, SkinnedMeshRenderer skinnedMeshRenderer) { var boneMatrixOffset = 0; if(_modelCreateParams.Count > 0) { var lastModelCreateParam = _modelCreateParams[_modelCreateParams.Count - 1]; var lastAnimationParam = lastModelCreateParam.animationClipCreateParams[lastModelCreateParam.animationClipCreateParams.Length - 1]; boneMatrixOffset = lastAnimationParam.boneMatrixOffset + lastAnimationParam.boneMatrices.Length; } var animParams = new List<AnimationClipCreateParam>(); var animator = prefab.GetComponentInChildren<Animator>(); var animClips = animator.runtimeAnimatorController.animationClips; ModelCreateParam.animationClipCreateParams = new AnimationClipCreateParam[animClips.Length]; var prevPrefabPos = prefab.transform.localPosition; var prevPrefabRot = prefab.transform.localRotation; var prevPrefabScale = prefab.transform.localScale; prefab.transform.position = Vector3.zero; prefab.transform.rotation = Quaternion.identity; for(int i = 0; i < animClips.Length; i++) { var animParam = new AnimationClipCreateParam(); var animClip = animClips[i]; animParam.nameHash = Animator.StringToHash(animClip.name.ToLower()); animParam.boneMatrixOffset = boneMatrixOffset; animParam.frameCount = (int)(animClip.length * _animationFrameRate); animParam.boneMatrices = new Matrix4x4[animParam.frameCount * skinnedMeshRenderer.bones.Length]; var matrixIndex = 0; for(int frame = 0; frame < animParam.frameCount; frame++) { animClip.SampleAnimation(prefab, (float)frame / _animationFrameRate); for(int boneIndex = 0; boneIndex < skinnedMeshRenderer.bones.Length; boneIndex++) { var bone = skinnedMeshRenderer.bones[boneIndex]; animParam.boneMatrices[matrixIndex] = bone.localToWorldMatrix * skinnedMeshRenderer.sharedMesh.bindposes[boneIndex]; matrixIndex++; } } boneMatrixOffset += animParam.boneMatrices.Length; ModelCreateParam.animationClipCreateParams[i] = animParam; } prefab.transform.localPosition = prevPrefabPos; prefab.transform.localRotation = prevPrefabRot; prefab.transform.localScale = prevPrefabScale; animClips[0].SampleAnimation(prefab, (float)0.0f); } private void DestroyModelCreateParam(ModelCreateParam ModelCreateParam) { for(int i = 0; i < ModelCreateParam.gbVertexStreams.Length; i++) { if(ModelCreateParam.gbVertexStreams[i] != null) ModelCreateParam.gbVertexStreams[i].Dispose(); } if(ModelCreateParam.gbIndices != null) ModelCreateParam.gbIndices.Dispose(); } private void CreateTextureArrays() { if(_modelCreateParamDirty == false) return; var baseModelCreateParam = _modelCreateParams[0]; for(int i = 0; i < _textureParams.Length; i++) { var textureParam = _textureParams[i]; var baseTexture = baseModelCreateParam.textures[i] as Texture2D; textureParam.textureArray = new Texture2DArray(baseTexture.width, baseTexture.height, _modelCreateParams.Count, baseTexture.format, baseTexture.mipmapCount, false); for(int j = 0; j < _modelCreateParams.Count; j++) { var modelCreateParam = _modelCreateParams[j]; var srcTex = modelCreateParam.textures[i] as Texture2D; for(int m = 0; m < srcTex.mipmapCount; m++) { var pixelData = srcTex.GetPixelData<Color32>(m); textureParam.textureArray.SetPixelData<Color32>(pixelData, m, j); } } textureParam.textureArray.Apply(updateMipmaps: false); } } public int CreateUnit(int modelId, int animationNameToHash, Vector3 position, Quaternion rotation, Vector3 scale) { var unit = new UnitParam(); unit.modelId = modelId; unit.animationId = _modelCreateParams[unit.modelId].animationClipCreateParams.Select((x,i) => (x, i )).Where(y => y.x.nameHash == animationNameToHash).Select(z => z.i).FirstOrDefault(); unit.position = position; unit.rotation = rotation; unit.scale = scale; unit.animationTime = 0.0f; unit.Setup(); _units.Add(unit); _unitDirty = true; return _units.Length - 1; } private unsafe void LateUpdate() { if(_unitUpdateDescs.IsCreated == true) { _jhUnits.Complete(); _gbUnitUpdateDescs.SetData(_unitUpdateDescs); if(_gbArgs != null) Graphics.DrawProceduralIndirect(_material, _bounds, MeshTopology.Triangles, _gbIndices, _gbArgs); } var unitCount = _units.Length; if(_modelCreateParamDirty == true) { _modelCreateParamDirty = false; //Vertices if(_gbVertices != null) _gbVertices.Dispose(); var lastModelCreateParam = _modelCreateParams[_modelCreateParams.Count - 1]; var vertexCount = lastModelCreateParam.vertexOffset + lastModelCreateParam.vertexCount; _gbVertices = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, (int)vertexCount, _vertexStride); var kernelIndex = _computeShader.FindKernel("GenerateVertices"); uint numThreadX, numThreadY, numThreadZ; _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ); _computeShader.SetBuffer(kernelIndex, _spDestVertices, _gbVertices); _computeShader.SetInt(_spDestStride, _vertexStride); for(int i = 0; i < _modelCreateParams.Count; i++) { var modelCreateParam = _modelCreateParams[i]; _computeShader.SetInt(_spDestOffsetVertex, modelCreateParam.vertexOffset * _vertexStride); _computeShader.SetInt(_spSrcVertexCount, modelCreateParam.vertexCount); var groupLoopCount = (modelCreateParam.vertexCount + (int)(numThreadY - 1)) / (int)numThreadY; var destOffsetInStride = 0; for(int j = 0; j < modelCreateParam.gbVertexStreams.Length; j++) { var srcStride = modelCreateParam.gbVertexStreams[j].stride; _computeShader.SetBuffer(kernelIndex, _spSrcVertices, modelCreateParam.gbVertexStreams[j]); _computeShader.SetInt(_spSrcStride, srcStride); _computeShader.SetInt(_spDestOffsetInStride, destOffsetInStride); _computeShader.Dispatch(kernelIndex, srcStride / 4, groupLoopCount, 1); destOffsetInStride += srcStride; } } _material.SetBuffer(_spVerticesId, _gbVertices); //ModelParams if(_modelParams.IsCreated == true) _modelParams.Dispose(); _modelParams = new NativeArray<ModelParam>(_modelCreateParams.Count, Allocator.Persistent); var pModelParams = (ModelParam*)NativeArrayUnsafeUtility.GetUnsafePtr(_modelParams); for(int i = 0; i < _modelCreateParams.Count; i++) { var modelCreateParam = _modelCreateParams[i]; pModelParams[i].vertexOffset = modelCreateParam.vertexOffset; pModelParams[i].indexOffset = modelCreateParam.indexOffset; pModelParams[i].textureOffset = modelCreateParam.textureOffset; pModelParams[i].animationClipOffset = modelCreateParam.animationClipOffset; pModelParams[i].boneCount = modelCreateParam.boneCount; } //TextureArray for(int i = 0; i < _textureParams.Length; i++) { var textureParam = _textureParams[i]; _material.SetTexture(textureParam.spTextureId, textureParam.textureArray); } // Animations if(_gbBoneMatrices != null) _gbBoneMatrices.Dispose(); var lastAnimationClipCreateParam = lastModelCreateParam.animationClipCreateParams[lastModelCreateParam.animationClipCreateParams.Length - 1]; var boneMatrixCount = lastAnimationClipCreateParam.boneMatrixOffset + lastAnimationClipCreateParam.boneMatrices.Length; _gbBoneMatrices = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, (int)boneMatrixCount, sizeof(Matrix4x4)); for(int i = 0; i < _modelCreateParams.Count; i++) { var modelCreateParam = _modelCreateParams[i]; for(int j = 0; j < modelCreateParam.animationClipCreateParams.Length; j++) { var animParam = modelCreateParam.animationClipCreateParams[j]; _gbBoneMatrices.SetData(animParam.boneMatrices, 0, animParam.boneMatrixOffset, animParam.boneMatrices.Length); } } _material.SetBuffer(_spBoneMatricesId, _gbBoneMatrices); var animCliipParamCount = lastModelCreateParam.animationClipOffset + lastModelCreateParam.animationClipCreateParams.Length; if(_animationClipParams.IsCreated == true) _animationClipParams.Dispose(); _animationClipParams = new NativeArray<AnimationClipParam>(animCliipParamCount, Allocator.Persistent); var pAnimationClipParams = (AnimationClipParam*)NativeArrayUnsafeUtility.GetUnsafePtr(_animationClipParams); var index = 0; for(int i = 0; i < _modelCreateParams.Count; i++) { var modelCreateParam = _modelCreateParams[i]; for(int j = 0; j < modelCreateParam.animationClipCreateParams.Length; j++) { var animParam = modelCreateParam.animationClipCreateParams[j]; pAnimationClipParams[index].boneMatrixOffset = animParam.boneMatrixOffset; pAnimationClipParams[index].frameCount = animParam.frameCount; pAnimationClipParams[index].nameHash = animParam.nameHash; index++; } } } if(_unitDirty == true) { _unitDirty = false; //Indices if(_gbIndices != null) _gbIndices.Dispose(); var indexCount = 0; for(int i = 0; i < unitCount; i++) { var modelCreateParam = _modelCreateParams[_units[i].modelId]; indexCount += modelCreateParam.gbIndices.count; } _gbIndices = new GraphicsBuffer(GraphicsBuffer.Target.Index | GraphicsBuffer.Target.Raw, indexCount, sizeof(UInt32)); var indexOffset = 0; var kernelIndex = _computeShader.FindKernel("GenerateIndices"); uint numThreadX, numThreadY, numThreadZ; _computeShader.GetKernelThreadGroupSizes(kernelIndex, out numThreadX, out numThreadY, out numThreadZ); _computeShader.SetBuffer(kernelIndex, _spDestIndices, _gbIndices); for(int i = 0; i < unitCount; i++) { var modelCreateParam = _modelCreateParams[_units[i].modelId]; _computeShader.SetInt(_spDestOffsetIndex, indexOffset * sizeof(UInt32)); _computeShader.SetInt(_spSrcIndexCount, modelCreateParam.gbIndices.count); _computeShader.SetInt(_spSrcUnitIndex, i); _computeShader.SetInt(_spSrcVertexOffset, modelCreateParam.vertexOffset); _computeShader.SetBuffer(kernelIndex, _spSrcIndices, modelCreateParam.gbIndices); var groupLoopCount = (modelCreateParam.gbIndices.count + (int)(numThreadX - 1)) / (int)numThreadX; _computeShader.Dispatch(kernelIndex, groupLoopCount, 1, 1); indexOffset += modelCreateParam.gbIndices.count; } //IndirecctArgument var argInstanceNum = 1; var argIndexOffset = 0; var argVertexOffset = 0; var argInstanceOffset = 0; //zero only? var args = new int[]{_gbIndices.count, argInstanceNum, argIndexOffset, argVertexOffset, argInstanceOffset}; var stride = Buffer.ByteLength(args); if(_gbArgs != null) _gbArgs.Dispose(); _gbArgs = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, stride); _gbArgs.SetData(args); //UnitDesc if(_gbUnitDescs != null) _gbUnitDescs.Dispose(); _gbUnitDescs = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, unitCount, sizeof(UnitDesc)); using(var unitDescs = new NativeArray<UnitDesc>(unitCount, Allocator.Temp)) { var pUnitDesc = (UnitDesc*)NativeArrayUnsafeUtility.GetUnsafePtr(unitDescs); for(int i = 0; i < unitCount; i++) { var unit = _units[i]; pUnitDesc[i].modelId = unit.modelId; } _gbUnitDescs.SetData(unitDescs); } _material.SetBuffer(_spUnitDescsId, _gbUnitDescs); //UnitUpdateDesc if(_gbUnitUpdateDescs != null) _gbUnitUpdateDescs.Dispose(); _gbUnitUpdateDescs = new GraphicsBuffer(GraphicsBuffer.Target.Vertex | GraphicsBuffer.Target.Raw, unitCount, sizeof(UnitUpdateDesc)); if(_unitUpdateDescs.IsCreated == true) _unitUpdateDescs.Dispose(); _unitUpdateDescs = new NativeArray<UnitUpdateDesc>(unitCount, Allocator.Persistent); _material.SetBuffer(_spUnitUpdateDescsId, _gbUnitUpdateDescs); } var job = new JobUnits() { units = _units.AsDeferredJobArray(), unitUpdateDescs = _unitUpdateDescs, modelParams = _modelParams, animationClipParams = _animationClipParams, deltaTime = Time.deltaTime, }; _jhUnits = job.Schedule(_units.Length, 1); JobHandle.ScheduleBatchedJobs(); } }
TestDrawProcedual.compute
#pragma kernel GenerateVertices ByteAddressBuffer SrcVertices; uint SrcVertexCount; int SrcStride; RWByteAddressBuffer DestVertices; int DestStride; int DestOffsetVertex; int DestOffsetInStride; [numthreads(1,32,1)] void GenerateVertices(uint2 id : SV_DispatchThreadID) { if (id.y >= SrcVertexCount) return; int srcOffsetVertex = id.y * SrcStride; int destOffsetVertex = DestOffsetVertex + id.y * DestStride; int offsetInStride = id.x * 4; int destOffsetInStride = DestOffsetInStride + offsetInStride; int value = SrcVertices.Load(srcOffsetVertex + offsetInStride); DestVertices.Store(destOffsetVertex + destOffsetInStride, value); } #pragma kernel GenerateIndices ByteAddressBuffer SrcIndices; uint SrcIndexCount; uint SrcUnitIndex; uint SrcVertexOffset; RWByteAddressBuffer DestIndices; int DestOffsetIndex; [numthreads(128, 1, 1)] void GenerateIndices(uint id : SV_DispatchThreadID) { if (id.x >= SrcIndexCount) return; int destOffsetIndex = DestOffsetIndex + id.x * 4; int srcOffsetIndex = id.x * 4; uint value = SrcIndices.Load(srcOffsetIndex); value = (SrcUnitIndex << 22) | ((SrcVertexOffset + value) & 0x3fffff); DestIndices.Store(destOffsetIndex, value); }
TestDrawProcedual.shader
Shader "Test/TestDrawProcedual" { Properties { _MainTex("Tex", 2DArray) = "" {} } CGINCLUDE #include "UnityCG.cginc" #include "AutoLight.cginc" #pragma require 2darray struct appdata { uint vertexId : SV_VertexID; uint instanceId : SV_InstanceID; }; struct VertexAndNormal { float4 vertex; float3 normal; }; struct VertexStream { float3 position; float3 normal; float4 tangent; float2 uv; float4 blendWeight; int4 blendIndices; }; StructuredBuffer<VertexStream> _Vertices; StructuredBuffer<float4x4> _BoneMatrices; struct UnitDesc { int modelId; }; StructuredBuffer<UnitDesc> _UnitDescs; struct UnitUpdateDesc { float4x4 worldMatrix; int boneMatrixId; }; StructuredBuffer<UnitUpdateDesc> _UnitUpdateDescs; UNITY_DECLARE_TEX2DARRAY(_MainTex); void GetBoneMatrices(VertexStream vertex, UnitUpdateDesc unitUdapteDesc, out float4x4 boneMatrix0, out float4x4 boneMatrix1, out float4x4 boneMatrix2, out float4x4 boneMatrix3) { int animMatrixId = unitUdapteDesc.boneMatrixId; boneMatrix0 = _BoneMatrices[animMatrixId + vertex.blendIndices.x]; boneMatrix1 = _BoneMatrices[animMatrixId + vertex.blendIndices.y]; boneMatrix2 = _BoneMatrices[animMatrixId + vertex.blendIndices.z]; boneMatrix3 = _BoneMatrices[animMatrixId + vertex.blendIndices.w]; } void ComputeBone(VertexStream vertex, UnitUpdateDesc unitUdapteDesc, out float4 pos, out float3 normal) { float4x4 boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3; GetBoneMatrices(vertex, unitUdapteDesc, boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3); pos = float4(vertex.position, 1.0); pos = mul(boneMatrix0, pos) * vertex.blendWeight.x + mul(boneMatrix1, pos) * vertex.blendWeight.y + mul(boneMatrix2, pos) * vertex.blendWeight.z + mul(boneMatrix3, pos) * vertex.blendWeight.w; normal = mul(boneMatrix0, vertex.normal) * vertex.blendWeight.x + mul(boneMatrix1, vertex.normal) * vertex.blendWeight.y + mul(boneMatrix2, vertex.normal) * vertex.blendWeight.z + mul(boneMatrix3, vertex.normal) * vertex.blendWeight.w; } void ComputeBone(VertexStream vertex, UnitUpdateDesc unitUdapteDesc, out float4 pos) { float4x4 boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3; GetBoneMatrices(vertex, unitUdapteDesc, boneMatrix0, boneMatrix1, boneMatrix2, boneMatrix3); pos = float4(vertex.position, 1.0); pos = mul(boneMatrix0, pos) * vertex.blendWeight.x + mul(boneMatrix1, pos) * vertex.blendWeight.y + mul(boneMatrix2, pos) * vertex.blendWeight.z + mul(boneMatrix3, pos) * vertex.blendWeight.w; } ENDCG SubShader { Tags{ "LightMode" = "ForwardBase" "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma target 5.0 #pragma multi_compile_fwdbase #pragma vertex vert #pragma fragment frag struct v2f { float4 pos : SV_POSITION; float3 uv : TEXCOORD0; SHADOW_COORDS(1) }; v2f vert(appdata i) { v2f o; uint descId = (i.vertexId >> (22)) & 0x3ff; uint vertexId = i.vertexId & 0x3fffff; UnitDesc unitDesc = _UnitDescs[descId]; VertexStream vertex = _Vertices[vertexId]; UnitUpdateDesc unitUdapteDesc = _UnitUpdateDescs[descId]; VertexAndNormal v; ComputeBone(vertex, unitUdapteDesc, v.vertex, v.normal); unity_ObjectToWorld = unitUdapteDesc.worldMatrix; o.pos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, v.vertex)); o.uv = float3(vertex.uv, unitDesc.modelId); TRANSFER_SHADOW(o) return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col = UNITY_SAMPLE_TEX2DARRAY(_MainTex, i.uv); fixed shadow = max(SHADOW_ATTENUATION(i), 0.5); return col * shadow; } ENDCG } Pass { Name "ShadowCaster" Tags{"LightMode" = "ShadowCaster"} Zwrite On ZTest LEqual CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #pragma target 5.0 struct v2f { float3 uv : TEXCOORD0; V2F_SHADOW_CASTER; }; inline float3 CustomObjectToWorldDir(in float3 dir, in float3x3 worldMatrix) { return normalize(mul(worldMatrix, dir)); } inline float4 CustomClipSpaceShadowCasterPos(in float4 vertex, in float3 normal, in float4x4 worldMatrix) { float4 wPos = mul(worldMatrix, vertex); if (unity_LightShadowBias.z != 0.0) { float3 wNormal = CustomObjectToWorldDir(normal, (float3x3)worldMatrix); float3 wLight = normalize(CustomObjectToWorldDir(wPos.xyz, (float3x3)worldMatrix)); float shadowCos = dot(wNormal, wLight); float shadowSine = sqrt(1 - shadowCos * shadowCos); float normalBias = unity_LightShadowBias.z * shadowSine; wPos.xyz -= wNormal * normalBias; } return mul(UNITY_MATRIX_VP, wPos); } v2f vert(appdata i) { v2f o; uint descId = (i.vertexId >> (22)) & 0x3ff; uint vertexId = i.vertexId & 0x3fffff; UnitDesc unitDesc = _UnitDescs[descId]; VertexStream vertex = _Vertices[vertexId]; UnitUpdateDesc unitUdapteDesc = _UnitUpdateDescs[descId]; VertexAndNormal v; ComputeBone(vertex, unitUdapteDesc, v.vertex, v.normal); unity_ObjectToWorld = unitUdapteDesc.worldMatrix; o.uv = float3(vertex.uv, unitDesc.modelId); TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col = UNITY_SAMPLE_TEX2DARRAY(_MainTex, i.uv); SHADOW_CASTER_FRAGMENT(i) } ENDCG } } }