Множество трехмерных объектов

108

При создании достаточно сложного трехмерного объекта (хотя бы такого, как куб в примерах из предыдущих статей) необходимо управлять огромным количеством параметров и писать много кода. В рассмотренных ранее примерах приложение помещает фигуру в пользовательский элемент управления, представляющий страницу. Однако лучше поместить ее в выделенный класс, служащий оболочкой для всего кода рисования и предоставляющий легкий доступ к настраиваемым свойствам. Этим вы не только упростите код, но и облегчите использование копий объекта в трехмерной сцене.

Рассмотрим пример с текстурированным кубом. Переместим весь алгоритм рисования в отдельный класс Cube3D:

public class Cube3D
{
        // Сохранение буфера вершин и параметров эффекта
        private VertexBuffer vertexBuffer;
        private BasicEffect effect;

        // Свойство доступа к параметрам мировой матрицы (необходимо
        // для перемещения и поворота объекта)
        public Matrix World
        {
            get { return effect.World; }
            set { effect.World = value; }
        }

        // Свойство доступа к матрице вида (необходимо
        // для перемещения и поворота камеры)
        public Matrix View
        {
            get { return effect.View; }
            set { effect.View = value; }
        }

        // Свойство доступа к проекции вида (необходимо
        // для изменения пропорций DrawingSurface)
        public Matrix Projection
        {
            get { return effect.Projection; }
            set { effect.Projection = value; }
        }
        
    ...

Класс Cube3D предоставляет доступ к трем матрицам, которые необходимы для манипулирования моделью в приложении при кодировании средств интерактивности и анимации. Все подробности рисования, закодированные в объектах VertexBuffer и BasicEffect, скрыты.

У класса Cube3D есть два конструктора. Оба они принимают несколько ключевых параметров: используемое устройство GraphicsDevice, объект Vector3, позиционирующий левый нижний задний угол куба, ширину куба, адрес URI текстуры и пропорцию поверхности рисования DrawingSurface. Второй конструктор, кроме того, принимает матрицу вида, отвечающую за расположение камеры. Для ее получения первый конструктор вызывает второй и применяет жестко закодированную матрицу вида.

public Cube3D(GraphicsDevice device, Vector3 bottomLeftBack, float width, 
      string textureUri, float aspectRatio) 
         : this(device, bottomLeftBack, width, textureUri, aspectRatio,
         Matrix.CreateLookAt(new Vector3(0, 0, 5), Vector3.Zero, Vector3.Up))
{
            // Код не нужен, потому что вызывается второй конструктор
}

Второй конструктор содержит код, который в предыдущих примерах находился в методе PrepareDrawing(). Единственное отличие нового кода состоит в том, что он немного скорректирован для приема аргументов конструктора. Например, все вершины теперь определяются относительно заднего левого нижнего угла куба и на основе заданной ширины:

public Cube3D(GraphicsDevice device, Vector3 bottomLeftBack, float width,
            string textureUri, float aspectRatio, Matrix view)
{
            Vector2 textureTopLeft = new Vector2(0, 0);
            Vector2 textureTopRight = new Vector2(1, 0);
            Vector2 textureBottomLeft = new Vector2(0, 1);
            Vector2 textureBottomRight = new Vector2(1, 1);

            Vector3 topLeftFront = new Vector3(bottomLeftBack.X, bottomLeftBack.Y + width, bottomLeftBack.Z + width);
            Vector3 bottomLeftFront = new Vector3(bottomLeftBack.X, bottomLeftBack.Y, bottomLeftBack.Z + width);
            Vector3 topRightFront = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y + width, bottomLeftBack.Z + width);
            Vector3 bottomRightFront = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y, bottomLeftBack.Z + width);
            Vector3 topLeftBack = new Vector3(bottomLeftBack.X, bottomLeftBack.Y + width, bottomLeftBack.Z);
            Vector3 topRightBack = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y + width, bottomLeftBack.Z);
            Vector3 bottomRightBack = new Vector3(bottomLeftBack.X + width, bottomLeftBack.Y, bottomLeftBack.Z);
            VertexPositionTexture[] vertices = new VertexPositionTexture[36];
            // Передняя грань
            vertices[0] = new VertexPositionTexture(topRightFront, textureTopRight);
            vertices[1] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
            vertices[2] = new VertexPositionTexture(topLeftFront, textureTopLeft);
            vertices[3] = new VertexPositionTexture(topRightFront, textureTopRight);
            vertices[4] = new VertexPositionTexture(bottomRightFront, textureBottomRight);
            vertices[5] = new VertexPositionTexture(bottomLeftFront, textureBottomLeft);
            // Задняя грань
            vertices[6] = new VertexPositionTexture(bottomLeftBack, textureBottomRight);
            vertices[7] = new VertexPositionTexture(topRightBack, textureTopLeft);
            vertices[8] = new VertexPositionTexture(topLeftBack, textureTopRight);
            vertices[9] = new VertexPositionTexture(bottomRightBack, textureBottomLeft);
            vertices[10] = new VertexPositionTexture(topRightBack, textureTopLeft);
            vertices[11] = new VertexPositionTexture(bottomLeftBack, textureBottomRight);
            // Верхняя грань
            vertices[12] = new VertexPositionTexture(topLeftBack, textureTopLeft);
            vertices[13] = new VertexPositionTexture(topRightBack, textureTopRight);
            vertices[14] = new VertexPositionTexture(topLeftFront, textureBottomLeft);
            vertices[15] = new VertexPositionTexture(topRightBack, textureTopRight);
            vertices[16] = new VertexPositionTexture(topRightFront, textureBottomRight);
            vertices[17] = new VertexPositionTexture(topLeftFront, textureBottomLeft);
            // Нижняя грань
            vertices[18] = new VertexPositionTexture(bottomRightBack, textureTopLeft);
            vertices[19] = new VertexPositionTexture(bottomLeftBack, textureTopRight);
            vertices[20] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
            vertices[21] = new VertexPositionTexture(bottomRightFront, textureBottomLeft);
            vertices[22] = new VertexPositionTexture(bottomRightBack, textureTopLeft);
            vertices[23] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
            // Левая грань
            vertices[24] = new VertexPositionTexture(bottomLeftFront, textureBottomRight);
            vertices[25] = new VertexPositionTexture(bottomLeftBack, textureBottomLeft);
            vertices[26] = new VertexPositionTexture(topLeftFront, textureTopRight);
            vertices[27] = new VertexPositionTexture(topLeftFront, textureTopRight);
            vertices[28] = new VertexPositionTexture(bottomLeftBack, textureBottomLeft);
            vertices[29] = new VertexPositionTexture(topLeftBack, textureTopLeft);
            // Правая грань
            vertices[30] = new VertexPositionTexture(bottomRightBack, textureBottomRight);
            vertices[31] = new VertexPositionTexture(bottomRightFront, textureBottomLeft);
            vertices[32] = new VertexPositionTexture(topRightFront, textureTopLeft);
            vertices[33] = new VertexPositionTexture(bottomRightBack, textureBottomRight);
            vertices[34] = new VertexPositionTexture(topRightFront, textureTopLeft);
            vertices[35] = new VertexPositionTexture(topRightBack, textureTopRight);

            // Установка буфера вершин         
            vertexBuffer = new VertexBuffer(device, typeof(VertexPositionTexture), vertices.Length, BufferUsage.WriteOnly);
            vertexBuffer.SetData(0, vertices, 0, vertices.Length, 0);

            // Настройка камеры
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1, 100);

            // Задание эффекта
            effect = new BasicEffect(device);
            effect.World = Matrix.Identity;
            effect.View = view;
            effect.Projection = view * projection;

            // Загрузка текстуры из рисунка в объект BitmapImage         
            Stream s = Application.GetResourceStream(new Uri(textureUri, UriKind.Relative)).Stream;
            BitmapImage bmp = new BitmapImage();
            bmp.SetSource(s);

            // Передача рисунка объекту Texture2D
            Texture2D texture;
            texture = new Texture2D(device, bmp.PixelWidth, bmp.PixelHeight);
            bmp.CopyTo(texture);

            // Установка текстуры           
            effect.TextureEnabled = true;
            effect.Texture = texture;
}

Класс Cube3D содержит также метод Draw(), который может быть вызван приложением для отображения куба на поверхности рисования. При вызове метода Draw(), код передает ему соответствующее устройство GraphicsDevice:

public void Draw(GraphicsDevice device)
{
            device.SetVertexBuffer(vertexBuffer);
            device.SamplerStates[0] = SamplerState.LinearClamp;

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, vertexBuffer.VertexCount / 3);
            }
}

Затратив некоторое время на перемещение кода в отдельный класс, вы будете вознаграждены за проделанную работу упрощением кода и готовой инфраструктурой обработки событий пользовательского элемента управления. В следующем примере демонстрируется создание трех экземпляров класса Cube3D для прорисовки трех кубов разных размеров и в разных позициях. Как видите, код чистый и не загроможден деталями:

public partial class MainPage : UserControl
{
        private Cube3D cube;
        private Cube3D cube2;
        private Cube3D cube3;
        private Cube3D cube4;

        public MainPage()
        {
            InitializeComponent();

            GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
            string uri = "Silverlight 3D;component/super_man.png";

            // Создание вида, общего для всех трех кубов
            Matrix view = Matrix.CreateLookAt(new Vector3(1, 1, 3), Vector3.Zero,
              Vector3.Up);

            // Создание четырех кубов
            cube = new Cube3D(device, new Vector3(-1, -1, -1), 2, uri, 1.33f, view);
            cube2 = new Cube3D(device, new Vector3(-2, -2, 1), 1.5f, uri, 1.33f, view);
            cube3 = new Cube3D(device, new Vector3(-5, -2, -2.5f), 2, uri, 1.33f, view);
            cube4 = new Cube3D(device, new Vector3(-6, -3.2f, -0.5f), 2, uri, 1.33f, view);
        }

        private void DrawingSurface_Draw_1(object sender, DrawEventArgs e)
        {
            GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
            device.Clear(new Color(0, 0, 0));

            cube.Draw(device);
            cube2.Draw(device);
            cube3.Draw(device);
            cube4.Draw(device);
        }
}
Рисование многих копий трехмерного объекта
Пройди тесты
Лучший чат для C# программистов