Instanced WireBoxVisual3D

I can see there are various ways to display instanced data, however I'm struggling to be able to instance WireBoxVisual3D.

The samples show the following:
  • InstancedText
  • InstancedMeshGeometry3d (but this isn't ModelVisual3D)
  • InstancedModel3DGroup
  • InstancedMeshNode
The closest I can see is to use InstancedModelGroupVisual3D, and manually create a Model3DGroup, but when I try and access the Content property of the WireBoxVisual3D (so I can add it to the Model3DGroup), it is always null.

Is this possible? Any help is appreciated.
It is not possible to render many instances of 3D lines. This would require a new shader and an update to the ThickLineEffect.

But you have the following options:

WireBoxVisual3D is rendered by using 3D lines (a geometry shader is used to generate the 3D lines with specified line thickness and in such a way that they are always facing the camera).

But instead of that, you can create 3D lines from line tubes - that are fixed MeshGeometry3D objects that are not changed when the camera is changed but when rendered as solid color (or as emissive material) that is not shaded by light that can be used as 3D lines - see example in Ab3d.PowerToys samples project:


Note that there you do not specify the line thickness that is defined in screen coordinates regardless of the camera, but you set tube radius in 3D world coordinates - so the lines will get visually thinner when the camera is moving away.

You can generate tube lines mesh by using Ab3d.Meshes.TubeLineMesh3D and Ab3d.Meshes.TubePathMesh3D. You can combine meshes by Ab3d.Utilities.MeshUtils.CombineMeshes. 

This will give you a single MeshGeometry3D that can be used for instancing with InstancedMeshGeometryVisual3D. To prevent shading the meshes, you need to render them with a solid color - set IsSolidColorMaterial on InstancedMeshGeometryVisual3D to true.

You can render instanced objects as wireframe objects instead of as solid models.

To do this you will need to set the OverrideRasterizerState on the RenderObjectsRenderingStep to DXDevice.CommonStates.WireframeMultisampleCullNone. 

This is a more advanced customization. This required to create another RenderObjectsRenderingStep and add it before the DefaultRenderObjectsRenderingStep. Then for the new RenderObjectsRenderingStep set the OverrideRasterizerState to XDevice.CommonStates.WireframeMultisampleCullNone and set FilterRenderingQueuesFunction to render only ComplexGeometryRenderingQueue. You also need to update the DefaultRenderObjectsRenderingStep by setting the FilterRenderingQueuesFunction to the function that will render all rendering queues except ComplexGeometryRenderingQueue.

In the InstancedMeshGeometry3DTest.xaml sample from Ab3d.DXEngine samples this can be achieved by using the following code (add that into the constructor):
MainDXViewportView.DXSceneInitialized += delegate (object sender, EventArgs args)
    // Create a new RenderObjectsRenderingStep that will render only wireframe instanced objects.
    // If you are not rendering any other objects or want to render all objects as wireframe, then you do not need to add this new RenderObjectsRenderingStep
    // and can just set the OverrideRasterizerState on the DefaultRenderObjectsRenderingStep.

    var renderWireframeInstancesRenderingStep = new RenderObjectsRenderingStep("RenderWireframeInstances");

    // Override RasterizerState to render objects are wireframe instead of solid objects (not that line thickness is always 1 in this case)
    renderWireframeInstancesRenderingStep.OverrideRasterizerState = MainDXViewportView.DXScene.DXDevice.CommonStates.WireframeMultisampleCullNone;

    // Render only objects in the ComplexGeometryRenderingQueue (instanced objects are always put into this rendering queue).
    // Note that if you use multiple instanced objects or some other complex objects with many positions or lines
    // (defined by DXScene.MeshTriangleIndicesCountRequiredForComplexGeometry and DXScene.LinesCountRequiredForComplexGeometry -
    // you can also increase those two numbers to prevent putting other objects into ComplexGeometryRenderingQueue)
    // then you will need to create another RenderObjectsRenderingStep and also set the FilterObjectsFunction (one will show only instances with wireframe and the other other complex objects).
    renderWireframeInstancesRenderingStep.FilterRenderingQueuesFunction = queue => queue == MainDXViewportView.DXScene.ComplexGeometryRenderingQueue;

    MainDXViewportView.DXScene.RenderingSteps.AddBefore(MainDXViewportView.DXScene.DefaultRenderObjectsRenderingStep, renderWireframeInstancesRenderingStep);

    // Update the DefaultRenderObjectsRenderingStep to prevent rendering ComplexGeometryRenderingQueue
    // This will render other objects normally.
    MainDXViewportView.DXScene.DefaultRenderObjectsRenderingStep.FilterRenderingQueuesFunction = queue => queue != MainDXViewportView.DXScene.ComplexGeometryRenderingQueue;

    // To render wireframe with solid color (without shading the lines) also set the "_instancedMeshGeometryVisual3D.IsSolidColorMaterial = true;" in the code below.

The screenshot of this can be seen here:

Disadvantages of this technique:
  • you need to render all lines for all triangles - for example for box mesh the diagonal line will be also rendered as seen in the screenshot above;
  • line thickness is always 1 (you cannot set other line thickness);
  • you cannot use line depth bias to render lines over solid 3D objects

Use many WireBoxVisual3D objects. Ab3d.DXEngine uses multi-threading and DirectX commands caching and can render many individual 3D objects very fast.

To optimize that you can convert many WireBoxVisual3D into a single MultiLineVisual3D where you manually define the 3D positions for all lines for all wire boxes.

You can start with defining 3D lines for one 3D wire box with center position at (0, 0, 0) and size (1, 1, 1). This would define the default lines. Then for each instance of wire box (instance in the InstanceData) transform the positions of the default lines by the WorldMatrix. 

The following code can be used to do that:
InstanceData[] allInstancesData = new InstanceData[1000];
// ... define allInstancesData ...

var worldTransform = new MatrixTransform3D();

var defaultWireBoxPositions = new List<Point3D>();
// ... define defaultWireBoxPositions ...

var allInstancedWireBoxesPositions = new Point3DCollection(allInstancesData.Length * defaultWireBoxPositions.Count); // presize the collection

for (var i = 0; i < allInstancesData.Length; i++)
    worldTransform.Matrix = allInstancesData[i].World.ToWpfMatrix3D();

    for (var j = 0; j < defaultWireBoxPositions.Count; j++)
        var oneFinalPosition = worldTransform.Transform(defaultWireBoxPositions[j]);

var multiLineVisual = new MultiLineVisual3D()
    Positions = allInstancedWireBoxesPositions,

If you are not changing the world matrices very often then this is in my opinion the preferred way to show 3D lines. If you change the matrices often, then this would require to regenerate the allInstancedWireBoxesPositions. In this case you need to decide if you can optimize this code or use options (1) or (2)
Andrej Benedik
Thanks for the response! I thought it might be something like that, thank you for providing alternatives.

Forum Jump:

Users browsing this thread:
1 Guest(s)