前回、半透明を試してみたところ、うまく半透明の効果が現れない場合があった。これは、手前のものを先に描画すると、まだ奥のものが描画されていないためにアルファブレンドできないということだと思う。大抵のレンダリングシステムでも同じようなものだろう。

結局のところ、半透明をうまく描画するためには、オブジェクトを奥から手前の順に描画していけばいい、ということになる。

オブジェクトは Viewport3D クラスの Children プロパティ(Visual3DCollection)に格納されている。描画順は、単にこのプロパティに格納されている順番に行われているようなので、カメラから遠い順にソートし直すことにする。

今回はこのソートを行うクラス SceneSortingHelper を導入した。ソースコードは以下のとおり。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Media3D;
 
namespace sample1
{
    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, Visual3DCollection Models)
        {
            ArrayList list = new ArrayList();
            foreach (ModelVisual3D model in Models)
            {
                ModelVisual3D m = model as ModelVisual3D;
                if (m != null)
                {
                    if (m.Content is Light)
                    {
                        list.Add(new ModelDistance(0, model));
                    }
                    else if (m.Content is Model3DGroup)
                    {
                        var center = new Point3D(
                            model.Content.Bounds.X + model.Content.Bounds.SizeX / 2,
                            model.Content.Bounds.Y + model.Content.Bounds.SizeY / 2,
                            model.Content.Bounds.Z + model.Content.Bounds.SizeZ / 2);
                        var modelPosition = model.Transform.Transform(center);
                        double distance = (Point3D.Subtract(CameraPosition, modelPosition)).Length;
                        list.Add(new ModelDistance(distance, model));
                    }
                    else
                    {
                        list.Add(new ModelDistance(0, model));
                    }
                }
                else
                {
                    list.Add(new ModelDistance(0, model));
                }
            }
            list.Sort(new DistanceComparer(SortDirection.FarToNear));
            Models.Clear();
            foreach (ModelDistance modelDistance in list)
            {
                Models.Add(modelDistance.model);
            }
        }
 
        private class ModelDistance
        {
            public ModelDistance(double distance, ModelVisual3D model)
            {
                this.distance = distance;
                this.model = model;
            }
 
            public double distance;
            public ModelVisual3D model;
        }
 
        private enum SortDirection
        {
            NearToFar,
            FarToNear
        }
 
        private class DistanceComparer : IComparer
        {
            public DistanceComparer(SortDirection sortDirection)
            {
                _sortDirection = sortDirection;
            }
 
            int IComparer.Compare(Object o1, Object o2)
            {
                double x1 = ((ModelDistance)o1).distance;
                double x2 = ((ModelDistance)o2).distance;
                if (_sortDirection == SortDirection.NearToFar)
                {
                    return (int)(x1 - x2);
                }
                else
                {
                    return (int)(-(x1 - x2));
                }
            }
 
            private SortDirection _sortDirection;
        }
    }
}

ソートは、レンダリングのタイミングで行う必要があるので、CompositionTarget.Rendering イベント内で行う。 カメラ位置は TrackballDecorator を導入しているので動き回るため、TrackballDecorator の変換してから使う。MainWindow.xaml.cs は以下のとおり。
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace sample1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Init();
 
            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }
 
        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            var root = this.Content as Grid;
            var td = root.Children[0] as _3DTools.TrackballDecorator;
            var viewport = td.Content as Viewport3D;
 
            var camera = viewport.Camera as PerspectiveCamera;
            var cameraPostion = td.Transform.Transform(camera.Position);
 
            SceneSortingHelper.AlphaSort(cameraPostion, viewport.Children);
        }
 
        private void Init()
        {
            var root = this.Content as Grid;
 
            // trackball
            var td = new _3DTools.TrackballDecorator();
            root.Children.Add(td);
 
            // viewport
            var viewport = new Viewport3D();
            td.Content = viewport;
 
            // camera
            var camera = new PerspectiveCamera();
            camera.Position = new Point3D(14, 13, 12);
            camera.LookDirection = new Vector3D(-14, -13, -12);
            camera.UpDirection = new Vector3D(0, 1, 0);
            viewport.Camera = camera;
 
            // light
            var light = new DirectionalLight();
            light.Color = Colors.White;
            light.Direction = new Vector3D(-2, -3, -1);
            var lightModel = new ModelVisual3D();
            lightModel.Content = light;
            viewport.Children.Add(lightModel);
 
            // light2
            var light2 = new AmbientLight();
            light2.Color = Color.FromRgb(128, 128, 128);
            var lightModel2 = new ModelVisual3D();
            lightModel2.Content = light2;
            viewport.Children.Add(lightModel2);
 
            // axis
            var xAxis = new _3DTools.ScreenSpaceLines3D();
            xAxis.Points.Add(new Point3D(-100, 0, 0));
            xAxis.Points.Add(new Point3D(100, 0, 0));
            xAxis.Color = Colors.Red;
            xAxis.Thickness = 1;
            var yAxis = new _3DTools.ScreenSpaceLines3D();
            yAxis.Points.Add(new Point3D(0, -100, 0));
            yAxis.Points.Add(new Point3D(0, 100, 0));
            yAxis.Color = Colors.Green;
            yAxis.Thickness = 1;
            var zAxis = new _3DTools.ScreenSpaceLines3D();
            zAxis.Points.Add(new Point3D(0, 0, -100));
            zAxis.Points.Add(new Point3D(0, 0, 100));
            zAxis.Color = Colors.Blue;
            zAxis.Thickness = 1;
            var axis = new ModelVisual3D();
            axis.Children.Add(xAxis);
            axis.Children.Add(yAxis);
            axis.Children.Add(zAxis);
            viewport.Children.Add(axis);
 
            // cube
            var point0 = new Point3D(0, 0, 0);    // bottom-back-left
            var point1 = new Point3D(5, 0, 0);    // bottom-back-right
            var point2 = new Point3D(5, 0, 5);    // bottom-front-right
            var point3 = new Point3D(0, 0, 5);    // bottom-front-left
            var point4 = new Point3D(0, 5, 0);    // top-back-left
            var point5 = new Point3D(5, 5, 0);    // top-back-right
            var point6 = new Point3D(5, 5, 5);    // top-front-right
            var point7 = new Point3D(0, 5, 5);    // top-front-left
 
            //       Y
            //       | 
            //    p4 +------------------+ p5
            //      /|                 /|
            //     / |                / |
            // p7 /  |            p6 /  |
            //   +------------------+   |
            //   |   +--------------|---+---------- X
            //   |  / p0            |  / p1
            //   | /                | /
            //   |/                 |/
            //   +------------------+
            //  / p3                p2
            // Z
 
            var imageSrc = new BitmapImage(new Uri(@"C:\syaraku.JPG"));
            var bitmap = ImageHelper.BitmapImage2Bitmap(imageSrc);
            System.Drawing.Bitmap alphaImage = ImageHelper.CreateAlphaImage(bitmap, 0.5F);
            var alphaImageSrc = ImageHelper.ToWpfBitmap(alphaImage);
            var material = new DiffuseMaterial(new ImageBrush(alphaImageSrc));
 
            var group = new Model3DGroup();
            group.Children.Add(new GeometryModel3D(CreateMesh(point7, point6, point2, point3), material));    // front
            group.Children.Add(new GeometryModel3D(CreateMesh(point6, point5, point1, point2), material));    // right
            group.Children.Add(new GeometryModel3D(CreateMesh(point5, point4, point0, point1), material));    // back
            group.Children.Add(new GeometryModel3D(CreateMesh(point4, point7, point3, point0), material));    // left
            group.Children.Add(new GeometryModel3D(CreateMesh(point4, point5, point6, point7), material));    // top
            group.Children.Add(new GeometryModel3D(CreateMesh(point1, point0, point3, point2), material));    // bottom
 
            var cubeModel = new ModelVisual3D();
            cubeModel.Content = group;
            viewport.Children.Add(cubeModel);
            
            // cube2
            var group2 = new Model3DGroup();
            var br2 = new SolidColorBrush(Colors.Blue);
            br2.Opacity = 0.5;
            var material2 = new DiffuseMaterial(br2);
            group2.Children.Add(new GeometryModel3D(CreateMesh(point7, point6, point2, point3), material2));    // front
            group2.Children.Add(new GeometryModel3D(CreateMesh(point6, point5, point1, point2), material2));    // right
            group2.Children.Add(new GeometryModel3D(CreateMesh(point5, point4, point0, point1), material2));    // back
            group2.Children.Add(new GeometryModel3D(CreateMesh(point4, point7, point3, point0), material2));    // left
            group2.Children.Add(new GeometryModel3D(CreateMesh(point1, point0, point3, point2), material2));    // bottom
            group2.Children.Add(new GeometryModel3D(CreateMesh(point4, point5, point6, point7), material2));    // top
            var cubeModel2 = new ModelVisual3D();
            cubeModel2.Content = group2;
            var trans2 = new Transform3DGroup();
            trans2.Children.Add(new TranslateTransform3D(-3, -3, 3));
            cubeModel2.Transform = trans2;
            viewport.Children.Add(cubeModel2);
 
            // cube3
            var group3 = new Model3DGroup();
            var br3 = new SolidColorBrush(Colors.Green);
            br3.Opacity = 0.5;
            var material3 = new DiffuseMaterial(br3);
            group3.Children.Add(new GeometryModel3D(CreateMesh(point7, point6, point2, point3), material3));    // front
            group3.Children.Add(new GeometryModel3D(CreateMesh(point6, point5, point1, point2), material3));    // right
            group3.Children.Add(new GeometryModel3D(CreateMesh(point5, point4, point0, point1), material3));    // back
            group3.Children.Add(new GeometryModel3D(CreateMesh(point4, point7, point3, point0), material3));    // left
            group3.Children.Add(new GeometryModel3D(CreateMesh(point1, point0, point3, point2), material3));    // bottom
            group3.Children.Add(new GeometryModel3D(CreateMesh(point4, point5, point6, point7), material3));    // top
            var cubeModel3 = new ModelVisual3D();
            cubeModel3.Content = group3;
            var trans3 = new Transform3DGroup();
            trans3.Children.Add(new TranslateTransform3D(-3, 3, -3));
            cubeModel3.Transform = trans3;
            viewport.Children.Add(cubeModel3);
        }
 
        private static MeshGeometry3D CreateMesh(Point3D p0, Point3D p1, Point3D p2, Point3D p3)
        {
            var mesh = new MeshGeometry3D();
            mesh.Positions = new Point3DCollection(new Point3D[] { p0, p1, p2, p3 });
            mesh.TriangleIndices = new Int32Collection(new int[] { 0, 2, 1, 0, 3, 2 });
            mesh.TextureCoordinates = new PointCollection(new Point[] { new Point(0, 0), new Point(1, 0), new Point(1, 1), new Point(0, 1) });
            return mesh;
        }
    }
}

実行結果は以下のとおり。写楽立方体の向こうに青と緑の立方体が透けて見える。カメラを動かしても問題なくなった。
wpf3d-7

以上。