Порядок преобразований матриц

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

При манипуляциях с матрицами - вращении, перемещении, масштабировании важен порядок применения этих действий. Например, возьмем код из прошлой темы и применим Update последовательно вращение по нажатию на клавиши A и D и перемещение по нажатию на клавиши вправо-влево:

if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
    worldMatrix *= Matrix.CreateTranslation(-.01f, 0, 0);
}
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
    worldMatrix *= Matrix.CreateTranslation(.01f, 0, 0);
}
if (Keyboard.GetState().IsKeyDown(Keys.A))
{
    worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(3));
}
if (Keyboard.GetState().IsKeyDown(Keys.D))
{
    worldMatrix *= Matrix.CreateRotationY(-1 * MathHelper.ToRadians(3));
}

Если мы начнем вращать треугольник сразу после запуска, то он будет вращаться вокруг своей оси. Однако стоит нам задействовать перемещение, то после некоторого перемещения вправо или влево треугольник продолжит вращаться вокруг первоначальной точки, но не вокруг своей оси.

Теперь изменим программу, инкапсулировав вращение и перемещение в отдельных матрицах:

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;

        BasicEffect effect;
        Matrix translationMatrix = Matrix.Identity;
        Matrix rotationMatrix = Matrix.Identity;

        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[3];
            triangleVertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.Red);
            triangleVertices[1] = new VertexPositionColor(new Vector3(1, -1, 0), Color.Green);
            triangleVertices[2] = new VertexPositionColor(new Vector3(-1, -1, 0), Color.Blue);

            // Создаем буфер вершин
            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor),
                triangleVertices.Length, BufferUsage.None);
            
            effect = new BasicEffect(GraphicsDevice);
            effect.VertexColorEnabled = true;
            
            vertexBuffer.SetData(triangleVertices);

            RasterizerState rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;
            GraphicsDevice.RasterizerState = rasterizerState;

            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.Left))
            {
                translationMatrix *= Matrix.CreateTranslation(-.01f, 0, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                translationMatrix *= Matrix.CreateTranslation(.01f, 0, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.A))
            {
                rotationMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(3));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.D))
            {
                rotationMatrix *= Matrix.CreateRotationY(-1 * MathHelper.ToRadians(3));
            }
            worldMatrix = translationMatrix * rotationMatrix;

            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);
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                GraphicsDevice.DrawUserPrimitives<VertexPositionColor>
                    (PrimitiveType.TriangleStrip, triangleVertices, 0, 1);
            }

            base.Draw(gameTime);
        }
    }
}

Итак, здесь создаются две дополнительные матрицы:

Matrix translationMatrix = Matrix.Identity;
Matrix rotationMatrix = Matrix.Identity;

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

worldMatrix = translationMatrix * rotationMatrix;

Однако если мы применим вращение или перемещение, то получим тот же результат, что и в начале: после перемещения треугольник будет вращаться вокруг определенной орбиты, а не вокруг своей оси. Потому что вращение применяется в данном случае после перемещения. Чтобы вращать объект только вокруг своей оси нам надо, наоборот, сначала применить вращение к объекту, а потом перемещение:

worldMatrix = rotationMatrix * translationMatrix;

Можно сочетать типы вращений, например, вращение вокруг своей оси и вокруг некоторой орбиты (подобно тому как Земля вращается вокруг своей оси и вокруг солнца):

worldMatrix = rotationMatrix * translationMatrix * rotationMatrix;

Аналогичная ситуация и с масштабированием. Добавим в вышеобъявленным матрицам еще одну - матрицу масштабирования:

Matrix scaleMarix = Matrix.Identity;

И изменим метод Update():

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.Left))
    {
        translationMatrix *= Matrix.CreateTranslation(-.01f, 0, 0);
    }
    if (Keyboard.GetState().IsKeyDown(Keys.Right))
    {
        translationMatrix *= Matrix.CreateTranslation(.01f, 0, 0);
    }
    if (Keyboard.GetState().IsKeyDown(Keys.A))
    {
        rotationMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(3));
    }
    if (Keyboard.GetState().IsKeyDown(Keys.D))
    {
        rotationMatrix *= Matrix.CreateRotationY(-1 * MathHelper.ToRadians(3));
    }

    if (Keyboard.GetState().IsKeyDown(Keys.OemPlus))
    {
        scaleMarix *= Matrix.CreateScale(1.01f);
    }
    if (Keyboard.GetState().IsKeyDown(Keys.OemMinus))
    {
        scaleMarix *= Matrix.CreateScale(0.99f);
    }
    worldMatrix = scaleMarix * rotationMatrix * translationMatrix;

    base.Update(gameTime);
}

Поскольку масштабирование должно применяться к самому объекту, а не результату матричных преобразований, то матрица масштабирования применяется в этом случае самой первой.

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