Построение куба и буфер индексов

Последнее обновление: 14.11.2015

В прошлых темах мы создали трехмерную сцену с объектом, однако в роли объекта по сути выступал плоский треугольник. Теперь создадим подлинно трехмерный объект - куб.

Для создания куба лучше выбрать графические примитивы, которые не соединяются, например, TriangleList или LineList. Однако при стандартном подходе если мы будем использовать LineList, нам потребуется 12 линий, которые состоят из 24 вершин. При этом большинство вершин просто будут дублироваться.

Если мы будем использовать TriangleList, то нам потребуется 12 треугольников или 36 вершин. Хотя в реальности, чтобы создать куб, нам достаточно всего 8 вершин. То есть нам нужен механизм, который бы позволил определить индексы, которые бы использовались для выбора нужных вершин из буфера вершин при отрисовке примитивов. И MonoGame такой механизм предоставляет. Для этого нам надо использовать в программе буфер индексов вершин:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace First3DGame
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Matrix projectionMatrix;
        Matrix viewMatrix;
        Matrix worldMatrix;

        VertexPositionColor[] triangleVertices;
        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        BasicEffect effect;
		
        ushort[] lineCubeIndices =
            {
                0,1,// передние линии
                1,2,
    			2,3,
                3,0,

                4,5, // задние линии
                5,6,
                6,7,
    			7,4,

                0,4,// боковые линии
                3,7,
                1,5,
                2,6
            };

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 0, 6), Vector3.Zero, Vector3.Up);

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                (float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1, 100);

            worldMatrix = Matrix.CreateWorld(new Vector3(0f, 0f, 0f), new Vector3(0, 0, -1), Vector3.Up);

            // 8 вершин
            triangleVertices = new VertexPositionColor[8];
            triangleVertices[0] = new VertexPositionColor(new Vector3(-1, 1, 1), Color.Red);
            triangleVertices[1] = new VertexPositionColor(new Vector3(1, 1, 1), Color.Green);
            triangleVertices[2] = new VertexPositionColor(new Vector3(1, -1, 1), Color.Yellow);
            triangleVertices[3] = new VertexPositionColor(new Vector3(-1, -1, 1), Color.Blue);

            triangleVertices[4] = new VertexPositionColor(new Vector3(-1, 1, -1), Color.Red);
            triangleVertices[5] = new VertexPositionColor(new Vector3(1, 1, -1), Color.Green);
            triangleVertices[6] = new VertexPositionColor(new Vector3(1, -1, -1), Color.Yellow);
            triangleVertices[7] = new VertexPositionColor(new Vector3(-1, -1, -1), Color.Blue);

            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor),
                triangleVertices.Length, BufferUsage.None);
            vertexBuffer.SetData(triangleVertices);
            
            effect = new BasicEffect(GraphicsDevice);
            effect.VertexColorEnabled = true;

			// создаем буфер индексов
            indexBuffer = new IndexBuffer(graphics.GraphicsDevice, typeof(ushort), 24, BufferUsage.WriteOnly);
            indexBuffer.SetData<ushort>(lineCubeIndices);

            base.Initialize();
        }
        
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            
        }

        protected override void UnloadContent()
        { }
        
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.Up))
            {
                worldMatrix *= Matrix.CreateRotationX(MathHelper.ToRadians(1));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Down))
            {
                worldMatrix *= Matrix.CreateRotationX(-1 * MathHelper.ToRadians(1));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(1));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                worldMatrix *= Matrix.CreateRotationY(-1 * MathHelper.ToRadians(1));
            }

            base.Update(gameTime);
        }
        
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            
            effect.World = worldMatrix;
            effect.View = viewMatrix;
            effect.Projection = projectionMatrix;

            GraphicsDevice.SetVertexBuffer(vertexBuffer);
			// устанавливаем буфер индексов
            GraphicsDevice.Indices = indexBuffer;
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                // отрисовка примитива
                GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.LineList, 0, 0, 8, 0, 12);
            }

            base.Draw(gameTime);
        }
    }
}

Ключевым моментом здесь является создание буфера индексов. Для этого определяется массив lineCubeIndices, который определяет пары индексов вершин в буфере вершин. Каждая пара будет использоваться для создания линии.

Затем инициализируем буфер индексов:

indexBuffer = new IndexBuffer(graphics.GraphicsDevice, typeof(ushort), 24, BufferUsage.WriteOnly);
indexBuffer.SetData<ushort>(lineCubeIndices);

И в методе Draw устанавливаем индекс вершин для графического устройства и с помощью метода GraphicsDevice.DrawIndexedPrimitives() выполняем отрисовку:

GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.LineList, 0, 0, 8, 0, 12);

Этот метод принимает следующие параметры:

  • Тип графических примитивов

  • Базовая начальная вершина в буфере вершин

  • Минимальный начальный индекс в буфере индексов относительно базовой вершины

  • Количество вершин в буфере вершин

  • Начальный индекс в индексном буфере, с которого начинается считывание

  • Количество примитивов (12 линий)

В итоге на выходе мы получим куб:

Куб в MonoGame

Подобным образом мы можем создать куб из треугольников:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace First3DGame
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Matrix projectionMatrix;
        Matrix viewMatrix;
        Matrix worldMatrix;

        VertexPositionColor[] triangleVertices;
        VertexPositionColor[] cube;
        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        BasicEffect effect;

        ushort[] triangleCubeIndices =
            {
                0,1,2, // передняя сторона
    			2,3,0,

                6,5,4, // задняя сторона
    			4,7,6,

                4,0,3, // левый бок
    			3,7,4,

                1,5,6, // правый бок
    			6,2,1,

                4,5,1, // вверх
    			1,0,4,

                3,2,6, // низ
    			6,7,3,
            };
			
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 0, 6), Vector3.Zero, Vector3.Up);

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                (float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height,
                1, 100);

            worldMatrix = Matrix.CreateWorld(new Vector3(0f, 0f, 0f), new Vector3(0, 0, -1), Vector3.Up);
            
            triangleVertices = new VertexPositionColor[8];
            triangleVertices[0] = new VertexPositionColor(new Vector3(-1, 1, 1), Color.Red);
            triangleVertices[1] = new VertexPositionColor(new Vector3(1, 1, 1), Color.Green);
            triangleVertices[2] = new VertexPositionColor(new Vector3(1, -1, 1), Color.Yellow);
            triangleVertices[3] = new VertexPositionColor(new Vector3(-1, -1, 1), Color.Blue);
            triangleVertices[4] = new VertexPositionColor(new Vector3(-1, 1, -1), Color.Red);
            triangleVertices[5] = new VertexPositionColor(new Vector3(1, 1, -1), Color.Green);
            triangleVertices[6] = new VertexPositionColor(new Vector3(1, -1, -1), Color.Yellow);
            triangleVertices[7] = new VertexPositionColor(new Vector3(-1, -1, -1), Color.Blue);


            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor),
                triangleVertices.Length, BufferUsage.None);
            vertexBuffer.SetData(triangleVertices);
            
            effect = new BasicEffect(GraphicsDevice);
            effect.VertexColorEnabled = true;

            indexBuffer = new IndexBuffer(graphics.GraphicsDevice, typeof(ushort), 36, BufferUsage.WriteOnly);
            indexBuffer.SetData<ushort>(triangleCubeIndices);

            base.Initialize();
        }
        
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            
        }

        protected override void UnloadContent()
        { }
        
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.Up))
            {
                worldMatrix *= Matrix.CreateRotationX(MathHelper.ToRadians(1));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Down))
            {
                worldMatrix *= Matrix.CreateRotationX(-1 * MathHelper.ToRadians(1));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(1));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                worldMatrix *= Matrix.CreateRotationY(-1 * MathHelper.ToRadians(1));
            }
            base.Update(gameTime);
        }
        
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            
            effect.World = worldMatrix;
            effect.View = viewMatrix;
            effect.Projection = projectionMatrix;

            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            GraphicsDevice.Indices = indexBuffer;

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
            }

            base.Draw(gameTime);
        }
    }
}
Cube in MonoGame

При использовании индексов при отрисовки TriangleList нам надо учитывать порядок индексов при отрсиовки отдельного треугольника - вершины должны идти по часовой стрелке. Например, один из треугольников задней поверхности имеет индексы 6,5,4, так как при повороте к нам вершины будут идти именно в этом порядке, если мы их будет смотреть по часовой стрелке. Если мы изменим порядок индексов, то мы можем получить нежелательный эффект - видимая часть будет обращена внутрь куда, а не наружу, как сейчас. И в этом случае следствие backface culling мы не увидим вообще этот треугольник даже при повороте к нам.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850