Double sided rendering
#1
This might be a stupid question, but how do I render double sided geometry? I'm generating a terrain mesh and rendering it directly using DXScene.

I can see that the WPF implementations allow a "BackMaterial" but I see no such thing in the MeshObjectNode.
#2
Double sided materials are rendered in DXEngine with rendering the same object twice - first with front material and then with back material (in this case the normal is flipped in the shader and reverse the triangle culling).

This is done by adding two RenderableMeshPrimitive to rendering queues - the second has IsBackFaceMaterial set to true. 

In the current version of DXEngine (v2.3) the MeshObjectNode does not support that. This will be available in the next version.

Here is the source code of the new MeshObjectNode with new IsBackFaceMaterial property:
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using Ab3d.DirectX.Materials;
using SharpDX;

namespace Ab3d.DirectX
{
   // NOTE: Class is sealed for better performance - this way the virtual method can be directly called (without need to check virtual methods table first)

   /// <summary>
   /// MeshObjectNode is an <see cref="ObjectNode"/> that can render an object that is defined by object derived from <see cref="MeshBase"/> class.
   /// </summary>
   [Obfuscation(Feature = "control flow", Exclude = true, ApplyToMembers = true)]
   public sealed class MeshObjectNode : ObjectNode
   {
       private bool _isMaterialInitialized;

       /// <summary>
       /// Gets a Mesh that defines the 3D object geometry.
       /// </summary>
       public MeshBase Mesh { get; private set; }

       
       private Material[] _materials;

       /// <summary>
       /// Gets or sets an array of Material that can be set to override the materials defined in the Mesh.
       /// </summary>
       public Material[] Materials
       {
           get { return _materials; }
           set
           {
               if (_materials == value)
                   return;

               _materials = value;

               _isMaterialInitialized = false;
           }
       }

       /// <summary>
       /// IsBackFaceMaterial specifies if the material is used to render front (IsBackFaceMaterial == false) or back (IsBackFaceMaterial == true) faces.
       /// </summary>
       public bool IsBackFaceMaterial;


       /// <summary>
       /// Initializes a new instance of the <see cref="MeshObjectNode"/> class.
       /// </summary>
       /// <param name="meshBase">MeshBase object that defines the 3D object geometry</param>
       public MeshObjectNode(MeshBase meshBase)
           : this(meshBase, (Material[])null )
       {
       }

       /// <summary>
       /// Initializes a new instance of the <see cref="MeshObjectNode"/> class.
       /// </summary>
       /// <param name="meshBase">MeshBase object that defines the 3D object geometry</param>
       /// <param name="material">material used by this ObjectNode</param>
       public MeshObjectNode(MeshBase meshBase, Material material)
           : this(meshBase, new Material[] { material })
       {
       }

       /// <summary>
       /// Initializes a new instance of the <see cref="MeshObjectNode"/> class.
       /// </summary>
       /// <param name="mesh">MeshBase object that defines the 3D object geometry</param>
       /// <param name="materials">material used by this ObjectNode</param>
       /// <exception cref="System.ArgumentNullException">mesh;mesh is null.</exception>
       public MeshObjectNode(MeshBase mesh, Material[] materials)
       {
           if (mesh == null)
               throw new ArgumentNullException("mesh", "mesh is null.");

           Mesh = mesh;
           mesh.AddRef();

           Materials = materials;

           if (materials != null && materials.Length > 0)
           {
               for (var i = 0; i < materials.Length; i++)
                   materials[i].AddRef();
           }

           this.Name = mesh.Name;
           this.Bounds = mesh.Bounds;

           LockChildNodes(); // Prevent adding any ChildNodes
       }

       /// <summary>
       /// Updates the bounds of this SceneNode if the dirty flags indicates that the bounds could be changed of if the forceUpdate parameter is set to true
       /// </summary>
       /// <param name="forceUpdate">if true than bounds are updated regardless of the SceneNode's dirty flags</param>
       /// <returns>true if bounds were changed</returns>
       public override bool UpdateBounds(bool forceUpdate = false)
       {
           bool changeNeeded = forceUpdate || (this.DirtyFlags & (SceneNodeDirtyFlags.MeshChanged |
                                                                  SceneNodeDirtyFlags.MeshPositionsChanged |
                                                                  SceneNodeDirtyFlags.TransformChanged |
                                                                  SceneNodeDirtyFlags.WorldMatrixChanged)) != 0;

           if (!changeNeeded)
               return false;

           
           bool isChanged;

           if (Mesh != null && Mesh.Bounds != null && !Mesh.Bounds.IsEmpty)
           {
               this.Bounds = Mesh.Bounds;
               isChanged = true;
           }
           else
           {
               isChanged = false;
           }

           if (isChanged)
           {
               if (this.Transform != null && !this.Transform.IsIdentity && Bounds != null)
                   Bounds.Transform(this.Transform);

               NotifySceneNodeChange(SceneNodeDirtyFlags.BoundsChanged); // Mark this SceneNode as BoundsChanged
               NotifyAllParentSceneNodesChange(SceneNodeDirtyFlags.ChildBoundsChanged); // Notify all parent SceneNodes that this child was changed
           }

           return isChanged; // we have changed the bounds
       }

       /// <inheritdoc />
       protected override void OnInitializeResources(DXScene dxScene)
       {
           var dxDevice = parentDXScene.DXDevice;

           // Initialize Mesh
           if (!Mesh.IsInitialized)
               Mesh.InitializeResources(dxDevice);

           // Initialize materials
           EnsureInitializedMaterial(dxDevice);

           base.OnInitializeResources(dxScene);
       }

       /// <inheritdoc />
       public override void CollectRenderableObjects()
       {
           if (parentDXScene == null)
               return;

           EnsureInitializedMaterial(parentDXScene.DXDevice);

           if (!this.IsActive || Mesh.VertexBufferBindings == null)
               return;


            Material usedMaterial;

            if (this.Materials != null && this.Materials.Length > 0)
                usedMaterial = this.Materials[0];
            else if (Mesh.Materials != null && Mesh.Materials.Length > 0)
                usedMaterial = Mesh.Materials[0];
            else
                usedMaterial = null;

            if (usedMaterial != null)
            {
                // First create new IRenderableObject (RenderableMeshPrimitive in our case)
                RenderableMeshPrimitive newRenderableMeshPrimitive;

                if (this.IsWorldMatrixIdentity)
                    newRenderableMeshPrimitive = new RenderableMeshPrimitive(Mesh, usedMaterial);
                else
                    newRenderableMeshPrimitive = new RenderableMeshPrimitive(Mesh, usedMaterial, ref WorldMatrix);

                newRenderableMeshPrimitive.IsBackFaceMaterial = this.IsBackFaceMaterial;


                // Now select the RenderingQueue where the new IRenderableObject will be added to
                
                // Check if material HasTransparency - in that case put it into TransparentRenderingQueue, else move it to GeometryRenderingQueue
                var transparentMaterial = usedMaterial as ITransparentMaterial;

                RenderingQueue renderingQueue;
                if (transparentMaterial != null && transparentMaterial.HasTransparency)
                    renderingQueue = parentDXScene.TransparentRenderingQueue;
                else
                    renderingQueue = parentDXScene.GeometryRenderingQueue;

                if (renderingQueue != null)
                    renderingQueue.Add(newRenderableMeshPrimitive);
            }
       }

       private void EnsureInitializedMaterial(DXDevice dxDevice)
       {
           if (_isMaterialInitialized)
               return;

           if (Materials != null)
           {
               int materialsCount = Materials.Length;
               for (int i = 0; i < materialsCount; i++)
               {
                   var oneMaterial = Materials[i];
                   if (oneMaterial != null && !oneMaterial.IsInitialized)
                       oneMaterial.InitializeResources(dxDevice);
               }
           }

           _isMaterialInitialized = true;
       }

       /// <inheritdoc />
       protected override void Dispose(bool disposing)
       {
           if (disposing)
           {
               Mesh.Dispose();
               Mesh = null;

               if (Materials != null && Materials.Length > 0)
               {
                   for (var i = 0; i < Materials.Length; i++)
                       Materials[i].Dispose();
               }

               Materials = null;
               Bounds = Bounds.Empty;
               _isMaterialInitialized = false;
           }

           base.Dispose(disposing);
       }
   }
}

This source has updated CollectRenderableObjects method with new line:
newRenderableMeshPrimitive.IsBackFaceMaterial = this.IsBackFaceMaterial;

To render two-sided material create two instances of MeshObjectNode and set IsBackFaceMaterial to true on the second.


NOTE:
To use this source with DXEngine v2.3, you will need to change the IsWorldMatrixIdentity to isWorldMatrixIdentity and WorldMatrix to worldMatrix (using CamelCase becase in v2.3 those two properties are protected and not public as in a never version)
Andrej Benedik
#3
Ok. I did know how double sided rendering worked, I was just hoping there was some easier to consume way to enable it - perhaps as a material setting or perhaps as a property on the mesh object node.

Thanks for the source, I'll definitely take a look, but as I'm still evaluating your product, I'll most likely wait till your new version is released. I'm also looking forward to the oct-tree implementation you said was coming out in the upcoming version too.
  


Forum Jump:


Users browsing this thread:
1 Guest(s)