OpenGL программирование/Введение в современный OpenGL

Материал из Викиучебника
Перейти к: навигация, поиск
Ваша первая программа

Введение[править]

Большинство документаций по OpenGL описывают функции, которые в настоящее время устарели, в частности "фиксированный конвейер". OpenGL 2.0 и поздние версии содержат программируемый конвейер, где программируемая часть осуществляет шейдеры, написан в GLSL , базируемый на языке ANSI C.

Этот документ предназначен для людей, которые только начали изучать OpenGL и хотят использовать его с самого начала изучения. Программируемые конвейеры более гибкие, но менее удобные в использовании, чем фиксируемые конвейеры. Вы однако убедитесь, что мы начнем с простого кода в первую очередь. Мы будем использовать подход похожий на подход NeHe's в учебнике OpenGL 1.x, с примерами и учебными материалами, чтобы лучше понять теорию программируемых конвейеров позднее.

Вначале вершинные массивы и шейдеры будут похожи на мучительную работу, по сравнению со старым непосредственным режимом и фиксированным конвейер рендеринга. Однако, в конце концов, особенно если вы будете использовать буфер объектов, ваш код станет более чистым, а графика будет быстрее.

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

Распространяйте этот документ среди своих друзей! Викиучебник заслуживает большего вклада и признания:)

Заметки:

  • Вероятно можно смешать фиксированный конвейер и программируемый конвейер до некоторой степени, но фиксированный конвейер является устаревшим и недоступен во всех OpenGL ES 2.0 (или его производной WebGL), таким образом мы не будем использовать это.
  • В настоящие время OpenGL 3 и 4, в которые внедрили особенность геометрических шейдеров, но на этот раз это всего лишь эволюция света по сравнению с предыдущей версией. Поскольку он недоступен на мобильных платформах от 2012, сейчас мы сконцентрируемся на OpenGL 2.0.

Основные библиотеки[править]

Мы установим и используем следующие библиотеки:

  • OpenGL
  • GLEW (OpenGL Extension Wrangler) : действующие прототипы на C (и позже загружаемые расширение OpenGL)
  • GLUT (OpenGL Utility Toolkit) : создает окно OpenGL достаточно простым и кроссплатформенным способом

Под Debian and Ubuntu GNU/Linux, есть подходящие пакеты:

  • libgl1-mesa-dev (ищите в репозитории вашего линукс по "mesa")
  • libglew1.5-dev (... "glew")
  • freeglut3-dev (... "glut")

Удостоверьтесь, что вы установили основные инструменты компилятора, используйте этот мета-пакет:

  • build-essential

Т.к. вам придется подключать эти библиотеки непосредственно к вашей OpenGL-программе, проверьте APIs, Libraries and acronyms, чтобы понять, как именно могут называться отличающиеся названия пакетов с ними.

Заметка: Мы выбрали GLUT, потому что это минимальный портативный пакет, который мы смогли достать. Мы не будем использовать продвинутые особенности какого-либо продукта, и почти весь наш код будет построен на OpenGL, таким образом вы не должны иметь трудностей при переходе на другие, более сложные или не-кроссплатформенные библиотеки - такие как Qt, WGL, GLFW, SDL и SFML впоследствие.

Отображения треугольника в 2D[править]

Давайте начнем с простого :) Вместо того что бы пытаться разобраться со сложной программой (что займет большое количество времени), займёмся написанием простой, но функцианальной программы, которую мы сможем улучшать шаг за шагом.

Треугольник самая базовая единица в 3D програмировании. На самом деле всё что вы видите в видео играх состоит из треугольников! Маленькие, текстурированные треугольники, но треугольники тем не менее :)

Для отображения треугольника в программируемом конвеере, мы должны, как минимум:

  • Создать 'Makefile', чтобы собрать наше приложение.
  • Инициализировать OpenGL и вспомогательные библиотеки
  • Заполнить массив с координатами 3-х вершин (т.е. 3D точек) треугольника
  • В программе GLSL для:
    • вершинного шейдера: передать каждую вершину индивидуально, и он будет вычислять на экране 2D координаты
    • фрагментный (пиксельный) шейдер: OpenGL пройдет каждый пиксель, который содержится в нашем треугольнике, и будет вычислить его цвет.
  • пройти вершины 'вершинного шейдера'

Сборка приложения[править]

Очень легко настроить 'Make' для нашего примера. Запишите это в файле 'Makefile':

LDLIBS=-lglut -lGLEW -lGL
all: triangle

Для компиляции приложения, наберите в терминале:

make

Если применить немного больше фантазии, то Makefile будет выгядеть следующим образом:

CC=g++
  1. прим: на Mac OS X, начиная с версии Xcode 5.0, вместо gcc/g++ поставляется другой компилятор:
  2. CC=llvm
LDLIBS=-lglut -lGLEW -lGL
all: triangle
clean:
        rm -f *.o triangle
.PHONY: all clean

Это позволяет использовать команду 'make clean", которая удаляет все промежуточные файлы, которые могут быть сгенерированны.

The .PHONY rule is there to tell make that "all" and "clean" are not files, so it will not get confused if you actually have files named like that in the same directory.

If you want to use a different programming environment, see the Настройка OpenGL section.

Инициализация[править]

Давайте создадим файл triangle.c:

/* Использование стандартного вывода for fprintf */
#include <stdio.h>
#include <stdlib.h>
/* Используйте glew.h вместо gl.h, чтобы иметь доступ ко всем объявленным GL прототипам */
#include <GL/glew.h>
/* Использабание GLUT библиотеки для базовой настройки окна */
#include <GL/glut.h>
/* ПОЗЖЕ ЗДЕСЬ ДОБАВИМ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ*/
 
int init_resources(void)
{
  /* ЗАПОЛНИМ ПОЗЖЕ */
  return 1;
}
 
void onDisplay()
{
  /* ЗАПОЛНИМ ПОЗЖЕ */
}
 
void free_resources()
{
  /* ЗАПОЛНИМ ПОЗЖЕ */
}
 
int main(int argc, char* argv[])
{
  /* Glut-related инициализация функции */
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
  glutInitWindowSize(640, 480);
  glutCreateWindow("My First Triangle");
 
  /* Extension wrangler initialising */
  GLenum glew_status = glewInit();
  if (glew_status != GLEW_OK)
  {
    fprintf(stderr, "Error: %s\n", glewGetErrorString(glew_status));
    return EXIT_FAILURE;
  }
 
  /* Когда все init функции выполняться без ошибок,
      программе можно инициализировать ресурсы */
  if (1 == init_resources())
  {
    /* Мы можем выводить на дисплей it, если всё пойдёт хорошо*/
    glutDisplayFunc(onDisplay);
    glutMainLoop();
  }
 
  /* Если выход из программы осуществляется обычным путём,
       то освободим ресурсы и успешно завершим выполнение программы */
  free_resources();
  return EXIT_SUCCESS;
}

В init_resources, мы создадим нашу GLSL программу. В onDisplay, мы будем рисовать треугольник. В free_resources, мы уничтожим GLSL программу (прим. переводчика деструктор).

Массив вершин[править]

Наш первый треугольник будет отображаться в 2D - мы будем рассматривать нечто более сложное в ближайшее время. Мы описываем 2D (x,y) координаты треугольник для 3-х точек. По умолчанию, OpenGL координаты изменяются в диапазоне [-1, 1] .

  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8
  };

Сейчас давайте просто запомним этот код, а запишем его в программу чуть позже.

Примечание: координаты находятся между -1 и +1, но наше окно не квадрат!В следующих уроках мы увидим, как исправить пропорции.

Вершинный шейдер[править]

(вольный перевод)Эта GLSL программа, которая поможет каждой точке нашего массива, по очереди, найти своё место на экране. В нашем случае точки и так имеют 2D координаты, так что мы не будем их изменять. Наша программа GLSL, выглядит так:

#version 120
attribute vec2 coord2d;
void main(void) {
  gl_Position = vec4(coord2d, 0.0, 1.0);
}
  • #version 120 означает GLSL v1.20 в OpenGL 2.1.
  • OpenGL ES 2 GLSL также основан на GLSL v1.20, но его версия 1.00 (#version 100). [1].
  • coord2d - это текущая вершина; Это входная переменная и нам нужно её объявить в нашем Си коде.
  • gl_Position - это окончательная позиция экрана; Это built-in(т.е. встроенная) выходная переменная.
  • the vec4 takes our x and y coordinates, then 0 for the z coordinate. The last one, w=1.0 is for homogeneous coordinates (используется для трансформационных матриц).

Теперь нам нужно скомпилировать OpenGL шейдер. Начните init_resources с вышеупомянутого main:

/*
Функция: init_resources
Получает: void
Возвращает: int
Эта функция создает все GLSL related stuff
explained in this example.
Возвращает 1, если все в порядке, или 0 с отображаемой ошибкой.
*/
int init_resources(void)
{
  GLint compile_ok = GL_FALSE, link_ok = GL_FALSE;
 
  GLuint vs = glCreateShader(GL_VERTEX_SHADER);
  const char *vs_source = 
#ifdef GL_ES_VERSION_2_0
    "#version 100\n"  // OpenGL ES 2.0
#else
    "#version 120\n"  // OpenGL 2.1
#endif
    "attribute vec2 coord2d;                  "
    "void main(void) {                        "
    "  gl_Position = vec4(coord2d, 0.0, 1.0); "
    "}";
  glShaderSource(vs, 1, &vs_source, NULL);
  glCompileShader(vs);
  glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_ok);
  if (0 == compile_ok)
  {
    fprintf(stderr, "Error in vertex shader\n");
    return 0;
  }

Мы передаем в качестве источника строку в glShaderSource (позже мы будем читать код шейдеров иначе и удобнее). Мы указываем тип GL_VERTEX_SHADER.

Фрагментный шейдер[править]

Теперь когда OpenGL знает положение 3х точкек на экране, он заполнит пространство между ними, чтобы создать треугольник. Для каждого пикселя между 3мя точками будет вызываться пиксельный шейдер. В нашем пиксельном шейдере, мы укажем, что хотим, чтобы цвет каждого пикселя был синим:

#version 120
void main(void) {
  gl_FragColor[0] = 0.0;
  gl_FragColor[1] = 0.0;
  gl_FragColor[2] = 1.0;
}

Мы должны собрать его аналогичным образом с типом GL_FRAGMENT_SHADER. Давайте продолжим нашу процедуру init_resources:

  GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
  const char *fs_source =
    "#version 120           \n"
    "void main(void) {        "
    "  gl_FragColor[0] = 0.0; "
    "  gl_FragColor[1] = 0.0; "
    "  gl_FragColor[2] = 1.0; "
    "}";
  glShaderSource(fs, 1, &fs_source, NULL);
  glCompileShader(fs);
  glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_ok);
  if (!compile_ok) {
    fprintf(stderr, "Error in fragment shader\n");
    return 0;
  }

GLSL программа[править]

Программа GLSL комбинирует вершинных и фрагментный шейдер. Обычно они работают вместе, и вершинный шейдер может даже передавать дополнительную информацию, в пиксельный шейдер. Создать глобальную переменную ниже #include для хранения обработки программы:

GLuint program;

Вот как связать вершиный и фрагментный шейдер в программе. Продолжаем процедуру init_resources:

  program = glCreateProgram();
  glAttachShader(program, vs);
  glAttachShader(program, fs);
  glLinkProgram(program);
  glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
  if (!link_ok) {
    fprintf(stderr, "glLinkProgram:");
    return 0;
  }

Перебор вершин треугольника в вершинном шейдере[править]

Мы упоминали, что мы проходим каждую вершину треугольника вершинным шейдером с помощью атрибута coord2d. Вот как объявить его в коде C.

Во-первых, давайте создадим вторую глобальную переменную:

GLint attribute_coord2d;

Вот так будет выглядеть конец нашей процедуры init_resources:

  const char* attribute_name = "coord2d";
  attribute_coord2d = glGetAttribLocation(program, attribute_name);
  if (attribute_coord2d == -1) {
    fprintf(stderr, "Could not bind attribute %s\n", attribute_name);
    return 0;
  }
 
  return 1;
}

Теперь мы можем передать вершины нашего треугольника в вершинный шейдер. Давайте напишем процедуру onDisplay. Каждая секция объясняется в комментариях:

void onDisplay()
{
  /* Создаём белый фон */
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT);
 
  glUseProgram(program);
  glEnableVertexAttribArray(attribute_coord2d);
  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8,
  };
  /* Опишем наш массив вершин OpenGL (формат не определяется автоматически) */
  glVertexAttribPointer(
    attribute_coord2d, // атрибут
    2,                 // количество элементов на вершине, здесь две - (х, у).
    GL_FLOAT,          // тип каждого элемента
    GL_FALSE,          // принять наши параметры как есть
    0,                 // никаких дополнительных данных между каждой позицией
    triangle_vertices  // указатель на массив C.
  );
 
 
  /* Вставьте каждый элемент в buffer_vertices для вершинных шейдеров */
  glDrawArrays(GL_TRIANGLES, 0, 3);
  glDisableVertexAttribArray(attribute_coord2d);
 
  /* Вывести результат */
  glutSwapBuffers();
}

glVertexAttribPointer говорит OpenGL получить каждую вершину из буфера данных, созданной в init_resources и передать его в вершинный шейдер. Эти вершины определяют для каждой точки положение на экране, образуют треугольник, окрашевая пиксели с помошью пиксельного шейдера.

Примечание: в следующем уроке мы введем Vertex Buffer Objects, чуть более сложный и более новый способ хранения вершин графической карты.

Остаётся только часть free_resources, для чистки ресурсов, когда мы вышли из программы. Это не критично в данном конкретном случае, но хорошо бы структурировать приложение как-то так:

void free_resources()
{
  glDeleteProgram(program);
}

Наша первая OpenGL 2.0 программа завершена!

В случае неудачи[править]

Следующий урок добавляет большую устойчивость к нашему первому минималистичному коду. Можете попробывать код tut02 (см ссылку на код, приведенный ниже).

Эксперимент![править]

Другой пиксельный шейдер для нашей первой программы

Не стесняйтесь экспериментировать с этим кодом:

  • попробуйте сделать квадрат, используя 2 треугольника
  • попробуйте изменить цвета
  • можете почитать справочные страницы OpenGL для каждой функции, которую мы использовали:
  • попробуйте использовать этот код в пиксельном шейдере - для чего это нужно?
gl_FragColor[0] = gl_FragCoord.x/640.0;
gl_FragColor[1] = gl_FragCoord.y/480.0;
gl_FragColor[2] = 0.5;

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

Примечания[править]

  1. Cf. OpenGL ES Shading Language 1.0.17 Specification. Khronos.org. Проверено 2011-09-10 г. - The OpenGL ES Shading Language (also known as GLSL ES or ESSL) is based on the OpenGL Shading Language (GLSL) version 1.20

Шаблон:OpenGL Programming BottomNav