Внимание!

Эта публикация перенесена в раздел уроков по адресу Урок по созданию класса Камеры.
К ней прикреплена новая отдельная ветка комментариев форума, которую вы можетет найти после текста публикации.
Обсуждение публикации рекуомендуется вести по новому адресу, который указан выше.

Урок по созданию класса Камеры

Камера.

В этом уроке мы научимся создавать класс - камера.
Для этого мы сначала попробуем разобраться с командой библиотеки Glu - GluLookAt. В нашем случаи Glu.gluLookAt.Она принимает три набора
аргументов, которые задают точку наблюдения, прицельную точку (точку, на которую
направлена камера) и направление, которое следует считать верхним.



Пример:
/*http://esate.ru, OniKy*/


       Glu.gluLookAt(Position.x, Position.y, Position.z, //Позиция самой камеры
                View.x, View.y, View.z,         //Куда смотрим
                Up.x, Up.y, Up.z);            //Верх камеры


Вроде бы все просто. Теперь создадим отдельный класс в нашем приложении - Camera. Сразу подключим пространства и создадим Vertex3D - структуру, где будут храниться X,Y,Z векторов.

/*http://esate.ru, OniKy*/


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Tao.FreeGlut;
using Tao.OpenGl;
using Tao.Platform.Windows;

namespace Engine
{
   class Camera
   {
      private struct Vector3D
      {
        public float x, y, z;
      };
   }
}


Объявим вектора которые будем использовать:
/*http://esate.ru, OniKy*/


      private Vector3D mPos;   //Вектор позиции камеры
      private Vector3D mView;  //Куда смотрит камера
      private Vector3D mUp;   //Вектор верхнего направления
      private Vector3D mStrafe;//Вектор для стрейфа (движения влево и вправо) камеры.


Далее мы создадим метод, который будет нам возвращать перпендикулярный вектор от 3ех переданных векторов. Два любых вектора образуют плоскость, от которого мы и ищем перпендикуляр. Это все нам понадобиться для того чтоб мы смогли реализовать Стрейф.
/*http://esate.ru, OniKy*/


      private Vector3D Cross(Vector3D vV1, Vector3D vV2, Vector3D vVector2)
      {
        Vector3D vNormal;
        Vector3D vVector1;
        vVector1.x = vV1.x - vV2.x;
        vVector1.y = vV1.y - vV2.y;
        vVector1.z = vV1.z - vV2.z;

        // Если у нас есть 2 вектора (вектор взгляда и вертикальный вектор), 
        // у нас есть плоскость, от которой мы можем вычислить угол в 90 градусов.
        // Рассчет cross'a прост, но его сложно запомнить с первого раза. 
        // Значение X для вектора = (V1.y * V2.z) - (V1.z * V2.y)
        vNormal.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));

        // Значение Y = (V1.z * V2.x) - (V1.x * V2.z)
        vNormal.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));

        // Значение Z = (V1.x * V2.y) - (V1.y * V2.x)
        vNormal.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));

        // *ВАЖНО* Вы не можете менять этот порядок, иначе ничего не будет работать.
        // Должно быть именно так, как здесь. Просто запомните, если вы ищите Х, вы не
        // используете значение X двух векторов, и то же самое для Y и Z. Заметьте,
        // вы рассчитываете значение из двух других осей, и никогда из той же самой.

        // Итак, зачем всё это? Нам нужно найти ось, вокруг которой вращаться. Вращение камеры
        // влево и вправо простое - вертикальная ось всегда (0,1,0). 
        // Вращение камеры вверх и вниз отличается, так как оно происходит вне 
        // глобальных осей. Достаньте себе книгу по линейной алгебре, если у вас 
        // её ещё нет, она вам пригодится.

        // вернем результат.
        return vNormal;
      }


Далее создадим два метода который будут работать над векторами - Magnitude и Normalize.

Magnitude - Возвращает величину вектора.
/*http://esate.ru, OniKy*/


      private float Magnitude(Vector3D vNormal)
      {
        // Это даст нам величину нашей нормали, 
        // т.е. длину вектора. Мы используем эту информацию для нормализации
        // вектора. Вот формула: magnitude = sqrt(V.x^2 + V.y^2 + V.z^2)   где V - вектор.

        return (float)Math.Sqrt((vNormal.x * vNormal.x) +
              (vNormal.y * vNormal.y) +
              (vNormal.z * vNormal.z));
      }


и Normalize - Возвращает нормализированный вектор, длинна которого==1, это делает все рассчеты проще.
/*http://esate.ru, OniKy*/


private Vector3D Normalize(Vector3D vVector)
      {
        // Вы спросите, для чего эта ф-я? Мы должны убедиться, что наш вектор нормализирован.
        // Вектор нормализирован - значит, его длинна равна 1. Например,
        // вектор (2,0,0) после нормализации будет (1,0,0).

        // Вычислим величину нормали
        float magnitude = Magnitude(vVector);

        // Теперь у нас есть величина, и мы можем разделить наш вектор на его величину.
        // Это сделает длинну вектора равной единице, так с ним будет легче работать.
        vVector.x = vVector.x / magnitude;
        vVector.y = vVector.y / magnitude;
        vVector.z = vVector.z / magnitude;

        return vVector;
      }


И так, теперь начнем работать с глобальными методами, которые мы сможем вызвать. Для начала нам нужно установить позицию, взгляд и вертикальный вектор камеры, в этом нам поможет следующий метод:
/*http://esate.ru, OniKy*/


      public void Position_Camera(float pos_x, float pos_y, float pos_z,
           float view_x, float view_y, float view_z,
           float up_x, float up_y, float up_z)
      {
        mPos.x = pos_x;//Позиция камеры
        mPos.y = pos_y;//
        mPos.z = pos_z;//
        mView.x = view_x;//Куда смотрит, т.е. взгляд
        mView.y = view_y;//
        mView.z = view_z;//
        mUp.x = up_x;//Вертикальный вектор камеры
        mUp.y = up_y;//
        mUp.z = up_z;//
      }


Теперь дадим возможность крутить камеру вокруг своей оси, аля вид от 1го лица =). Передовая в метод переменную speed, будем управлять силой поворота, а также и направлением, если положительная переменная, то вправо, а отрицательная, как вы уже догадались, налево =).

/*http://esate.ru, OniKy*/


      public void Rotate_View(float speed)
      {
        Vector3D vVector;// Полчим вектор взгляда
        vVector.x = mView.x - mPos.x;
        vVector.y = mView.y - mPos.y;
        vVector.z = mView.z - mPos.z;

        

        mView.z = (float)(mPos.z + Math.Sin(speed) * vVector.x + Math.Cos(speed) * vVector.z);
        mView.x = (float)(mPos.x + Math.Cos(speed) * vVector.x - Math.Sin(speed) * vVector.z);
      }


Если сделали вид от первого лица, то сделаем и от 3го =). Чтобы реализовать вращение камеры, мы будем использовать axis-angle вращение.
Я должен предупредить, что формулы для рассчета вращения не очень просты, но занимают не много кода. Axis-angle вращение позволяет нам вращать точку в пространстве вокруг нужной оси. Это значит, что мы можем взять нашу точку взгляда (m_vView) и вращать вокруг нашей позиции. Чтобы лучше понять следующие рассчеты, советую вам посмотреть детальное описание: http://astronomy.swin.edu.au/~pbourke/geometry/rotate/

Также это функция реализована чтоб в переменную angle вы передавали угол в градусах.

/*http://esate.ru, OniKy*/


public void Rotate_Position(float angle, float x, float y, float z)
      {
        mPos.x = mPos.x - mView.x;
        mPos.y = mPos.y - mView.y;
        mPos.z = mPos.z - mView.z;

        Vector3D vVector = mPos;
        Vector3D AVector;

        float SinA = (float)Math.Sin(Math.PI * angle / 180.0);
        float CosA = (float)Math.Cos(Math.PI * angle / 180.0);

        // Найдем новую позицию X для вращаемой точки 
        AVector.x = (CosA + (1 - CosA) * x * x) * vVector.x;
        AVector.x += ((1 - CosA) * x * y - z * SinA) * vVector.y;
        AVector.x += ((1 - CosA) * x * z + y * SinA) * vVector.z;

        // Найдем позицию Y 
        AVector.y = ((1 - CosA) * x * y + z * SinA) * vVector.x;
        AVector.y += (CosA + (1 - CosA) * y * y) * vVector.y;
        AVector.y += ((1 - CosA) * y * z - x * SinA) * vVector.z;

        // И позицию Z 
        AVector.z = ((1 - CosA) * x * z - y * SinA) * vVector.x;
        AVector.z += ((1 - CosA) * y * z + x * SinA) * vVector.y;
        AVector.z += (CosA + (1 - CosA) * z * z) * vVector.z;

        mPos.x = mView.x + AVector.x;
        mPos.y = mView.y + AVector.y;
        mPos.z = mView.z + AVector.z;
      }


Займемся перемещением камеры =). Самое первое как вы поняли, нам нужна возможность перемещать камеру вперед и назад в зависимости куда смотрит камера. Следующий метод позволит нам это делать =).
/*http://esate.ru, OniKy*/

      public void Move_Camera(float speed) //Задаем скорость
      {
        Vector3D vVector; //Получаем вектор взгляда
        vVector.x = mView.x - mPos.x;
        vVector.y = mView.y - mPos.y;
        vVector.z = mView.z - mPos.z;

        vVector.y = 0.0f; // Это запрещает камере подниматься вверх
        vVector = Normalize(vVector);

        mPos.x += vVector.x * speed;
        mPos.z += vVector.z * speed;
        mView.x += vVector.x * speed;
        mView.z += vVector.z * speed;
      }


Сразу напишем Стрейф:
/*http://esate.ru, OniKy*/


      public void Strafe(float speed)
      {
        // добавим вектор стрейфа к позиции
        mPos.x += mStrafe.x * speed;
        mPos.z += mStrafe.z * speed;

        // Добавим теперь к взгляду
        mView.x += mStrafe.x * speed;
        mView.z += mStrafe.z * speed;
      }


Но для стрейфа, нужна каждый момент времени по новому вычислять перпендикуляр. Для этого напишем этот метод:
/*http://esate.ru, OniKy*/

      public void update()
      {
        Vector3D vCross = Cross(mView, mPos, mUp);

        //Нормализуем вектор стрейфа
        mStrafe = Normalize(vCross);
      }


Теперь добавим возможность смотреть камере вверх и вниз =).
/*http://esate.ru, OniKy*/

      public void upDown(float speed)
      {
        mPos.y += speed;
      }


Ну и конечно нужен метод который у нас будет обновлять взгляд и позицию камеры:
/*http://esate.ru, OniKy*/

      public void Look()
      {
        Glu.gluLookAt(mPos.x, mPos.y, mPos.z, //Нами ранее обсуждаемая команда =)
                  mView.x, mView.y, mView.z,
                  mUp.x, mUp.y, mUp.z);
      }


И так, на всякий случай еще допишем методы который смогут получать позиции в пространстве как и камеры, так и взгляда:
/*http://esate.ru, OniKy*/


      public double getPosX() //Возвращает позицию камеры по Х
      {
        return mPos.x;
      }

      public double getPosY() //Возвращает позицию камеры по Y
      {
        return mPos.y;
      }

      public double getPosZ() //Возвращает позицию камеры по Z
      {
        return mPos.z;
      }

      public double getViewX() //Возвращает позицию взгляда по Х
      {
        return mView.x;
      }

      public double getViewY() //Возвращает позицию взгляда по Y
      {
        return mView.y;
      }

      public double getViewZ() //Возвращает позицию взгляда по Z
      {
        return mView.z;
      }


Ну вот и все =) Класс написан, и может быть использован. Как же его применять? Итак. Создадим новый проект. Объявим нужные нам библиотеки и пространства имен, добавим SimpleOpenGlControl и переименуем его в AnT. Также добавим Timer. Сразу объявим новый наш класс:
/*http://esate.ru, OniKy*/


Camera cam = new Camera();


Напишем метод инициализации OpenGl:
/*http://esate.ru, OniKy*/

private void InitGL()
      {
        Glut.glutInit();
        Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_DOUBLE | Glut.GLUT_DEPTH);

        Gl.glClearColor(255, 255, 255, 1);

        Gl.glViewport(0, 0, AnT.Width, AnT.Height);

        Gl.glMatrixMode(Gl.GL_PROJECTION);
        Gl.glLoadIdentity();

        Glu.gluPerspective(45, (float)AnT.Width / (float)AnT.Height, 0.1, 200);

        Gl.glMatrixMode(Gl.GL_MODELVIEW);
        Gl.glLoadIdentity();

        Gl.glEnable(Gl.GL_DEPTH_TEST);
        Gl.glEnable(Gl.GL_LIGHTING);
        Gl.glEnable(Gl.GL_LIGHT0);

        Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
        Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
        Gl.glEnable(Gl.GL_BLEND);
        Gl.glEnable(Gl.GL_LINE_SMOOTH);
        Gl.glLineWidth(1.0f);

        cam.Position_Camera(0, 6, -15, 0, 3, 0, 0, 1, 0); //Вот тут в инициализации
//укажем начальную позицию камеры,взгляда и вертикального вектора.
      }


В загрузку формы кинем нашу инициализацию:
/*http://esate.ru, OniKy*/

      private void Form1_Load_1(object sender, EventArgs e)
      {
        InitGL();
      }


Также напишем метод рисования:
/*http://esate.ru, OniKy*/


private void Draw()
      {
        Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

        Gl.glLoadIdentity();
        Gl.glColor3i(255, 0, 0);

        cam.Look(); //Обновляем взгляд камеры

        Gl.glPushMatrix();

           DrawGrid(30, 1);//Нарисуем сетку

        Gl.glPopMatrix();
       
        Gl.glFlush();

        AnT.Invalidate();
      }


Метод рисования Сетки:
/*http://esate.ru, OniKy*/

 private void DrawGrid(int x, float quad_size)
      {
        //x - количество или длина сетки, quad_size - размер клетки
        Gl.glPushMatrix(); //Рисуем оси координат, цвет объявлен в самом начале
        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixColorOX);
        Gl.glTranslated((-x * 2) / 2, 0, 0);
        Gl.glRotated(90, 0, 1, 0);
        Glut.glutSolidCylinder(0.02, x * 2, 12, 12);
        Gl.glPopMatrix();

        Gl.glPushMatrix();
        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixColorOZ);
        Gl.glTranslated(0, 0, (-x * 2) / 2);
        Glut.glutSolidCylinder(0.02, x * 2, 12, 12);
        Gl.glPopMatrix();

        Gl.glPushMatrix();
        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixColorOY);
        Gl.glTranslated(0, x / 2, 0);
        Gl.glRotated(90, 1, 0, 0);
        Glut.glutSolidCylinder(0.02, x, 12, 12);
        Gl.glPopMatrix();

        Gl.glBegin(Gl.GL_LINES);

        Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixOXOYColor);

        // Рисуем сетку 1х1 вдоль осей
        for (float i = -x; i <= x; i += 1)
        {
           Gl.glBegin(Gl.GL_LINES);
           // Ось Х
           Gl.glVertex3f(-x * quad_size, 0, i * quad_size);
           Gl.glVertex3f(x * quad_size, 0, i * quad_size);

           // Ось Z
           Gl.glVertex3f(i * quad_size, 0, -x * quad_size);
           Gl.glVertex3f(i * quad_size, 0, x * quad_size);
           Gl.glEnd();
        }
      }


Напишем метод который будет считывать мышку:
/*http://esate.ru, OniKy*/

private void AnT_MouseDown(object sender, MouseEventArgs e)
      {
        if (e.Button == MouseButtons.Left)
           mouseRotate = true; //Если нажата левая кнопка мыши

        if (e.Button == MouseButtons.Middle)
           mouseMove = true; //Если нажата средняя кнопка мыши

        myMouseYcoord = e.X; //Передаем в нашу глобальную переменную позицию мыши по Х
        myMouseXcoord = e.Y;
      }

      private void AnT_MouseUp(object sender, MouseEventArgs e)
      {
        mouseRotate = false; 
        mouseMove = false;
      }

      private void AnT_MouseMove(object sender, MouseEventArgs e)
      {
        myMouseXcoordVar = e.Y;
        myMouseYcoordVar = e.X;
      }


И конечно нам надо обработать мышку, вот и сам метод:
/*http://esate.ru, OniKy*/

private void mouse_Events()
      {
        if (mouseRotate == true) //Если нажата левая кнопка мыши
        {
           AnT.Cursor = System.Windows.Forms.Cursors.SizeAll; //меняем указатель
       
           cam.Rotate_Position((float)(myMouseYcoordVar - myMouseYcoord), 0, 1, 0); //крутим камеру, в моем случае это от 3го лица

           rot_cam_X = rot_cam_X + (myMouseXcoordVar - myMouseXcoord);
           if ((rot_cam_X > -40) && (rot_cam_X < 40))
              cam.upDown(((float)(myMouseXcoordVar - myMouseXcoord)) / 10);

           myMouseYcoord = myMouseYcoordVar;
           myMouseXcoord = myMouseXcoordVar;
        }
        else
        {
           if (mouseMove == true)
           {
              AnT.Cursor = System.Windows.Forms.Cursors.SizeAll;

              cam.Move_Camera((float)(myMouseXcoordVar - myMouseXcoord) / 50);
              cam.Strafe(-((float)(myMouseYcoordVar - myMouseYcoord) / 50));

              myMouseYcoord = myMouseYcoordVar;
              myMouseXcoord = myMouseXcoordVar;

           }
           else
           {
              AnT.Cursor = System.Windows.Forms.Cursors.Default;//возвращаем курсор
           };
        };
      }


и дописываем в таймер =)
/*http://esate.ru, OniKy*/

           mouse_Events();
           cam.update();
           Draw();

Вот и собственно все =) На данном примере мы сделали возможность перемещаться по сцене как в редакторе моделей 3ds Max, можно использовать это также в играх типа Стратегий. Спасибо всем =)</cut>
0       1612        17.01.2011        27

Внимание!

Эта публикация перенесена в раздел уроков по адресу Урок по созданию класса Камеры.
К ней прикреплена новая отдельная ветка комментариев форума, которую вы можетет найти после текста публикации.
Обсуждение публикации рекуомендуется вести по новому адресу, который указан выше.

0  
17.01.2011 00:00:00
Отличный урок, рекомендую вам перенести его в блог «новые уроки...». Для этого надо присоединится к блогу (), потом отредактировать урок и указать публичный блог для публикации.
0  
18.01.2011 00:00:00
Извини, в уроке вроде не хватает объявления некоторых переменных:
MatrixColorOX
MatrixColorOZ
MatrixColorOY…
mouseRotate
mouseMove
myMouseYcoord
myMouseXcoordVar
rot_cam_X… и т.д.

И было бы классно исходники к уроку приложить).(на почту ANVI скинуть он зальет)
0  
22.01.2011 00:00:00
я думаю эти переменные и самим можно написать =) а исходник, на днях кину Анви )
0  
24.01.2011 00:00:00
буду ждать )
0  
25.01.2011 00:00:00
Замечательный урок! Спасибо.
0  
28.01.2011 00:00:00
урок класный, но думаю что та часть где ты камеру прикручиваешь лишняя, надо было просто словами написать что менять надо для поворота, и все бы поняли, хотя для ленивых это даже плюс
0  
30.01.2011 00:00:00
Для чайников (таких как я) — недостающие переменные:
private void DrawGrid(int x, float quad_size)
{
//x - количество или длина сетки, quad_size - размер клетки

float[] MatrixColorOX = {1,0,0,1};
float[] MatrixColorOY = {0,1,0,1};
float[] MatrixColorOZ = {0,0,1,1};
...

Далее:
bool mouseRotate, mouseMove = false;
int myMouseYcoord, myMouseXcoord, myMouseXcoordVar, myMouseYcoordVar;
float rot_cam_X;
private void AnT_MouseDown(object sender, MouseEventArgs e)
{
....
0  
31.01.2011 00:00:00
Исходники бы(
0  
01.02.2011 00:00:00
ленивый я смотрю ты =))
0  
01.02.2011 00:00:00
Ну не без этого)
0  
01.02.2011 00:00:00
скинул я исходники Анви) надо подождать когда закинет чтоб скачивать можно было)
0  
01.03.2011 00:00:00
блин, а я и забыл. Можно еще раз?
0  
02.02.2011 00:00:00
да я и сам очень ленивый =))
все лень делать, но как то пытаюсь бороться с этим =))
0  
24.02.2011 00:00:00
у меня все странно, ошибок нет, при запуске все прорисовывается, но на этом все, больше ничего не происходит(т.е нельзя двигаться там)
0  
24.02.2011 00:00:00
писал на 2010м
0  
07.03.2011 00:00:00
У меня просто черный экран. Ничего не прорисоввывется. Тоже 2010.
0  
07.03.2011 00:00:00
отлично! просто таймер нужно было активировать)
0  
09.03.2011 00:00:00
слушай, можешь кинуть ссылку на исходник в ЛС?
0  
09.03.2011 00:00:00
все заработало, потом стала ошибка вылзить

D:\пользователь\Documents\Visual Studio 2008\Projects\курсовая\курсовая\camera.cs(17,30): error CS0523: Struct member 'camera.Vector3D.mPos' of type 'camera.Vector3D' causes a cycle in the struct layout
это ппц какой то
0  
09.03.2011 00:00:00
я наверно уже всех достал, но все же, я решил все со своими ошибками. теперь пытаюсь реализовать приближение и удаление камеры за счет колесика, но ни как не получается обработать это событие
0  
09.03.2011 00:00:00
мне конкретно нужен зум, чтобы ось ось вращения не менялась при этом
0  
10.03.2011 00:00:00
только там без колёсика
0  
20.04.2011 00:00:00
сделала все, как написано, камера на мышь не реагирует…
может быть вы правда исходники дадите, чтобы было понятно, где ошибка?
0  
20.04.2011 00:00:00
разобралась.
0  
14.12.2011 00:00:00
Немогу понять вот эту строчку:
Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE, MatrixOXOYColor);

Ошибка на MatrixOXOYColor.

Она не где не обьявлялась. Что делать?
0  
14.12.2011 00:00:00
Кстати, ничего не прорисовывает.

Просто черное окно.

Таймер включен.
0  
31.03.2012 00:00:00
Где можно исходники или лучше exe взять. Хочется посмотреть как все это работает
^