- PVSM.RU - https://www.pvsm.ru -
В прошлом [1] уроке Вы научились создавать окно и собирать примеры. В этом уроке Вы научитесь рисовать объекты! Что же, прошу под кат.
Базовые уроки:
Продвинутые уроки:
Всякое:
Это будет еще один длинный урок.
OpenGL 3 позволяет с легкостью делать довольно сложные вещи, но в противовес этому заставляет совершать слишком много действий для отрисовки простого треугольника.
Не забывайте тестировать код из статьи на регулярной основе.
Если ваша программу падает сразу после запуска, возможно вы запускаете ее из неправильной директории. Прочтите еще раз раздел про настройке Visual Studio из первого урока [1].
Не будем сейчас вдаваться в детали. Вам сейчас надо создать Vertex Array Object и установить его как активный:
GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);
Выполните это один раз, после создания окна (создания контекста) и перед другими вызовами функций OpenGL.
Если хотите узнать побольше об VAO — то добро пожаловать в английскую википедию [2] под
Треугольник характеризуется тремя точками. Когда в трехмерной графике идет разговор о «точках» имеются ввиду вершины (вертексы, vertex).
Вершина имеет 3 координаты: X, Y и Z. Вы можете представлять эти 3 координаты так:
Так же еще есть другой метод, называемый Правилом Правой Руки. Если вы сожмете руку в кулак, направите оттопыренный большой палец направо, а указательный наверх.
Представлять координату Z в таком ключе довольно странно. Почему же это так? Короткий ответ: потому что сотни лет существования Математики Правой Руки, дадут Вам кучу полезных инструментов. И единственным минусом будет неинтуитивный Z.
Также стоит заметить, что вы можете двигать свою руку и координаты тоже будут двигаться. Поподробнее позже.
Так что нам понадобятся три 3D точки для того, что бы описать треугольник:
// Массив из 3 векторов, которые описывают 3 вершины
static const GLfloat g_vertex_buffer_data[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
};
Первая вершина (-1, -1, 0). Это означает, что до тех пор, пока мы ее не трансформировали, она будет отображаться в координатах (-1, -1) на экране. Что же это значит? Ось экрана находится по центру, X — направо, как всегда, Y — вверх. Пример:
Вы не можете изменить это правило. Оно записано в вашей видеокарте. Так что (-1, -1) это нижний левый угол экрана. (1, -1) это нижний правый и (0, 1) это центр сверху. Так что наш треугольник займет большую часть экрана.
Отрисовка треугольника
Следующим шагом будет передача треугольника в OpenGL. Мы сделаем это с помощью создания буффера:
// Эта переменная будет описывать наш вершинный буффер
GLuint vertexbuffer;
// Генерируем 1 буффер и размещаем его идентификатор в vertexBuffer
glGenBuffers(1, &vertexbuffer);
// Устанавливаем сгенерированный буффер, как активный
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
// Передаем наши вершины в OpenGL
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
Это надо сделать только 1 раз. Теперь в нашем основном цикле, где раньше мы ничего не отрисовывали, мы можем отрисовать треугольник:
// Первый буффер-аттрибут : вершины
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
0, // Аттрибут №0. Нет особой причины указывать именно 0, но этот номер должен совпадать с номером из шейдера.
3, // Количество
GL_FLOAT, // Тип
GL_FALSE, // Нормализован?
0, // Шаг
(void*)0 // Смещение
);
// Отрисовываем треугольник!
glDrawArrays(GL_TRIANGLES, 0, 3); // Начинаем с 0 вершины; всего 3 вершины -> 1 треугольник
glDisableVertexAttribArray(0);
Если вам повезет — то при запуске вы получите белый треугольник.
Но если у вас все также черный экран — то значит у вас отрисовывается черный треугольник на черном фоне. Что бы это исправить можно вызвать glClearColor и glClear перед каждой отрисовкой. Что изменит цвет фона. Либо задать цвет треугольнику. Чем мы сейчас и займемся.
В самой простой конфигурации нам понадобится 2 шейдера: один называется «Вершинным шейдером», а другой «Фрагментным шейдером». Вершинный шейдер вызывается для каждой вершины, в то время, когда Фрагментный шейдер вызывается для каждого сэмпла. У нас используется 4х кратный antialising, а значит у нас по 4 сэмпла на пиксель.
Шейдеры программируются на языке GLSL: Graphics Library Shader Language, который является частью OpenGL. В отличии от C или Java, GLSL компилируется во время исполнения программы, что означает, что вы должны компилировать шейдеры при каждом запуске программы.
Обычно для каждого шейдера отводится отдельный файл. К примеру у нас есть SimpleFragmentShader.fragmentshader и SimpleVertexShader.vertexshader. Расширение может быть любым. Хоть .txt или .glsl.
Вот код. Не обязательно полностью понимать код, поскольку он вызывается лишь один раз в программе, так что комментариев должно быть достаточно. Так как эта функция будет использоваться во всем уроках, она будет помещена в отдельный файл common/loadShader.cpp. Заметьте, что также как и к буфферам, к шейдерам доступ осуществляется по их индексу. Реализация спрятана в драйвере.
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){
// Создаем шейдеры
GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
// Считываем код вершинного шейдера из файла
std::string VertexShaderCode;
std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
if(VertexShaderStream.is_open()){
std::string Line = "";
while(getline(VertexShaderStream, Line))
VertexShaderCode += "n" + Line;
VertexShaderStream.close();
}else{
printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !n", vertex_file_path);
getchar();
return 0;
}
// Считываем код фрагментного шейдера из файла
std::string FragmentShaderCode;
std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
if(FragmentShaderStream.is_open()){
std::string Line = "";
while(getline(FragmentShaderStream, Line))
FragmentShaderCode += "n" + Line;
FragmentShaderStream.close();
}
GLint Result = GL_FALSE;
int InfoLogLength;
// Компилируем вершинный шейдер
printf("Compiling shader : %sn", vertex_file_path);
char const * VertexSourcePointer = VertexShaderCode.c_str();
glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
glCompileShader(VertexShaderID);
// Проверяем вершинный шейдер
glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
printf("%sn", &VertexShaderErrorMessage[0]);
}
// Компилируем фрагментный шейдер
printf("Compiling shader : %sn", fragment_file_path);
char const * FragmentSourcePointer = FragmentShaderCode.c_str();
glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
glCompileShader(FragmentShaderID);
// Проверяем фрагментный шейдер
glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
printf("%sn", &FragmentShaderErrorMessage[0]);
}
// Соединяем шейдеры в программу
printf("Linking programn");
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
glLinkProgram(ProgramID);
// Проверяем программу
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
printf("%sn", &ProgramErrorMessage[0]);
}
glDetachShader(ProgramID, VertexShaderID);
glDetachShader(ProgramID, FragmentShaderID);
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
return ProgramID;
}
Давайте начнем с вершинного шейдера. Первая строка скажет компилятору, что мы используем синтаксис OpenGL 3.
#version 330 core
Вторая строка описывает входные данные:
layout(location = 0) in vec3 vertexPosition_modelspace;
Давайте опишем эту строку по подробнее:
Функция, вызываемая для каждой вершины, называется main, прямо как в C:
void main(){
Наша main функция будет просто устанавливать позицию вершины на координаты, указанные в буфере. Так что если мы передает (1, 1) одна из вершин треугольника будет в верхнем правом углу экрана. В следующих уроках мы познакомимся с более интересными вычислениями, которые можно производить над входными данными.
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w = 1.0;
}
«gl_Position» одна из нескольких встроенных переменных. Вы должны передать ей какое-то значение. Все остальное — не обязательно. (Про «все остальное» мы поговорим в 4 уроке)
Для нашего первого фрагментного шейдера мы реализуем нечто очень простое: раскраску каждого фрагмента в красный цвет. (Помните, что на каждый пиксель 4 фрагмента, поскольку мы используем 4х AA)
#version 330 core
out vec3 color;
void main(){
color = vec3(1,0,0);
}
Да, vec3(1, 0, 0) означает красный. Дело в том, что компьютерный экраны представляют цвет, как комбинацию из Красного, Зеленого и Синего (RGB). Так что (1, 0, 0) означает самый яркий красный, нет зеленого и нет синего.
Перед главным циклом вызываем функцию LoadShaders.
// Создаем и компилируем нашу GLSL программу из шейдеров
GLuint programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );
Теперь внутри главного цикла в начале очищаем экран. Функция glClearColor(0.0f, 0.0f, 0.4f, 0.0f) установит цвета фона на синий. Для очистки экрана вызывается:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
А затем говорим OpenGL, какой шейдер мы используем:
// Используем наш шейдер
glUseProgram(programID);
// Отрисовка треугольника...
Вот и все. Вот наш красный треугольник на синем фоне.
В следующем уроке мы поговорим о трансформациях: как настроить камеру, двигать объекты и т.д.
Автор: Megaxela
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/opengl/164178
Ссылки в тексте:
[1] прошлом: https://habrahabr.ru/post/306650/
[2] в английскую википедию: https://www.opengl.org/wiki/Vertex_Specification#Vertex_Array_Object
[3] Источник: https://habrahabr.ru/post/306678/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.