OpenGL: Кривые Безье

Этот урок покажет метод создания кривых Безье. Эти знания вам ОЧЕНЬ пригодятся в будущем, особенно когда вы увидите, насколько эти кривые популярны в играх (начиная с Quake). Например, мы будем использовать их для туннелей, маршрутов движения камеры, и др...

Чтобы продемонстрировать использование кривых безье, мы создадим маленькую сферу, которую сможем двигать вдоль кривой безье клавишами ВЛЕВО и ВПРАВО. Создание кривых Безье само по себе несложно, но поначалу расчеты могут вас напугать. Кривые Безье имеют как минимум три точки.

Первая точка — стартовая точка кривой. Вторая точка — контрольная точка кривой. Контрольная точка «контролирует» искривление кривой. Третья точка — конечная точка кривой. В этом уроке мы будем использовать 4 контрольные точки (включая начальную и конечную). Первая и последняя точка — начало и конец кривой, вторая и третья — для задания формы кривой.

Итак, приступим.
Исходный код взят из первого урока — «инициализация».

Файл main.cpp:

// Сначала обьявим две константы:

#define BALL_SPEED  0.02f       // Скорость кругового вращения камеры
#define MAX_STEPS   25.0f       // Число шагов, составляющих линию

// Создадим простенький класс для хранения вершин
struct CVector3
{
public:
float x, y, z;
};

float g_CurrentTime = 0.0f; // Текущая позиция сферы на кривой (между 0 и 1)

CVector3 g_vStartPoint    = {4,  0,  0};   // Стартовая точка кривой
CVector3 g_vControlPoint1 = {2,  3,  2};   // Первая контрольная точка
CVector3 g_vControlPoint2 = { 2, 3, 2};   // Вторая контрольная точка
CVector3 g_vEndPoint      = { 4,  0,  0};   // Конечная точка кривой

//////////////////////////////////////////////////////////////////////////////
//
// Расширим инициализацию:

void Init(HWND hWnd)
{
g_hWnd = hWnd;
GetClientRect(g_hWnd, &g_rRect);
InitializeOpenGL(g_rRect.right, g_rRect.bottom);

// Просто чтобы придать сфере больше реализма, включим дефолтное
// освещение и затенение. Сначала включим освещение и источник света,
// затем разрешим использовать цвет.

glEnable(GL_LIGHT0);            // Источник света
glEnable(GL_LIGHTING);          // Освещение
glEnable(GL_COLOR_MATERIAL);        // Разрешим использование цветов

}

///////////////////////////////// POINT ON CURVE \\\\*
/////
/////   Новая функция: возврщаем точку XYZ на кривой, в зависимости от «t» (от 0 до 1)
/////
///////////////////////////////// POINT ON CURVE \\\\*

CVector3 PointOnCurve(CVector3 p1, CVector3 p2, CVector3 p3, CVector3 p4, float t)
{
float var1, var2, var3;
CVector3 vPoint = {0.0f, 0.0f, 0.0f};

// Вот соль этого урока. Наже — формула для четырёх точек кривой безье:
// B (t) = P1 * ( 1 — t )^3 + P2 * 3 * t * ( 1 — t )^2 + P3 * 3 * t^2 * ( 1 — t ) + P4 * t^3
//
// Да, согласен, это не слишком простая формула, но зато она очень прямолинейна!
// Если вы были внимательны, вы увидели, что элементы в ней повторяются.
// «t» — время от 0 до 1. Вы можете подумать, что это дестанция протяжения кривой,
// и вы будете правы. P1 — P4 это 4 контрольные точки. Каждая из них имеет ассоциированные
// с ней (x, y, z). Вы заметили, что в функции встречаются несколько выражений (1 — t)?
// Чтобы немного подчистить наш код, мы занесём одинаковые выражения в переменную.
// Это немного поможет не запутатся в повторяющемся коде.

// Занесём в переменную (1-t):
var1 = 1 t;

// Занесём в переменную (1-t)^3
var2 = var1 * var1 * var1;

// Занесем в переменную t^3
var3 = t * t * t;

// Теперь, обрезав некоторые рассчеты, просто следуем нашей функции.
// Я не буду вдаватся в детали рассчетов кривых безье, поскольку есть места в интернете,
// где всё расписано очень подробно. Ниже я их приведу.
vPoint.x = var2*p1.x + 3*t*var1*var1*p2.x + 3*t*t*var1*p3.x + var3*p4.x;
vPoint.y = var2*p1.y + 3*t*var1*var1*p2.y + 3*t*t*var1*p3.y + var3*p4.y;
vPoint.z = var2*p1.z + 3*t*var1*var1*p2.z + 3*t*t*var1*p3.z + var3*p4.z;

// Теперь у нас есть точка на кривой, вернём её.
return(vPoint);
}

///////////////////////////////// DRAW SPHERE \\\\*
/////
/////   Новая функция: рисует сферу с центром в XYZ, и с переданным радиусом
/////
///////////////////////////////// DRAW SPHERE \\\\*

void DrawSphere(float x, float y, float z, float radius)
{
// Создадим сферу с помощью quadric-обьекта.

GLUquadricObj *pSphere = gluNewQuadric();

glPushMatrix();

glTranslatef(x, y, z);

// Рисуем сферу с переданным радиусом и детальностью 15.
gluSphere(pSphere, radius, 15, 15);

glPopMatrix();

gluDeleteQuadric(pSphere);
}

///////////////////////////////// RENDER SCENE \\\\*
/////
/////   Отрисовка нашей сцены
/////
///////////////////////////////// RENDER SCENE \\\\*

void RenderScene()
{

static float rotateY = 0.0f;    // Статическая переменная для вращения

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();

// Ниже мы нарисуем кривую Безье с четырьмя контрольными точками.
// Затем, используя GL_LINES, нарисуем сегменты линий для этой кривой.
// Вы можете использовать клавиши ВЛЕВО/ВПРАВО, чтобы двигать сферу
// вдоль кривой.

gluLookAt(0, 0.5f, 10.0f,   0, 0.5f, 0,   0, 1, 0);

// Выключаем освещение, чтобы четко видеть кривую
glDisable(GL_LIGHTING);
glColor3ub(0, 255, 0);

CVector3 vPoint = {0.0f, 0.0f, 0.0f};   // инициализируем CVector3 для хранения точек

// Просто для динамики и чтобы видеть всё с разных сторон, я вращаю кривую и сферу
glRotatef(rotateY, 0.0f, 1.0f, 0.0f);

rotateY += 0.1f;            // Увеличиваем вращение

// Просим OpenGL рендерить линию толщиной в 1.5

glLineWidth(1.5);

// До этого мы ещё не использовали GL_LINE_STRIP, так что давайте я обьясню.
// Вместо того, чтобы передавать первую точку линии, потом вторую, и т.д...
// мы можем сделать «полосу линий» (line strip). Это значит, что мы передаём
// только ОДНУ точку, и OpenGL соединяет их для нас. Если мы просто применим
// GL_LINES, будет отрисована кривая из _разбитых_ сегментов. Полоса линий же
// нарисует одну сплошную кривую. GL_STRIP очень удобна, а к тому же очень быстра.
// Вы можете также делать стрипы квадратов и триугольников.

glBegin(GL_LINE_STRIP);

// Здесь мы проходим вдоль кривой и получаем точки для построения
// полосы линий. Посколько наша функция PointOnCurve () принимает
// 4 точки и временное значение, мы используем цикл, начиная от 0 до
// MAX_STEPS. Так как время 0 — это начало кривой, и 1 — конец,
// мы делим 1 на количество нужных нам шагов. Меньшее значение шагов
// уменьшит число линий. По сути это просто цепочка линий, и
// чем больше шагов, тем более сглаженной будет кривая.

// Проходим по кривой, начиная с нуля и заканчивая на 1 + шаг.
// Так как используем полоску линий, нужно добавить ещё 1 точку как конечную.
for(float t = 0; t <= (1 + (1.0f / MAX_STEPS)); t += 1.0f / MAX_STEPS)
{
// Передаём 4 наших точки. Также передаём «t», текущее время от 0 до 1.
// Если передано 0 — получаем стартовую точку линии, если 1 — конечную.
// Всё, что между ними — точки на линии, например, 0.5 — точка в центре
// кривой.

// Получаем текущую точку на линии
vPoint = PointOnCurve(g_vStartPoint, g_vControlPoint1, g_vControlPoint2, g_vEndPoint, t);

// Рисуем текущую точку на расстоянии «t» вдоль по линии.
glVertex3f(vPoint.x, vPoint.y, vPoint.z);
}

glEnd();

// Теперь, нарисовав кривую, включим освещение, чтобы сферы были немного затенённые.
glEnable(GL_LIGHTING);

// Чтобы двигать сферу вдоль кривой, будем использовать ту же функцию, что и для
// отрисовки кривой, только в «t» будем передавать текущее положение сферы.
// В начале «время» сферы — 0, и это начало кривой. Нажав ПРАВУЮ стрелку, g_CurrentTime
// увеличится, и сфера сдвинется вдоль кривой с заданной скоростью (time+=BALL_SPEED).
// Когда g_CurrentTime достигает 1, останавливаемся. Если 0 — тоже останавливаемся.

// Получаем текущую точку на кривой
vPoint = PointOnCurve(g_vStartPoint, g_vControlPoint1,
g_vControlPoint2, g_vEndPoint, g_CurrentTime);

glColor3ub(255, 0, 0);          // Установим цвет в красный

// Рисуем сферу с радиусом 0.2 в текущей точке кривой
DrawSphere(vPoint.x, vPoint.y, vPoint.z, 0.2f);

glColor3ub(255, 255, 0);        // Желтый цвет

// Теперь нужно отобразить контрольные точки, чтобы вы лучше поняли, что они делают.
// Мы представим их маленькими желтыми сферами.

// Первая контрольная точка
DrawSphere(g_vControlPoint1.x, g_vControlPoint1.y, g_vControlPoint1.z, 0.1f);

// Вторая
DrawSphere(g_vControlPoint2.x, g_vControlPoint2.y, g_vControlPoint2.z, 0.1f);

SwapBuffers(g_hDC);
}

/////////////////////////////////////////////////////////////////////////////////////////////
//
// Наконец, изменим обработку клавиш:
//

case WM_KEYDOWN:

switch(wParam) {
case VK_ESCAPE:
PostQuitMessage(0);
break;

case VK_LEFT:       // Если нажата ВЛЕВО

g_CurrentTime -= BALL_SPEED;    // Уменьшаем «время» сферы по кривой

if(g_CurrentTime < 0)       // Если меньше нуля
g_CurrentTime = 0;  // устанавливаем в 0
break;

case VK_RIGHT:      // Если нажата ВПРАВО

g_CurrentTime += BALL_SPEED;    // Увеличиваем «время» сферы на кривой

if(g_CurrentTime > 1)       // Если превысили 1
g_CurrentTime = 1;  // Устанавливаем в 1
break;

}
break;

Исходные коды к уроку

Понравилась статья? Поделиться с друзьями: