Раздел «Технологии программирования».ManagedDirectX:

Более подробно о тонкостях программной реализации

Работа с камерой в DirectX

Поскольку проект было решено написать с использованием технологии .NET framework by Microsoft, то в качестве системы рендеринга был выбран графический конвейер Managed DirectX, что позволило в полной мере использовать как преимущество высокуровневой модели DirectX, так и преимущества .NET платформы и управляемого кода.

Пример простейшего кода, который рисует чайник - наиболее стандартный пример работы графической подсистемы:

using System.Drawing;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

public class GraphicsClass : GraphicsSample
{
   private float x = 0.0f;
   private float y = 0.0f;
   private Point destination = new Point(0, 0);
   private Mesh teapot = null;
   // Дополнительный код...

   protected override void Render()
   {
      // Очистка буфера с заливкой фона синим цветом
      device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Blue,
         1.0f, 0);

      // Начало сцены
      device.BeginScene();

      device.Lights[0].Enabled = true;

      // Подготовка матриц проекции, мировых координат
      // и координат окна вывода
      Matrix m = new Matrix();

      if( destination.Y != 0 )
         y += DXUtil.Timer(DirectXTimer.GetElapsedTime) *
              (destination.Y * 25);

      if( destination.X != 0 )
         x += DXUtil.Timer(DirectXTimer.GetElapsedTime) *
              (destination.X * 25);

      m = Matrix.RotationY(y);
      m *= Matrix.RotationX(x);

      device.Transform.World = m;
      device.Transform.View = Matrix.LookAtLH(
          new Vector3( 0.0f, 3.0f,-5.0f ),
          new Vector3( 0.0f, 0.0f, 0.0f ),
          new Vector3( 0.0f, 1.0f, 0.0f ) );
      device.Transform.Projection = Matrix.PerspectiveFovLH(
          (float)Math.PI / 4, 1.0f, 1.0f, 100.0f );

      // Рендеринг чайника
      teapot.DrawSubset(0);

      // Конец сцены
      device.EndScene();
   }
}

Наиболее интересной частью проекта с точки зрения реализации трехмерной графики был создание системы управления камерами в таком виде, чтобы это было удобно как с точки зрения пользовательского интерфейса, так и с точки зрения конечного программиста реализующего систему камер и их мониторинга-управления.

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

_Примечание: используется также собственный класс Point3f - трехмерной точки, логика которого полностью эквивалентна классу Point2f в языке java.

using System;
using Microsoft.DirectX;

namespace Reallity
{
   /// <summary>
   /// Summary description for Camera.
   /// </summary>
   public class Camera
   {
      /// <summary>
      /// Point from  which we watch
      /// </summary>
      private Point3f eye ;
      /// <summary>
      /// Vectors of camera's frame 
      /// </summary>
      private Point3f u ,v ,n ;
      /// <summary>
      /// values for setting camera
      /// </summary>
      float viewAngle = 0,aspect = 0,nearDist = 0,farDist = 0;
      /// <summary>
      /// Specific choice of graphics
      /// </summary>
      Graph graphics = null;

      public Camera(Graph graphics)
      {
         this.graphics = graphics;
      }
      /// <summary>
      /// Initial setting of camera
      /// </summary>
      /// <param name="extEye">Point from which we watch</param>
      /// <param name="extLook">Point wihch we look at</param>
      /// <param name="extUp">For direction of the camera</param>
      public void SetUp(Point3f extEye, Point3f extLook, Point3f extUp)
      {         
         Set(extEye, extLook, extUp);
         Watch();
      }
      public void Set(Point3f extEye, Point3f extLook, Point3f extUp)
      {
         eye   =   extEye.Clone();
         extEye.Sub(extLook);
         n   =   extEye.Clone();
         n.Invert();
         u   =   extUp.VectorMult(n);
         v   =   n.VectorMult(u); 
         n.Normalize();
         u.Normalize();
         v.Normalize();
         //defaul values
         aspect=InitialData.ASPECT;
         viewAngle=(float)InitialData.ANGLE_OF_CAMERA;
         nearDist   =   InitialData.NEAR;
         farDist   =   InitialData.FAR;
      }
      public void Watch()
      {
         Microsoft.DirectX.Direct3D.Device device = (Microsoft.DirectX.Direct3D.Device)Graph.graphicalDevice;
         device.Transform.Projection   =   Matrix.PerspectiveFovLH(this.viewAngle,
            this.aspect,this.nearDist,this.farDist);
         this.SetModelViewMatrix();
      }
      /// <summary>
      /// For right and left rotating 
      /// </summary>
      /// <param name="angle">Angle</param>
      public void Yaw(float angle)
      {
         float cs = (float)System.Math.Cos(3.14159265/180*angle);//Math.PI
         float sn = (float)System.Math.Sin(3.14159265/180*angle);
         Point3f t=   u;
         u.x   =   cs*t.x+sn*n.x;
         u.y   =   cs*t.y+sn*n.y;
         u.z   =   cs*t.z+sn*n.z;
         n.x   =   -sn*t.x+cs*n.x;
         n.y   =   -sn*t.y+cs*n.y;
         n.z   =   -sn*t.z+cs*n.z;
         this.SetModelViewMatrix();               
      }
      /// <summary>
      /// 
      /// </summary>
      /// <param name="angle"></param>
      public void Pitch(float angle)
      {
         float cs = (float)System.Math.Cos(Math.PI/180*angle);
         float sn = (float)System.Math.Sin(Math.PI/180*angle);
         Point3f t = v;
         v.x = cs*t.x+sn*n.x;
         v.y = cs*t.y+sn*n.y;
         v.z = cs*t.z+sn*n.z;
         n.x = -sn*t.x+cs*n.x;
         n.y = -sn*t.y+cs*n.y;
         n.z = -sn*t.z+cs*n.z;
         this.SetModelViewMatrix();   
      }
      /// <summary>
      /// For moving of camera
      /// </summary>
      /// <param name="delU">Right</param>
      /// <param name="delV">Up</param>
      /// <param name="delN">Forward</param>
      public void Slide(float delU, float delV, float delN)
      {
         float delX = delU*u.x+delV*v.x+delN*n.x;
         float delY = delU*u.y+delV*v.y+delN*n.y;
         float delZ = delU*u.z+delV*v.z+delN*n.z;
         eye.x += delX;
         eye.y += delY;
         eye.z += delZ;
         this.SetModelViewMatrix();   
      }
      /// <summary>
      /// Move camera, not affect by y-coordinate
      /// </summary>
      /// <param name="point2f"></param>
      public void Move(/*float x,float y*/Point3f point3f)
      {
         
         eye.Copy(point3f);
         //eye.x = point3f.x;
         //eye.z = point3f.y;
         //eye.Copy(point2f);
         /*eye.x=x;
         //eye.y+=0;
         eye.z=y;*/
         this.SetModelViewMatrix();
      }
      /// <summary>
      /// Set matrix of values
      /// </summary>
      private void SetModelViewMatrix()
      {
         float dx = -eye.ScalarProduct(u);
         float dy = -eye.ScalarProduct(v);
         float dz = -eye.ScalarProduct(n);
         Matrix mat = new Matrix();
         mat.M11 = u.x;   mat.M12 = v.x;   mat.M13 = n.x;   mat.M14 = 0;//dx;
         mat.M21 = u.y;   mat.M22 = v.y;   mat.M23 = n.y;   mat.M24 = 0;//dy;
         mat.M31 = u.z;   mat.M32 = v.z;   mat.M33 = n.z;   mat.M34 = 0;//dz;
         mat.M41 = dx;   mat.M42 = dy;   mat.M43 = dz;   mat.M44 = 1;
         Microsoft.DirectX.Direct3D.Device device = (Microsoft.DirectX.Direct3D.Device)Graph.graphicalDevice;
         device.Transform.View = mat;
         
            
         return;
      }
   }
}

Видно, что наиболее сложная часть кода управления это метод void SetModelViewMatrix(), который вставляет новые параметры в видовую матрицу, таким образом меняя точку зрения камеры. Видно, как реализованы методы, которые меняют положение камеры (Slide), и её направления (Pitch - тангаж и Yaw - рысканье) ну и некоторые другие методы, которые обеспечивают дополнительную лёгкость обращения с камерой.

Для облегчения понимания кода мы приведём несколько иллюстраций:

* Камера для создания перспективных видов сцены:
Clip.jpg

* Attaching coordinate system to the camera:
Clip_2.jpg

* Построение векторов u,v,n:
Clip_3.jpg

Построение трехмерных объектов

Конвейер Direct3D следует рассматривать как набор алгоритмов, выполняющих операции над трехмерными геометрическими величинами (3D geometric quantities), каковыми в случае Direct3D являются предопределенные вершины (vertices) и примитивы (primitives). Основное предназначение конвейера — преобразование геометрических данных в изображение, формируемое на экране. Этап тесселяции в Direct3D — разбиение на треугольники фиксированного набора предопределенных примитивов более высокого порядка, в том числе треугольных (triangle patches), прямоугольных (rectangle patches) и полигональных участков поверхностей (N patches) (хотя треугольные участки поверхности остаются наиболее распространенной формой). В настоящее время этап тесселяции нельзя программировать, поэтому Direct3D не предоставляет никаких механизмов для генерации геометрических данных на основе программируемых процедур.

Конвейер DirectX:
directx_01.gif

Поэтому мы для создания трехмерных объектов использовали определенный формат вершин записанный в вершинном буффере. Примером может служить участок кода, для создания образа дороги в буфере. Мы используем формат вершин CustomVertex.PositionNormal, чтобы настроить модель освещения на наиболее близкую к реальности.

      private void InitRoad(Point2f roadLocation,RoadInfo roadInfo)
      {
         Microsoft.DirectX.Direct3D.Device device = (Microsoft.DirectX.Direct3D.Device)Graph.graphicalDevice;
         CustomVertex.PositionNormal[] vertexOnRoad = new CustomVertex.PositionNormal[4];
         //
         float a = roadInfo.length;
         //
         vertexOnRoad[0]=   new CustomVertex.PositionNormal(roadLocation.x,0,roadLocation.y,0,1,0);
         vertexOnRoad[1]=   new CustomVertex.PositionNormal(roadLocation.x+a,0,roadLocation.y,0,1,0);
         vertexOnRoad[2]=   new CustomVertex.PositionNormal(roadLocation.x,0,roadLocation.y+ roadInfo.numStrip*roadInfo.width,0,1,0);
         vertexOnRoad[3]=   new CustomVertex.PositionNormal(roadLocation.x+a,0,roadLocation.y+roadInfo.numStrip*roadInfo.width,0,1,0);
         VertexBuffer verRoad = new VertexBuffer(typeof(CustomVertex.PositionNormal), 4, device, Usage.WriteOnly, CustomVertex.PositionNormal.Format, Pool.Default);
         CustomVertex.PositionNormal[] pVerRoad = (CustomVertex.PositionNormal[])verRoad.Lock(0,LockFlags.Discard/*0*/);
         //
         for(int j = 0;j<4;j++)
            pVerRoad[j] = vertexOnRoad[j];
         verRoad.Unlock();
         this.verticesOnRoad = verRoad;
         int numSepOnSoleRoad = (int)(roadInfo.length/(Car.Length)); 
         CustomVertex.PositionNormal[,] vertexOnSeparatings = new CustomVertex.PositionNormal[numSepOnSoleRoad*(roadInfo.numStrip-1),4];
         VertexBuffer[] verSeparatings = new VertexBuffer[numSepOnSoleRoad*(roadInfo.numStrip-1)];
         for(int k=1;k<=roadInfo.numStrip-1;k++)
         {
            for(int i=1;i<=numSepOnSoleRoad;i++)
            {
               vertexOnSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1),0]=new CustomVertex.PositionNormal(roadLocation.x+Car.Length*i+Car.Length/4,0.01f,roadLocation.y+roadInfo.width*k-roadInfo.width/20,0,1,0);
               vertexOnSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1),1]=new CustomVertex.PositionNormal(roadLocation.x+Car.Length*i-Car.Length/4,0.01f,roadLocation.y+roadInfo.width*k-roadInfo.width/20,0,1,0);
               vertexOnSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1),2]=new CustomVertex.PositionNormal(roadLocation.x+Car.Length*i+Car.Length/4,0.01f,roadLocation.y+roadInfo.width*k+roadInfo.width/20,0,1,0);
               vertexOnSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1),3]=new CustomVertex.PositionNormal(roadLocation.x+Car.Length*i-Car.Length/4,0.01f,roadLocation.y+roadInfo.width*k+roadInfo.width/20,0,1,0);
               verSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1)] = new VertexBuffer(typeof(CustomVertex.PositionNormal), 4, device, Usage.WriteOnly, CustomVertex.PositionNormal.Format,Pool.Default);
               CustomVertex.PositionNormal[] pVerSeparatings= (CustomVertex.PositionNormal[])verSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1)].Lock(0,LockFlags.Discard/*0*/);
               for(int j=0;j<4;j++)
                  pVerSeparatings[j] = vertexOnSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1),j];
               verSeparatings[(k-1)*(numSepOnSoleRoad)+(i-1)].Unlock();
            }
         }
         this.verticesOnSeparatings = verSeparatings;
      }

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

      public void DrawRoad(RoadInfo roadInfo)
      {
         Microsoft.DirectX.Direct3D.Device device = (Microsoft.DirectX.Direct3D.Device)Graph.graphicalDevice;
         device.Transform.World = Matrix.Identity;
         Material materialOnRoad = new Material();
         materialOnRoad.Ambient = Drawing.COLOR_OF_ROAD;
         materialOnRoad.Diffuse = Drawing.COLOR_OF_ROAD;
         materialOnRoad.Specular = Drawing.COLOR_OF_ROAD;
         device.Material = materialOnRoad;
         VertexBuffer verRoad = (VertexBuffer)this.verticesOnRoad;
         device.SetStreamSource(0,verRoad,0);
         device.VertexFormat = CustomVertex.PositionNormal.Format;
         device.DrawPrimitives(PrimitiveType.TriangleStrip,0,2);
         //}
         Material materialOnSeparating = new Material();
         materialOnSeparating.Ambient = Drawing.COLOR_OF_STRIPES;
         materialOnSeparating.Diffuse = Drawing.COLOR_OF_STRIPES;
         materialOnSeparating.Specular = Drawing.COLOR_OF_STRIPES;
         device.Material = materialOnSeparating;
         VertexBuffer[] verSeparatings = (VertexBuffer[])this.verticesOnSeparatings;
         for(int i=0;i<verSeparatings.Length;i++)
         {
            device.SetStreamSource(0,verSeparatings[i],0);
            device.VertexFormat = CustomVertex.PositionNormal.Format;
            device.DrawPrimitives(PrimitiveType.TriangleStrip,0,2);
         }
         
      }

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

Более подробно можно почитать в MSDN, к примеру хорошей обзорной статьёй будет вот эта статья

-- BasanovTimofey? - 13 Jun 2005

Attachment sort Action Size Date Who Comment
Clip.jpg manage 45.3 K 30 May 2005 - 07:22 BasanovTimofey? Камера для создания перспективных видов сцены
Clip_2.jpg manage 21.3 K 30 May 2005 - 07:26 BasanovTimofey? Attaching coordinate system to the camera
Clip_3.jpg manage 18.2 K 30 May 2005 - 07:30 BasanovTimofey? Построение векторов u,v,n
directx_01.gif manage 19.8 K 30 May 2005 - 07:43 BasanovTimofey? Конвейер DirectX