Instanced MeshObjectNode
#1
Hi,

I have a question about how the RenderableMeshPrimitives are created. I have following setup:
1. I create a DXMesh (let's say SimpleMesh<PositionNormalTexture>) that uses PBR. The corresponding Mesh.Id is 331.
2. Then I create 3 MeshObjectNodes with the same DXMesh. Each part is called part5 and consists of a SceneNode for local to world transformation and a MeshObjectNode called part5_ImportScaleFactorScaledMesh that introduces additional scaling (conversion to units, i.e. from meters to centimeters). The data are at the end (pretty verbose).

Now if I look at the data, it seems that for each MeshObjectNode is created new instance of RenderableMeshPrimitive. From that I assume, that there is no instancing involved. I would like to achieve instancing:
1. There should be only one RenderableMeshPrimitive but it would have instanced data for transformations that would be instanced on the gpu.
2. I have seen InstancedMeshGeometry3DNode. But it works only for WPF MeshGeometry3D (which DXMesh is not). Can it be used in some way (ideally without converting DXMesh to MeshGeometry3D)?
3. What the InstancedMeshGeometty3DNode actually does? Would it be like described in 1, or would RenderableMeshPrimitive be created for each instance? It should create RenderableInstancedObjectsPrimitive so I assume it's the former.
4. Can I force MeshObjectNode to create RenderableInstancedObjectsPrimitive? There is no InstancedMeshObjectNode as far as I know.

Thanks,
Janovsky Roman

The data actually in the system (it's just a part of the whole scene and there are more instances):
1. SceneNode (666) 'part5'    WorldBounds: (-25 25 -12)->(-25 64 -7) [1 x 39 x 4]    LocalBounds: (-25 25 -12)->(-25 64 -7) [1 x 39 x 4]  Transform: [-0.0 0.0 -1.0 0.0][0.0 1.0 0.0 0.0][1.0 0.0 -0.0 0.0][-25.4 62.8 -7.5 1.0] Flags: Unchanged
        MeshObjectNode (667) 'part5_ImportScaleFactorScaledMesh'    WorldBounds: (-25 25 -12)->(-25 64 -7) [1 x 39 x 4]    LocalBounds: (0 -38 -0)->(4 1 1) [4 x 39 x 1]  Transform: Scale(0.1 0.1 0.1) ; Mesh.Id: 331 (IndexCount: 154,620); Mesh.Materials count: 1; SubMeshes count: 1 || PhysicallyBasedMaterial; Flags: Unchanged
2. SceneNode (668) 'part5'    WorldBounds: (-26 25 -12)->(-25 64 -7) [1 x 39 x 4]    LocalBounds: (-26 25 -12)->(-25 64 -7) [1 x 39 x 4]  Transform: [-0.0 0.0 -1.0 0.0][0.0 1.0 0.0 0.0][1.0 0.0 -0.0 0.0][-25.9 62.8 -7.5 1.0] Flags: Unchanged
        MeshObjectNode (669) 'part5_ImportScaleFactorScaledMesh'    WorldBounds: (-26 25 -12)->(-25 64 -7) [1 x 39 x 4]    LocalBounds: (0 -38 -0)->(4 1 1) [4 x 39 x 1]  Transform: Scale(0.1 0.1 0.1) ; Mesh.Id: 331 (IndexCount: 154,620); Mesh.Materials count: 1; SubMeshes count: 1 || PhysicallyBasedMaterial; Flags: Unchanged
3. SceneNode (670) 'part5'    WorldBounds: (-26 25 -12)->(-26 64 -7) [1 x 39 x 4]    LocalBounds: (-26 25 -12)->(-26 64 -7) [1 x 39 x 4]  Transform: [-0.0 0.0 -1.0 0.0][0.0 1.0 0.0 0.0][1.0 0.0 -0.0 0.0][-26.4 62.8 -7.5 1.0] Flags: Unchanged
        MeshObjectNode (671) 'part5_ImportScaleFactorScaledMesh'    WorldBounds: (-26 25 -12)->(-26 64 -7) [1 x 39 x 4]    LocalBounds: (0 -38 -0)->(4 1 1) [4 x 39 x 1]  Transform: Scale(0.1 0.1 0.1) ; Mesh.Id: 331 (IndexCount: 154,620); Mesh.Materials count: 1; SubMeshes count: 1 || PhysicallyBasedMaterial; Flags: Unchanged

ComplexGeometry:
   0   RenderableMeshPrimitive indices count: 298818;  Material: PhysicallyBasedMaterial (776) OverrideEffect: PhysicallyBasedRenderingEffect; [0.0 -0.1 -0.0 0.0][0.0 0.0 0.1 0.0][-0.1 -0.0 0.0 0.0][-10.7 44.7 6.6 1.0]; StateSortValue: 0x800000; OriginalObject: Ab3d.DirectX.SubMesh (Mesh.Id: 323; SubMesh index: 0);   RenderingFlags: IsVisible IsFrontCounterClockwise IsCastingShadow IsReceivingShadow 
   1   RenderableMeshPrimitive indices count: 245904;  Material: PhysicallyBasedMaterial (778) OverrideEffect: PhysicallyBasedRenderingEffect; [-0.0 -0.1 0.0 0.0][0.0 0.0 0.1 0.0][-0.1 0.0 0.0 0.0][-10.7 44.7 7.6 1.0]; StateSortValue: 0x800000; OriginalObject: Ab3d.DirectX.SubMesh (Mesh.Id: 324; SubMesh index: 0);   RenderingFlags: IsVisible IsFrontCounterClockwise IsCastingShadow IsReceivingShadow 
   2   RenderableMeshPrimitive indices count: 154620;  Material: PhysicallyBasedMaterial (780) OverrideEffect: PhysicallyBasedRenderingEffect; [-0.1 0.0 0.0 0.0][0.0 0.1 0.0 0.0][-0.0 0.0 -0.1 0.0][-26.5 62.8 7.4 1.0]; StateSortValue: 0x800000; OriginalObject: Ab3d.DirectX.SubMesh (Mesh.Id: 331; SubMesh index: 0);   RenderingFlags: IsVisible IsFrontCounterClockwise IsCastingShadow IsReceivingShadow 
   3   RenderableMeshPrimitive indices count: 154620;  Material: PhysicallyBasedMaterial (780) OverrideEffect: PhysicallyBasedRenderingEffect; [-0.1 0.0 0.0 0.0][0.0 0.1 0.0 0.0][-0.0 0.0 -0.1 0.0][-26.5 62.8 6.9 1.0]; StateSortValue: 0x800000; OriginalObject: Ab3d.DirectX.SubMesh (Mesh.Id: 331; SubMesh index: 0);   RenderingFlags: IsVisible IsFrontCounterClockwise IsCastingShadow IsReceivingShadow 
   4   RenderableMeshPrimitive indices count: 154620;  Material: PhysicallyBasedMaterial (780) OverrideEffect: PhysicallyBasedRenderingEffect; [-0.1 0.0 0.0 0.0][0.0 0.1 0.0 0.0][-0.0 0.0 -0.1 0.0][-26.5 62.8 6.4 1.0]; StateSortValue: 0x800000; OriginalObject: Ab3d.DirectX.SubMesh (Mesh.Id: 331; SubMesh index: 0);   RenderingFlags: IsVisible IsFrontCounterClockwise IsCastingShadow IsReceivingShadow 
   5   RenderableMeshPrimitive indices count: 154620;  Material: PhysicallyBasedMaterial (780) OverrideEffect: PhysicallyBasedRenderingEffect; [-0.1 0.0 0.0 0.0][0.0 0.1 0.0 0.0][-0.0 0.0 -0.1 0.0][-26.5 62.8 5.9 1.0]; StateSortValue: 0x800000; OriginalObject: Ab3d.DirectX.SubMesh (Mesh.Id: 331; SubMesh index: 0);   RenderingFlags: IsVisible IsFrontCounterClockwise IsCastingShadow IsReceivingShado
#2
The purpose of RenderablePrimitive objects is to issues one DirectX draw call. The RenderablePrimitive objects are organized in RenderingQueues. This allows to sort them and provide a very memory efficient (and fast) way to provide data necessary for the draw call.

Each MeshObjectNodes usually creates one RenderableMeshPrimitive (except when there are SubMeshes defined). This is done in the overridden CollectRenderableObjects method.

In this case there is no instancing - as said, each object is drawn with its own draw call.

When instancing is used, then one DirectX draw call provides mesh information and information about multiple instances - so one draw call can render many instances of the same mesh. To render instanced objects, the InstancedMeshGeometty3DNode in its CollectRenderableObjects method creates RenderableInstancedObjectsPrimitive objects and adds them to RenderingQueues.

The difference between RenderableInstancedObjectsPrimitive and RenderableMeshPrimitive is that the first one also defined the instance buffer that has the instances data and that the first one provides its own overload of RenderGeometry that calls DrawIndexedInstanced or DrawInstanced DirectX method to render the instances.


But behind the scenes, it is not enough that you only replace DrawIndexed call with DrawIndexedInstanced and provide instances buffer. The effect and shader that renders the scene also needs to support instances - in DXEngine the only shader that supports instances is StandardEffect and its SuperShader.

This means that only standard 3D objects with standard materials (StandardMaterial or WpfMaterial) can be rendered with instances.

The problem for your case is that you cannot use instancing with PBR materials. Even if you would create RenderableInstancedObjectsPrimitive with the correct instance buffer the instances would be rendered with standard material and not with PBR.


I am currently finishing the next version of Ab3d.DXEngine and Ab3d.PowerToys and will not add any new feature to this version.

But for the next version I have added a task to provide a new SceneNode object that will take DXMesh and InstancesData so it will be able to render instances data without MeshGeometry3D object (but still only using standard material).

But I do not plan to add support for instancing with PBR materials. The problem that I see with PBR material is that each PBR material requires much more data than standard material. With standard material usually one color is enough (and that color is part of each InstanceData) but PBR requires much more data and that data are usually specified by textures. So it would be hard to add that data to InstancaData struct.

But if you have a special case where this could be useful, then it would be possible to create a custom effect and shader that would support such case. 

If you think that the current solution without instances is not fast enough (even with using multi-threading and DirectX commands caching) and think that you would really need a solution with instancing, then in the near future I could do some custom coding for you and implement PBR instancing for your case (but this is not free).

Please do a good profiling / benchmarking to check the performance of the current solution and if the problem is really in on the CPU because a lot of DirectX objects need to be rendered.
Andrej Benedik
#3
Thanks for shedding some light on this :) As you said, it's a specific scenario and I don't consider it an issue. It's just that those parts are there 50 times, so I thought that instancing would be worth a shot. The scene by itself has around 10m triangles, multiply by 4 for each window and of course it won't be 60 fps (but 20-30 is pretty solid). 
And of course, this is when the whole model is in view, with no frustum and occlusion culling.

Quote:But for the next version I have added a task to provide a new SceneNode object that will take DXMesh and InstancesData so it will be able to render instances data without MeshGeometry3D object (but still only using standard material).
This seems very cool. Looking forward to it.
  


Forum Jump:


Users browsing this thread:
1 Guest(s)