Array ( )
Вход:




Главная | OpenGL | GLSL | AI | Сеть | Примеры | Библиотека

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;







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




Комментарии:

Войдите, чтобы оставить комментарий:












Яндекс.Метрика
 Яндекс цитирования.