private void Update() { if (splitors != null) { bool completed = true; foreach (var splitor in splitors) { splitor.OnUpdate(); if (!splitor.IsComplete) { completed = false; } } if (completed) { int totalCullCnt = 0; int batchCount = 0; foreach (var splitor in splitors) { totalCullCnt += splitor.TotalCount; foreach (var batch in splitor.SplitedBatches) { if (batch.InstanceCount > 0) { ++batchCount; } } } batchRendererGroup = new BatchRendererGroup(this.OnPerformCulling); cullData = new NativeArray <CullData>(totalCullCnt, Allocator.Persistent); cullDataOffset = new NativeArray <int>(batchCount, Allocator.Persistent); int cullOffset = 0; int batchIdx = 0; for (int i = 0; i < splitors.Length; ++i) { var splitor = splitors[i]; if (splitor.TotalCount <= 0) { continue; } var proto = layers[i]; foreach (var batch in splitor.SplitedBatches) { if (batch.InstanceCount > 0) { cullDataOffset[batchIdx] = cullOffset; ++batchIdx; AddBatch(proto.mesh, proto.mat, batch, ref cullOffset); } } } //clear foreach (var splitor in splitors) { splitor.Clear(); } splitors = null; } } }
private void Awake() { Camera camera = null; var data = camera.GetUniversalAdditionalCameraData(); // data.antialiasing = AntialiasingMode.None; // camera.clearFlags = CameraClearFlags.Depth; // camera.RenderWithShader(); // data.clearDepth = false; batchRendererGroup = new BatchRendererGroup(this.OnPerformCulling); cullData = new NativeArray <CullData>(25000, Allocator.Persistent); for (int j = 0; j < 50; j++) { var pos = new float3[50]; var rot = new quaternion[50]; var scale = new float3[50]; for (int i = 0; i < 50; i++) { pos[i] = new float3(i * 2, 0, j * 2); rot[i] = quaternion.identity; scale[i] = new float3(1.0f, 1.0f, 1.0f); } this.AddBatch(100 * j, 50, pos, rot, scale); } }
private void OnDestroy() { if (this._batchRendererGroup != null) { this._batchRendererGroup.Dispose(); this._batchRendererGroup = null; _cullDatas.Dispose(); } }
private void OnEnable() { batchRendererGroup = new BatchRendererGroup(CullingCallback); batchIndexes = new List <int>(10); foreach (var prefab in prefabs) { SetupBatch(prefab); } }
// private void Start() // { // // instance 的buffer 需要关闭 batcher // // ((UniversalRenderPipelineAsset) GraphicsSettings.currentRenderPipeline).useSRPBatcher = false; // } private void OnDestroy() { if (batchRendererGroup != null) { cullingDependency.Complete(); batchRendererGroup.Dispose(); batchRendererGroup = null; cullData.Dispose(); } }
void OnEnable() { _brg = new BatchRendererGroup(CullingCallback); var mesh = ObjectWeGetTheMeshFrom.GetComponent <MeshFilter>().sharedMesh; _batch = _brg.AddBatch(mesh, 0, _material, 0, castShadows: ShadowCastingMode.On, receiveShadows: true, invertCulling: false, bounds: new Bounds(Vector3.one * 10, Vector3.one * 20), instanceCount: 1, customProps: null, associatedSceneObject: null); }
private void Start() { _batchRendererGroup = new BatchRendererGroup(OnPerformCulling); // _cullDatas = new NativeArray<CullData>(50000,Allocator.Persistent); _cullDic = new NativeMultiHashMap <int, CullData>(100, Allocator.Persistent); Stopwatch stopwatch = new Stopwatch(); stopwatch.Restart(); aDD(); Debug.Log("batch render group :" + stopwatch.ElapsedMilliseconds); }
private JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext) { // no culling at all! this just sets up everything as visible. for (int batch = 0; batch < cullingContext.batchVisibility.Length; batch++) { var bv = cullingContext.batchVisibility[batch]; bv.visibleCount = bv.instancesCount; cullingContext.batchVisibility[batch] = bv; for (int instance = 0; instance < bv.instancesCount; instance++) { cullingContext.visibleIndices[bv.offset + instance] = instance; } } return(new JobHandle()); }
// You can safely ignore this. It is just a pass-throught that performs no culling JobHandle CullingCallback(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext) { // dummy culling for (var batchIndex = 0; batchIndex < cullingContext.batchVisibility.Length; ++batchIndex) { var batchVisibility = cullingContext.batchVisibility[batchIndex]; for (var i = 0; i < batchVisibility.instancesCount; ++i) { cullingContext.visibleIndices[batchVisibility.offset + i] = i; } batchVisibility.visibleCount = batchVisibility.instancesCount; cullingContext.batchVisibility[batchIndex] = batchVisibility; } return(default);
private void OnEnable() { _batchRendererGroup = new BatchRendererGroup(CullingCallback); _batchIndex = _batchRendererGroup.AddBatch( mesh, 0, material, 0, ShadowCastingMode.Off, false, false, new Bounds(Vector3.zero, Vector3.one * float.MaxValue), // ここではすごく大きいBoundsを渡しておく InstanceCount, null, gameObject); }
protected override void OnCreate() { _renderGroup = new BatchRendererGroup(OnPerformCulling); _spriteEntities = Entities.WithAllReadOnly <SpriteComponent, LocalToWorld, SharedSpriteMaterialComponent>().ToEntityQuery(); _spriteWithMesh = GetEntityQuery(new EntityQueryDesc { All = new[] { ComponentType.ReadOnly <SharedSpriteMaterialComponent>(), ComponentType.ChunkComponentReadOnly <ChunkSpriteMeshComponent>() }, }); _spriteMissingMesh = GetEntityQuery(new EntityQueryDesc { All = new[] { ComponentType.ReadOnly <SharedSpriteMaterialComponent>() }, None = new[] { ComponentType.ChunkComponent <ChunkSpriteMeshComponent>() } }); }
private JobHandle OnPerformCulling(BatchRendererGroup batchRendererGroup, BatchCullingContext cullingContext) { var planes = Unity.Rendering.FrustumPlanes.BuildSOAPlanePackets(cullingContext.cullingPlanes, Allocator.TempJob); var lodParms = LODGroupExtensions.CalculateLODParams(cullingContext.lodParameters); var cull = new MyCullJob() { Planes = planes, LODParams = lodParms, IndexList = cullingContext.visibleIndices, Batches = cullingContext.batchVisibility, CullDatas = _cullDic, }; // Debug.Log(_cullDic.GetKeyArray(Allocator.Temp).Length); ; var handle = cull.Schedule(batchIndex + 1, 1); return(handle); }
private JobHandle CullingCallback(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext) { var inputDependency = _jobDependency; for (var i = 0; i < cullingContext.batchVisibility.Length; ++i) { var job = new CullingJob { CullingContext = cullingContext, Matrices = rendererGroup.GetBatchMatrices(i), BatchIndex = i }.Schedule(inputDependency); // Jobの依存関係を更新 _jobDependency = JobHandle.CombineDependencies(job, _jobDependency); } return(_jobDependency); }
private JobHandle OnPerformCulling( BatchRendererGroup rendererGroup, BatchCullingContext cullingContext) { var planes = FrustumPlanes.BuildSOAPlanePackets(cullingContext.cullingPlanes, Allocator.TempJob); var lodParams = LODGroupExtensions.CalculateLODParams(cullingContext.lodParameters); var cull = new MyCullJob() { Planes = planes, LODParams = lodParams, IndexList = cullingContext.visibleIndices, Batches = cullingContext.batchVisibility, CullDatas = cullData, }; var handle = cull.Schedule(100, 32, cullingDependency); cullingDependency = JobHandle.CombineDependencies(handle, cullingDependency); return(handle); }
private void Awake() { batchRendererGroup = new BatchRendererGroup(MyOnPerformCulling); cullData = new NativeArray <CullData>(25000, Allocator.Persistent); for (int i = 0; i < 50; i++) { var pos = new float3[50]; var rot = new quaternion[50]; var scale = new float3[50]; for (int j = 0; j < 50; j++) { pos[j] = new float3(j * 2, 0, i * 2); rot[j] = quaternion.identity; //Random.rotation; scale[j] = 1; //Random.value; } AddBatch(100 * i, 50, pos, rot, scale); } }
private JobHandle MyOnPerformCulling(BatchRendererGroup renderergroup, BatchCullingContext cullingContext) { var planes = OtherUtils.BuildSOAPlanePackets(cullingContext.cullingPlanes, Allocator.TempJob); var lodParams = OtherUtils.CalculateLODParams(cullingContext.lodParameters); //传入visibleIndices和batchVisibility 在job里面修改 var cull = new MyCullJob() { planes = planes, lodParams = lodParams, indexList = cullingContext.visibleIndices, batches = cullingContext.batchVisibility, cullDatas = cullData, }; //50组lod0 + 50组lod1 = 100 // cullingDependency 形成队列关系 var handle = cull.Schedule(100, 32, cullingDependency); cullingDependency = JobHandle.CombineDependencies(handle, cullingDependency); return(handle); }
private void Start() { _batchRendererGroup = new BatchRendererGroup(OnPerformCulling); _cullDatas = new NativeArray <CullData>(50000, Allocator.Persistent); Stopwatch stopwatch = new Stopwatch(); stopwatch.Restart(); for (int j = 0; j < arrayLength; j++) { var pos = new float3[3]; var rot = new quaternion[3]; var scale = new float3[3]; for (int i = 0; i < 3; i++) { pos[i] = new float3(i * 5, j * 2, j * 2); rot[i] = quaternion.identity; scale[i] = new float3(1, 1, 1); } AddBatch(3 * j, 3, pos, rot, scale); } Debug.Log("batch render group :" + stopwatch.ElapsedMilliseconds); }
private JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext) { var planes = new NativeArray <float4>(cullingContext.cullingPlanes.Length, Allocator.TempJob); for (int i = 0; i < cullingContext.cullingPlanes.Length; ++i) { var p = cullingContext.cullingPlanes[i]; planes[i] = new float4(p.normal, p.distance); } var cull = new MTDetailCullJob() { Planes = planes, CamerPos = cullingContext.lodParameters.cameraPosition, IndexList = cullingContext.visibleIndices, Batches = cullingContext.batchVisibility, CullDataOffset = cullDataOffset, CullDatas = cullData, }; var handle = cull.Schedule(cullingContext.batchVisibility.Length, 16, cullingDependency); cullingDependency = JobHandle.CombineDependencies(handle, cullingDependency); return(handle); }
/// <summary> /// This is the culling callback. The batch renderer calls this once and you need schedule your culling jobs here. /// The culling context contains an array of visible indices of the total size of all batches. Each batch has an /// offset into that array in the batch visibility struct. To perform the culling, write the number of visible /// objects per batch back into that batch's batch visibility struct and write the indices of all visible objects /// into the array of visible indices. The indices must be the indices of the objects in the batch. /// </summary> JobHandle PerformCulling(BatchRendererGroup renderergroup, BatchCullingContext cullingcontext) { if (m_UseSimdCulling) { // kick-off your SIMD jobs here! return(default);
// Start is called before the first frame update void Start() { m_BatchRendererGroup = new BatchRendererGroup(this.OnPerformCulling, IntPtr.Zero); #if ENABLE_PICKING m_PickingMaterial = LoadMaterialWithHideAndDontSave("Hidden/HDRP/BRGPicking"); m_BatchRendererGroup.SetPickingMaterial(m_PickingMaterial); #endif #if ENABLE_ERROR_LOADING_MATERIALS if (SetFallbackMaterialsOnStart) { m_ErrorMaterial = LoadMaterialWithHideAndDontSave("Hidden/HDRP/MaterialError"); m_BatchRendererGroup.SetErrorMaterial(m_ErrorMaterial); m_LoadingMaterial = LoadMaterialWithHideAndDontSave("Hidden/HDRP/MaterialLoading"); m_BatchRendererGroup.SetLoadingMaterial(m_LoadingMaterial); } #endif // Create a batch... var renderers = FindObjectsOfType <MeshRenderer>(); Debug.Log("Converting " + renderers.Length + " renderers..."); m_renderers = new NativeArray <DrawRenderer>(renderers.Length, Allocator.Persistent); m_batchHash = new NativeHashMap <DrawKey, int>(1024, Allocator.Persistent); m_rangeHash = new NativeHashMap <RangeKey, int>(1024, Allocator.Persistent); m_drawBatches = new NativeList <DrawBatch>(Allocator.Persistent); m_drawRanges = new NativeList <DrawRange>(Allocator.Persistent); // Fill the GPU-persistent scene data ComputeBuffer int bigDataBufferVector4Count = 4 /*zero*/ + 1 /*probes*/ + 1 /*speccube*/ + 7 /*SH*/ + m_renderers.Length * 3 * 2 /*per renderer 4x3 matrix+inverse*/; var vectorBuffer = new NativeArray <Vector4>(bigDataBufferVector4Count, Allocator.Temp); // First 4xfloat4 of ComputeBuffer needed to be zero filled for default property fall back! vectorBuffer[0] = new Vector4(0, 0, 0, 0); vectorBuffer[1] = new Vector4(0, 0, 0, 0); vectorBuffer[2] = new Vector4(0, 0, 0, 0); vectorBuffer[3] = new Vector4(0, 0, 0, 0); var startOffset = 4; // Fill global data (shared between all batches) var probesOcclusionOffset = startOffset; vectorBuffer[probesOcclusionOffset] = new Vector4(1, 1, 1, 1); startOffset++; var specCubeOffset = startOffset; vectorBuffer[specCubeOffset] = ReflectionProbe.defaultTextureHDRDecodeValues; startOffset++; var SHOffset = startOffset; var SH = new SHProperties(RenderSettings.ambientProbe); vectorBuffer[SHOffset + 0] = SH.SHAr; vectorBuffer[SHOffset + 1] = SH.SHAg; vectorBuffer[SHOffset + 2] = SH.SHAb; vectorBuffer[SHOffset + 3] = SH.SHBr; vectorBuffer[SHOffset + 4] = SH.SHBg; vectorBuffer[SHOffset + 5] = SH.SHBb; vectorBuffer[SHOffset + 6] = SH.SHC; startOffset += 7; var localToWorldOffset = startOffset; var worldToLocalOffset = localToWorldOffset + m_renderers.Length * 3; m_instances = new NativeList <DrawInstance>(1024, Allocator.Persistent); for (int i = 0; i < renderers.Length; i++) { var renderer = renderers[i]; m_renderers[i] = new DrawRenderer { bounds = new AABB { Center = new float3(0, 0, 0), Extents = new float3(0, 0, 0) } }; var meshFilter = renderer.gameObject.GetComponent <MeshFilter>(); if (!renderer || !meshFilter || !meshFilter.sharedMesh || renderer.enabled == false) { continue; } // Disable the existing Unity MeshRenderer to avoid double rendering! renderer.enabled = false; /* mat4x3 packed like this: * p1.x, p1.w, p2.z, p3.y, * p1.y, p2.x, p2.w, p3.z, * p1.z, p2.y, p3.x, p3.w, * 0.0, 0.0, 0.0, 1.0 */ var m = renderer.transform.localToWorldMatrix; vectorBuffer[i * 3 + 0 + localToWorldOffset] = new Vector4(m.m00, m.m10, m.m20, m.m01); vectorBuffer[i * 3 + 1 + localToWorldOffset] = new Vector4(m.m11, m.m21, m.m02, m.m12); vectorBuffer[i * 3 + 2 + localToWorldOffset] = new Vector4(m.m22, m.m03, m.m13, m.m23); var mi = renderer.transform.worldToLocalMatrix; vectorBuffer[i * 3 + 0 + worldToLocalOffset] = new Vector4(mi.m00, mi.m10, mi.m20, mi.m01); vectorBuffer[i * 3 + 1 + worldToLocalOffset] = new Vector4(mi.m11, mi.m21, mi.m02, mi.m12); vectorBuffer[i * 3 + 2 + worldToLocalOffset] = new Vector4(mi.m22, mi.m03, mi.m13, mi.m23); // Renderer bounds var transformedBounds = AABB.Transform(m, meshFilter.sharedMesh.bounds.ToAABB()); m_renderers[i] = new DrawRenderer { bounds = transformedBounds }; var mesh = m_BatchRendererGroup.RegisterMesh(meshFilter.sharedMesh); var sharedMaterials = new List <Material>(); renderer.GetSharedMaterials(sharedMaterials); var shadows = renderer.shadowCastingMode; for (int matIndex = 0; matIndex < sharedMaterials.Count; matIndex++) { var material = m_BatchRendererGroup.RegisterMaterial(sharedMaterials[matIndex]); var key = new DrawKey { material = material, meshID = mesh, submeshIndex = (uint)matIndex, shadows = shadows }; #if ENABLE_PICKING key.pickableObjectInstanceID = renderer.gameObject.GetInstanceID(); #endif var drawBatch = new DrawBatch { key = key, instanceCount = 0, instanceOffset = 0 }; m_instances.Add(new DrawInstance { key = key, instanceIndex = i }); int drawBatchIndex; if (m_batchHash.TryGetValue(key, out drawBatchIndex)) { drawBatch = m_drawBatches[drawBatchIndex]; } else { drawBatchIndex = m_drawBatches.Length; m_drawBatches.Add(drawBatch); m_batchHash[key] = drawBatchIndex; // Different renderer settings? -> new range var rangeKey = new RangeKey { shadows = shadows }; var drawRange = new DrawRange { key = rangeKey, drawCount = 0, drawOffset = 0, }; int drawRangeIndex; if (m_rangeHash.TryGetValue(rangeKey, out drawRangeIndex)) { drawRange = m_drawRanges[drawRangeIndex]; } else { drawRangeIndex = m_drawRanges.Length; m_drawRanges.Add(drawRange); m_rangeHash[rangeKey] = drawRangeIndex; } drawRange.drawCount++; m_drawRanges[drawRangeIndex] = drawRange; } drawBatch.instanceCount++; m_drawBatches[drawBatchIndex] = drawBatch; } } m_GPUPersistentInstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Raw, (int)bigDataBufferVector4Count * 16 / 4, 4); m_GPUPersistentInstanceData.SetData(vectorBuffer); Debug.Log("DrawRanges: " + m_drawRanges.Length + ", DrawBatches: " + m_drawBatches.Length + ", Instances: " + m_instances.Length); // Prefix sum to calculate draw offsets for each DrawRange int prefixSum = 0; for (int i = 0; i < m_drawRanges.Length; i++) { var drawRange = m_drawRanges[i]; drawRange.drawOffset = prefixSum; m_drawRanges[i] = drawRange; prefixSum += drawRange.drawCount; } // Generate draw index ranges for each DrawRange m_drawIndices = new NativeArray <int>(m_drawBatches.Length, Allocator.Persistent); var m_internalRangeIndex = new NativeArray <int>(m_drawRanges.Length, Allocator.Temp); for (int i = 0; i < m_drawBatches.Length; i++) { var draw = m_drawBatches[i]; if (m_rangeHash.TryGetValue(new RangeKey { shadows = draw.key.shadows }, out int drawRangeIndex)) { var drawRange = m_drawRanges[drawRangeIndex]; m_drawIndices[drawRange.drawOffset + m_internalRangeIndex[drawRangeIndex]] = i; m_internalRangeIndex[drawRangeIndex]++; } } m_internalRangeIndex.Dispose(); // Prefix sum to calculate instance offsets for each DrawCommand prefixSum = 0; for (int i = 0; i < m_drawBatches.Length; i++) { // DrawIndices remap to get DrawCommands ordered by DrawRange var remappedIndex = m_drawIndices[i]; var drawBatch = m_drawBatches[remappedIndex]; drawBatch.instanceOffset = prefixSum; m_drawBatches[remappedIndex] = drawBatch; prefixSum += drawBatch.instanceCount; } // Generate instance index ranges for each DrawCommand m_instanceIndices = new NativeArray <int>(m_instances.Length, Allocator.Persistent); var m_internalDrawIndex = new NativeArray <int>(m_drawBatches.Length, Allocator.Temp); for (int i = 0; i < m_instances.Length; i++) { var instance = m_instances[i]; if (m_batchHash.TryGetValue(instance.key, out int drawBatchIndex)) { var drawBatch = m_drawBatches[drawBatchIndex]; m_instanceIndices[drawBatch.instanceOffset + m_internalDrawIndex[drawBatchIndex]] = instance.instanceIndex; m_internalDrawIndex[drawBatchIndex]++; } } m_internalDrawIndex.Dispose(); // Bounds ("infinite") UnityEngine.Bounds bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(1048576.0f, 1048576.0f, 1048576.0f)); m_BatchRendererGroup.SetGlobalBounds(bounds); // Batch metadata buffer... // Per instance data int objectToWorldID = Shader.PropertyToID("unity_ObjectToWorld"); int worldToObjectID = Shader.PropertyToID("unity_WorldToObject"); int colorID = Shader.PropertyToID("_BaseColor"); // Global data (should be moved to C++ side) int probesOcclusionID = Shader.PropertyToID("unity_ProbesOcclusion"); int specCubeID = Shader.PropertyToID("unity_SpecCube0_HDR"); int SHArID = Shader.PropertyToID("unity_SHAr"); int SHAgID = Shader.PropertyToID("unity_SHAg"); int SHAbID = Shader.PropertyToID("unity_SHAb"); int SHBrID = Shader.PropertyToID("unity_SHBr"); int SHBgID = Shader.PropertyToID("unity_SHBg"); int SHBbID = Shader.PropertyToID("unity_SHBb"); int SHCID = Shader.PropertyToID("unity_SHC"); var batchMetadata = new NativeArray <MetadataValue>(11, Allocator.Temp); batchMetadata[0] = CreateMetadataValue(objectToWorldID, localToWorldOffset * UnsafeUtility.SizeOf <Vector4>(), true); batchMetadata[1] = CreateMetadataValue(worldToObjectID, worldToLocalOffset * UnsafeUtility.SizeOf <Vector4>(), true); batchMetadata[2] = CreateMetadataValue(probesOcclusionID, probesOcclusionOffset * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[3] = CreateMetadataValue(specCubeID, specCubeOffset * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[4] = CreateMetadataValue(SHArID, (SHOffset + 0) * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[5] = CreateMetadataValue(SHAgID, (SHOffset + 1) * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[6] = CreateMetadataValue(SHAbID, (SHOffset + 2) * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[7] = CreateMetadataValue(SHBrID, (SHOffset + 3) * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[8] = CreateMetadataValue(SHBgID, (SHOffset + 4) * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[9] = CreateMetadataValue(SHBbID, (SHOffset + 5) * UnsafeUtility.SizeOf <Vector4>(), false); batchMetadata[10] = CreateMetadataValue(SHCID, (SHOffset + 6) * UnsafeUtility.SizeOf <Vector4>(), false); // Register batch m_batchID = m_BatchRendererGroup.AddBatch(batchMetadata, m_GPUPersistentInstanceData.bufferHandle); m_initialized = true; }
public JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext, BatchCullingOutput cullingOutput, IntPtr userContext) { if (!m_initialized) { return(new JobHandle()); } #if ENABLE_PICKING bool isPickingCulling = cullingContext.viewType == BatchCullingViewType.Picking; #endif var splitCounts = new NativeArray <int>(cullingContext.cullingSplits.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); for (int i = 0; i < splitCounts.Length; ++i) { var split = cullingContext.cullingSplits[i]; splitCounts[i] = split.cullingPlaneCount; } var planes = FrustumPlanes.BuildSOAPlanePacketsMulti(cullingContext.cullingPlanes, splitCounts, Allocator.TempJob); BatchCullingOutputDrawCommands drawCommands = new BatchCullingOutputDrawCommands(); drawCommands.drawRanges = Malloc <BatchDrawRange>(m_drawRanges.Length); drawCommands.drawCommands = Malloc <BatchDrawCommand>(m_drawBatches.Length * splitCounts.Length * 10); // TODO: Multiplying the DrawCommand count by splitCount*10 is NOT an conservative upper bound. But in practice is enough. Sorting would give us a real conservative bound... drawCommands.visibleInstances = Malloc <int>(m_instanceIndices.Length); #if ENABLE_PICKING drawCommands.drawCommandPickingInstanceIDs = isPickingCulling ? Malloc <int>(m_drawBatches.Length) : null; #endif // Zero init: Culling job sets the values! drawCommands.drawRangeCount = 0; drawCommands.drawCommandCount = 0; drawCommands.visibleInstanceCount = 0; drawCommands.instanceSortingPositions = null; drawCommands.instanceSortingPositionFloatCount = 0; cullingOutput.drawCommands[0] = drawCommands; var visibilityLength = (m_renderers.Length + 7) / 8; var rendererVisibility = new NativeArray <ulong>(visibilityLength, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); var cullingJob = new CullingJob { planes = planes, splitCounts = splitCounts, renderers = m_renderers, rendererVisibility = rendererVisibility }; var drawOutputJob = new DrawCommandOutputJob { batchID = m_batchID, rendererVisibility = rendererVisibility, instanceIndices = m_instanceIndices, drawBatches = m_drawBatches, drawRanges = m_drawRanges, drawIndices = m_drawIndices, drawCommands = cullingOutput.drawCommands }; var jobHandleCulling = cullingJob.Schedule(visibilityLength, 8); var jobHandleOutput = drawOutputJob.Schedule(jobHandleCulling); return(jobHandleOutput); }
// The callback method called by Unity whenever it visibility culls to determine which // objects to draw. This method will output draw commands that describe to Unity what // should be drawn for this BatchRendererGroup. public unsafe JobHandle OnPerformCulling( BatchRendererGroup rendererGroup, BatchCullingContext cullingContext, BatchCullingOutput cullingOutput, IntPtr userContext) { // UnsafeUtility.Malloc() requires an alignment, so use the largest integer type's alignment // which is a reasonable default. int alignment = UnsafeUtility.AlignOf <long>(); // Acquire a pointer to the BatchCullingOutputDrawCommands struct so we can easily // modify it directly. var drawCommands = (BatchCullingOutputDrawCommands *)cullingOutput.drawCommands.GetUnsafePtr(); // Allocate memory for the output arrays. In a more complicated implementation the amount of memory // allocated could be dynamically calculated based on what we determined to be visible. // In this example, we will just assume that all of our instances are visible and allocate // memory for each of them. We need the following allocations: // - a single draw command (which draws kNumInstances instances) // - a single draw range (which covers our single draw command) // - kNumInstances visible instance indices. // The arrays must always be allocated using Allocator.TempJob. drawCommands->drawCommands = (BatchDrawCommand *)UnsafeUtility.Malloc(UnsafeUtility.SizeOf <BatchDrawCommand>(), alignment, Allocator.TempJob); drawCommands->drawRanges = (BatchDrawRange *)UnsafeUtility.Malloc(UnsafeUtility.SizeOf <BatchDrawRange>(), alignment, Allocator.TempJob); drawCommands->visibleInstances = (int *)UnsafeUtility.Malloc(kNumInstances * sizeof(int), alignment, Allocator.TempJob); drawCommands->drawCommandCount = 1; drawCommands->drawRangeCount = 1; drawCommands->visibleInstanceCount = kNumInstances; // Our example does not use depth sorting, so we can leave the instanceSortingPositions as null. drawCommands->instanceSortingPositions = null; drawCommands->instanceSortingPositionFloatCount = 0; // Configure our single draw command to draw kNumInstances instances // starting from offset 0 in the array, using the batch, material and mesh // IDs that we registered in the Start() method. No special flags are set. drawCommands->drawCommands[0].visibleOffset = 0; drawCommands->drawCommands[0].visibleCount = kNumInstances; drawCommands->drawCommands[0].batchID = m_BatchID; drawCommands->drawCommands[0].materialID = m_MaterialID; drawCommands->drawCommands[0].meshID = m_MeshID; drawCommands->drawCommands[0].submeshIndex = 0; drawCommands->drawCommands[0].flags = 0; drawCommands->drawCommands[0].sortingPosition = 0; // Configure our single draw range to cover our single draw command which // is at offset 0. drawCommands->drawRanges[0].drawCommandsBegin = 0; drawCommands->drawRanges[0].drawCommandsCount = 1; // In this example we don't care about shadows or motion vectors, so we leave everything // to the default zero values, except the renderingLayerMask which we have to set to all ones // so the instances will be drawn regardless of mask settings when rendering. drawCommands->drawRanges[0].filterSettings = new BatchFilterSettings { renderingLayerMask = 0xffffffff, }; // Finally, write the actual visible instance indices to their array. In a more complicated // implementation, this output would depend on what we determined to be visible, but in this example // we will just assume that everything is visible. for (int i = 0; i < kNumInstances; ++i) { drawCommands->visibleInstances[i] = i; } // This simple example does not use jobs, so we can just return an empty JobHandle. // Performance sensitive applications are encouraged to use Burst jobs to implement // culling and draw command output, in which case we would return a handle here that // completes when those jobs have finished. return(new JobHandle()); }
// During initialization, we will allocate all required objects, and set up our custom instance data. void Start() { // Create the BatchRendererGroup and register assets m_BRG = new BatchRendererGroup(this.OnPerformCulling, IntPtr.Zero); m_MeshID = m_BRG.RegisterMesh(mesh); m_MaterialID = m_BRG.RegisterMaterial(material); // Create the buffer that holds our instance data m_InstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Raw, (kExtraBytes + kBytesPerInstance * kNumInstances) / sizeof(int), sizeof(int)); // Place one zero matrix at the start of the instance data buffer, so loads from address 0 will return zero var zero = new Matrix4x4[1] { Matrix4x4.zero }; // Create transform matrices for our three example instances var matrices = new Matrix4x4[kNumInstances] { Matrix4x4.Translate(new Vector3(-2, 0, 0)), Matrix4x4.Translate(new Vector3(0, 0, 0)), Matrix4x4.Translate(new Vector3(2, 0, 0)), }; // Convert the transform matrices into the packed format expected by the shader var objectToWorld = new PackedMatrix[kNumInstances] { new PackedMatrix(matrices[0]), new PackedMatrix(matrices[1]), new PackedMatrix(matrices[2]), }; // Also create packed inverse matrices var worldToObject = new PackedMatrix[kNumInstances] { new PackedMatrix(matrices[0].inverse), new PackedMatrix(matrices[1].inverse), new PackedMatrix(matrices[2].inverse), }; // Make all instances have unique colors var colors = new Vector4[kNumInstances] { new Vector4(1, 0, 0, 1), new Vector4(0, 1, 0, 1), new Vector4(0, 0, 1, 1), }; // In this simple example, the instance data is placed into the buffer like this: // Offset | Description // 0 | 64 bytes of zeroes, so loads from address 0 return zeroes // 64 | 32 uninitialized bytes to make working with SetData easier, otherwise unnecessary // 96 | unity_ObjectToWorld, three packed float3x4 matrices // 240 | unity_WorldToObject, three packed float3x4 matrices // 384 | _BaseColor, three float4s // Compute start addresses for the different instanced properties. unity_ObjectToWorld starts // at address 96 instead of 64, because the computeBufferStartIndex parameter of SetData // is expressed as source array elements, so it is easier to work in multiples of sizeof(PackedMatrix). uint byteAddressObjectToWorld = kSizeOfPackedMatrix * 2; uint byteAddressWorldToObject = byteAddressObjectToWorld + kSizeOfPackedMatrix * kNumInstances; uint byteAddressColor = byteAddressWorldToObject + kSizeOfPackedMatrix * kNumInstances; // Upload our instance data to the GraphicsBuffer, from where the shader can load them. m_InstanceData.SetData(zero, 0, 0, 1); m_InstanceData.SetData(objectToWorld, 0, (int)(byteAddressObjectToWorld / kSizeOfPackedMatrix), objectToWorld.Length); m_InstanceData.SetData(worldToObject, 0, (int)(byteAddressWorldToObject / kSizeOfPackedMatrix), worldToObject.Length); m_InstanceData.SetData(colors, 0, (int)(byteAddressColor / kSizeOfFloat4), colors.Length); // Set up metadata values to point to the instance data. Set the most significant bit 0x80000000 in each, // which instructs the shader that the data is an array with one value per instance, indexed by the instance index. // Any metadata values used by the shader and not set here will be zero. When such a value is used with // UNITY_ACCESS_DOTS_INSTANCED_PROP (i.e. without a default), the shader will interpret the // 0x00000000 metadata value so that the value will be loaded from the start of the buffer, which is // where we uploaded the matrix "zero" to, so such loads are guaranteed to return zero, which is a reasonable // default value. var metadata = new NativeArray <MetadataValue>(3, Allocator.Temp); metadata[0] = new MetadataValue { NameID = Shader.PropertyToID("unity_ObjectToWorld"), Value = 0x80000000 | byteAddressObjectToWorld, }; metadata[1] = new MetadataValue { NameID = Shader.PropertyToID("unity_WorldToObject"), Value = 0x80000000 | byteAddressWorldToObject, }; metadata[2] = new MetadataValue { NameID = Shader.PropertyToID("_BaseColor"), Value = 0x80000000 | byteAddressColor, }; // Finally, create a batch for our instances, and make the batch use the GraphicsBuffer with our // instance data, and the metadata values that specify where the properties are. Note that // we do not need to pass any batch size here. m_BatchID = m_BRG.AddBatch(metadata, m_InstanceData.bufferHandle); }
// Start is called before the first frame update void Start() { m_BatchRendererGroup = new BatchRendererGroup(this.OnPerformCulling, IntPtr.Zero); int itemCount = itemGridSize * itemGridSize; m_itemCount = itemCount; // Bounds UnityEngine.Bounds bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(1048576.0f, 1048576.0f, 1048576.0f)); m_BatchRendererGroup.SetGlobalBounds(bounds); // Register mesh and material if (m_mesh) { m_meshID = m_BatchRendererGroup.RegisterMesh(m_mesh); } if (m_material) { m_materialID = m_BatchRendererGroup.RegisterMaterial(m_material); } // Batch metadata buffer int objectToWorldID = Shader.PropertyToID("unity_ObjectToWorld"); int matrixPreviousMID = Shader.PropertyToID("unity_MatrixPreviousM"); int worldToObjectID = Shader.PropertyToID("unity_WorldToObject"); int colorID = Shader.PropertyToID("_BaseColor"); // Generate a grid of objects... int bigDataBufferVector4Count = 4 + itemCount * (3 * 3 + 1); // 4xfloat4 zero + per instance = { 3x mat4x3, 1x float4 color } m_sysmemBuffer = new NativeArray <Vector4>(bigDataBufferVector4Count, Allocator.Persistent, NativeArrayOptions.ClearMemory); m_GPUPersistentInstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Raw, (int)bigDataBufferVector4Count * 16 / 4, 4); // 64 bytes of zeroes, so loads from address 0 return zeroes. This is a BatchRendererGroup convention. int positionOffset = 4; m_sysmemBuffer[0] = new Vector4(0, 0, 0, 0); m_sysmemBuffer[1] = new Vector4(0, 0, 0, 0); m_sysmemBuffer[2] = new Vector4(0, 0, 0, 0); m_sysmemBuffer[3] = new Vector4(0, 0, 0, 0); // Matrices UpdatePositions(m_center); // Colors int colorOffset = positionOffset + itemCount * 3 * 3; for (int i = 0; i < itemCount; i++) { Color col = Color.HSVToRGB(((float)(i) / (float)itemCount) % 1.0f, 1.0f, 1.0f); // write colors right after the 4x3 matrices m_sysmemBuffer[colorOffset + i] = new Vector4(col.r, col.g, col.b, 1.0f); } m_GPUPersistentInstanceData.SetData(m_sysmemBuffer); var batchMetadata = new NativeArray <MetadataValue>(4, Allocator.Temp, NativeArrayOptions.UninitializedMemory); batchMetadata[0] = CreateMetadataValue(objectToWorldID, 64, true); // matrices batchMetadata[1] = CreateMetadataValue(matrixPreviousMID, 64 + itemCount * UnsafeUtility.SizeOf <Vector4>() * 3, true); // previous matrices batchMetadata[2] = CreateMetadataValue(worldToObjectID, 64 + itemCount * UnsafeUtility.SizeOf <Vector4>() * 3 * 2, true); // inverse matrices batchMetadata[3] = CreateMetadataValue(colorID, 64 + itemCount * UnsafeUtility.SizeOf <Vector4>() * 3 * 3, true); // colors // Register batch m_batchID = m_BatchRendererGroup.AddBatch(batchMetadata, m_GPUPersistentInstanceData.bufferHandle); m_initialized = true; }
public JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext, BatchCullingOutput cullingOutput, IntPtr userContext) { if (!m_initialized) { return(new JobHandle()); } BatchCullingOutputDrawCommands drawCommands = new BatchCullingOutputDrawCommands(); drawCommands.drawRangeCount = 1; drawCommands.drawRanges = Malloc <BatchDrawRange>(1); drawCommands.drawRanges[0] = new BatchDrawRange { drawCommandsBegin = 0, drawCommandsCount = 1, filterSettings = new BatchFilterSettings { renderingLayerMask = 1, layer = 0, motionMode = m_motionVectorTest ? MotionVectorGenerationMode.Object : MotionVectorGenerationMode.Camera, shadowCastingMode = ShadowCastingMode.On, receiveShadows = true, staticShadowCaster = false, allDepthSorted = false } }; drawCommands.visibleInstances = Malloc <int>(m_itemCount); int n = 0; int radius = (itemGridSize / 2) * (itemGridSize / 2); // (grid/2)^2 int radiusO = (radius * 90) / 100; int radiusI = (radiusO * 85) / 100; for (int r = 0; r < itemGridSize; r++) { for (int i = 0; i < itemGridSize; i++) { bool visible = true; if (m_cullTest) { int dist = (r - itemGridSize / 2) * (r - itemGridSize / 2) + (i - itemGridSize / 2) * (i - itemGridSize / 2); if ((dist >= radiusI) && (dist <= radiusO)) { visible = false; } } if (visible) { drawCommands.visibleInstances[n++] = r * itemGridSize + i; } } } drawCommands.visibleInstanceCount = n; drawCommands.drawCommandCount = 1; drawCommands.drawCommands = Malloc <BatchDrawCommand>(1); drawCommands.drawCommands[0] = new BatchDrawCommand { visibleOffset = 0, visibleCount = (uint)n, batchID = m_batchID, materialID = m_materialID, meshID = m_meshID, submeshIndex = 0, splitVisibilityMask = 0xff, flags = m_motionVectorTest ? BatchDrawCommandFlags.HasMotion : BatchDrawCommandFlags.None, sortingPosition = 0 }; drawCommands.instanceSortingPositions = null; drawCommands.instanceSortingPositionFloatCount = 0; cullingOutput.drawCommands[0] = drawCommands; return(new JobHandle()); }