ViRuSTriNiTy
ViRuSTriNiTy

Reputation: 5155

How to handle diffuse material transparency in WPF 3D?

I have a scene where multiple models have planar faces and a diffuse material with transparency.

These models sometimes overlap due to a changing camera angle and then these models are not rendered correctly like in the following picture where the blue transparent face hides a part of the red cube:

blue transparent face hides part of the red cube

After some research i found out that depth sorting is needed to render the models correctly in all cases.

Is there any library out there that already handles this e.g. by an implementation of the Newell's algorithm or does someone knows another solution?

Upvotes: 4

Views: 1654

Answers (1)

ViRuSTriNiTy
ViRuSTriNiTy

Reputation: 5155

I found a sorting algorithm that is based on the model bounding box originating from a very old Microsoft blog which is not reachable anymore.

Here is the class with some code changes to pass ModelVisual3D instances directly:

// original class taken from
// https://web.archive.org/web/20071201135137/http://blogs.msdn.com/pantal/archive/2007/07/23/sorting-for-wpf-3d-transparency.aspx
public static class SceneSortingHelper
{
    /// <summary>
    /// Sort Modelgroups in farthest to closest order, to enable transparency
    /// Should be applied whenever the scene is significantly re-oriented
    /// </summary>
    public static void AlphaSort(Point3D CameraPosition, 
        System.Collections.Generic.List<System.Windows.Media.Media3D.ModelVisual3D> ModelVisual3Ds, Transform3D WorldTransform)
    {
        ArrayList list = new ArrayList();
        foreach (var m in ModelVisual3Ds)
        {
            var location =
                WorldTransform.TransformBounds(
                    m.Transform.TransformBounds(
                        m.Content.Transform.TransformBounds(
                            m.Content.Bounds
                        )
                    )
                ).Location;

            double distance = Point3D.Subtract(CameraPosition, location).Length;
            list.Add(new ModelVisual3DDistance(distance, m));
        }
        list.Sort(new DistanceComparer(SortDirection.FarToNear));
        ModelVisual3Ds.Clear();
        foreach (ModelVisual3DDistance d in list)
        {
            ModelVisual3Ds.Add(d.modelVisual3D);
        }
    }

    private class ModelVisual3DDistance
    {
        public ModelVisual3DDistance(double distance, ModelVisual3D modelVisual3D)
        {
            this.distance = distance;
            this.modelVisual3D = modelVisual3D;
        }

        public double distance;
        public ModelVisual3D modelVisual3D;
    }

    private enum SortDirection
    {
        NearToFar,
        FarToNear
    }

    private class DistanceComparer : IComparer
    {
        public DistanceComparer(SortDirection sortDirection)
        {
            _sortDirection = sortDirection;
        }

        int IComparer.Compare(Object o1, Object o2)
        {
            double x1 = ((ModelVisual3DDistance)o1).distance;
            double x2 = ((ModelVisual3DDistance)o2).distance;

            if (_sortDirection == SortDirection.FarToNear)
            {
                if (x1 > x2)
                    return -1;
                else
                if (x1 < x2)
                    return 1;
                else
                    return 0;
            }
            else
            {
                if (x1 > x2)
                    return 1;
                else
                if (x1 < x2)
                    return -1;
                else
                    return 0;
            }
        }

        private SortDirection _sortDirection;
    }
}

I'm using the aforementioned class in connection with the following Viewport3D extension method ...

namespace Viewport3DExtensions
{
    public static class Implementation
    {
        public static void AlphaSortModels(this System.Windows.Controls.Viewport3D viewport3D)
        {
            var projectionCamera = viewport3D.Camera as System.Windows.Media.Media3D.ProjectionCamera;

            if (projectionCamera != null)
            {
                var modelVisual3Ds = new System.Collections.Generic.List<System.Windows.Media.Media3D.ModelVisual3D>();

                foreach (var c in viewport3D.Children)
                {
                    var m = c as System.Windows.Media.Media3D.ModelVisual3D;

                    if (m != null)
                        modelVisual3Ds.Add(m);
                }

                // note:
                //  the following method works well most of the time but sometimes the sort is wrong as the sort is simply based on model bounds,
                //  to get rid of artifacts we would need something like binary space partitioning
                SceneHelper.SceneSortingHelper.AlphaSort(
                    projectionCamera.Position,
                    modelVisual3Ds,
                    new System.Windows.Media.Media3D.Transform3DGroup()
                );

                viewport3D.Children.Clear();

                foreach (var c in modelVisual3Ds)
                    viewport3D.Children.Add(c);
            }
        }
    }
}

... like so

System.Windows.Controls.Viewport3D scene;  
...
scene.AlphaSortModels();

The scene now looks alright:

Transparency displayed correctly

Upvotes: 3

Related Questions