Наш очередной длинный урок.
А вот и результат:
OpenGL 3 позволяет делать сложные вещи достаточно просто, но
в то же время такое простое действие, как вывод треугольника - сделать с нуля, не так то и
легко.
Не забывайте копипастить код в ваш проект и пробовать!!!
VAO
Не буду вдаваться в подробности, но нам необходимо создать Vertex
Array Object и установить его как текущий:
GLuint VertexArrayID;
glGenVertexArrays(1,
&VertexArrayID);
glBindVertexArray(VertexArrayID);
Сделайте это сразу же после создания OpenGL окна(после инициализации
OpenGL контекста) и перед любым из вызовов OpenGL.
Если вам сильно хочется узнать побольше про VAO, поищите
описание в интернете, для текущего урока это не сильно важно.
Экранные Координаты
Треугольник можно задать тремя точками. Когда мы говорим про
точки в 3д пространстве, мы обычно употребляем термин «вершина». Вершина
содержит 3 координаты: X,Y и Z. Вы можете представить для себя эти координаты
так:
- X - вправо
- Y - вверх
- Z - за спину
Но есть еще более наглядный способ представить себе эту
систему координат: правило правой руки.
- X – большой палец правой руки
- Y – указательный палец правой руки
- Z – средний палец правой руки.
Теперь если вы направите свой большой палец направо,
указательный вверх, то средний будет показывать на вас.
Почему Z такое странное? Очень просто: просто за стони лет
существования математики и правила правой руки, было создано много методов
облегчающих расчеты которые ориентируются на этот неинтуитивный Z.
Кстати, заметьте, вы можете свободно двигать рукой, и ваши X,
Y и Z тоже будут двигаться за вами. Но об этом позже.
И так, нам нужно три точки в 3д пространстве, чтобы
нарисовать треугольник.
// Массив из 3
векторов которые будут представлять 3 вершины
static const GLfloat g_vertex_buffer_data[] = {
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). Что значит
-1,-1? Видеокарта трактует экранные координаты так, что центр экрана это 0, а
крайние точки: -1 и 1 по X и Y. Вот что у нас получается на широкоформатном
мониторе:
Неудобно, да, но поменять это нельзя. Поэтому наш
треугольник будет расположен по таким координатам: (-1,-1) это нижний левый
угол. (1,-1) – нижний правый. (0,1) – верхняя центральная точка.
Рисуем наш треугольник
Нашим следующим шагом будет скормить эти вершины в OpenGL. Для
этого необходимо сначала создать буфер:
// Идентификатор вершинного
буфера
GLuint vertexbuffer;
// Сначала генерируем OpenGL буфер и сохраняем указатель на него
в 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);
Эти операции нужно выполнить лишь один раз.
А теперь в главном цикле программы, где мы в прошлом уроке
не рисовали ничего, теперь мы нарисуем наш прекрасный треугольничек.
// Первый буфер атрибутов:
вершины
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
0, // Атрибут 0. Сакрального
смысла в нуле нет, но число должно совпадать с числом в шейдере
3, // количество
GL_FLOAT, // тип
GL_FALSE, // нормализировано ли?
0, // шаг
(void*)0 // смещение
в буфере
);
// Рисуем треугольник !
glDrawArrays(GL_TRIANGLES, 0, 3); //Начиная с вершины 0 и рисуем 3 штуки. Всего
=> 1 треугольник
glDisableVertexAttribArray(0);
А вот и результат:
Однако этот треугольник очень скучный, так как не цветной.
Можно попробовать его разукрасить в веселенький красный цвет. Раскраску можно
сделать с помощью шейдеров.
Шейдеры
В самом простом варианте нам понадобится два шейдера:
- Вершинный Шейдер. Это шейдер который будет вызываться для каждой вершины.
- Фрагментный Шейдер. Это шейдер который вызывается при растеризации на каждый пиксель. Если у нас 4х сглаживание, то фрагментный шейдер вызовется 4 раза на каждый пиксель.
Шейдеры в OpenGL программируются на языке GLSL: GL Shader Language,
который есть частью OpenGL. В отличии от С или Java,GLSL компилируется
программой при исполнении, что значит, что при каждом старте приложения ваши
шейдеры будут перекомпилироваться.
Обычно разные шейдеры хранят в разных файлах. Поэтому и у
нас будет два файла SimpleFragmentShader.fragmentshader и
SimpleVertexShader.vertexshader. Расширение файла не важно, вы можете писать
хоть .txt хоть .glsl.
А вот и код. Вам не нужно полностью его понимать, так как
скорее всего он будет написан лишь раз в вашей программе, поэтому думаю, что
моих комментариев будет достаточно. Этот код одинаковый для всех уроков,
поэтому я вынес его в один файл: common/loadShader.cpp. Заметьте, шейдеры как и
буферы не доступны прямо из программы: у нас есть лишь ID. Содержимое спрятано
внутри драйвера и в памяти видеокарты.
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();
}
// читаем фрагментный шейдер из файла
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 : %s\n", 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);
std::vector<char> VertexShaderErrorMessage(InfoLogLength);
glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL,
&VertexShaderErrorMessage[0]);
fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);
// Компилируем
фрагментный шейдер
printf("Compiling shader : %s\n", 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);
std::vector<char> FragmentShaderErrorMessage(InfoLogLength);
glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL,
&FragmentShaderErrorMessage[0]);
fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);
fprintf(stdout, "Linking program\n");
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
glLinkProgram(ProgramID);
// Устанавливаем
параметры
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
std::vector<char> ProgramErrorMessage( max(InfoLogLength, int(1))
);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL,
&ProgramErrorMessage[0]);
fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
return ProgramID;
Вершинный Шейдер
Давайте сначала напишем наш вершинный шейдер.
Первая строчка будет указанием компилятору, что мы хотим
использовать синтаксис OpenGL 3:
#version 330 core
Вторая строчка объявляет входные данные:
layout(location = 0)
in vec3 vertexPosition_modelspace;
Тут нужно объяснение поподробнее:
- “vec3” это вектор состоящий из трех компонентов в GLSL. Это почти то же самое(но не то), что и glm::vec3 что мы использовали для задания треугольника. Нужно не забывать, что если мы используем трехкомпонентный вектор в С++, то обязательно нужно использовать трехкомпонентный вектор и в GLSL.
- “layout(location=0)” – указание для компилятора, в каком из буферов будет находится атрибут vertexPosition_modelspace. Каждая вершина может иметь множество атрибутов: Позиция, цвета, текстурные координаты, и еще кучу других вещей. GLSL не знает ничего про цвет, он видит лишь vec3. А наша задача – рассказать, что из себя представляет каждый входящий буфер. Это мы делаем устанавливая layout в то же самое значение, что и glVertexAttribPointer. Мы там поставили «0», но с таким же успехом могли поставить и 12(но это число не может быть больше чем glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &v)). Самое главное, чтобы эти числа были одинаковы по обе стороны.
- «vertexPosition_modelspace» - идентификатор «переменной». Можете назвать как угодно. В нашем случае будет содержать положение вершины которая обрабатывается в данный момент вершинным шейдером.
- «in» значит, что это входящий параметр. Вскоре у нас будет и «out».
Точно так же как и в С в GLSL есть функция main:
void main(){
Тут все проще простого. Мы просто устанавливаем положение
вершины в значение которое пришло к нам из буфера. Так что если нам пришло
(1,1) одна из вершин треугольника будет в верхнем правом углу экрана. В
последующих уроках я покажу, что в этом шейдере можно делать штуки и поинтереснее.
gl_Position.xyz = vertexPosition_modelspace;
gl_Position.w
= 1.0;
}
gl_Position – это одна из встроенных переменных и мы просто
обязаны установить какое-то значение в неё. Все остальные – по вашему желанию.
Мы рассмотрим некоторые из «все остальные» в уроке 4.
Фрагментный Шейдер
В нашем первом фрагментном шейдере мы выполним очень простую
операцию: установим цвет каждого фрагмента в красный цвет(не забудьте, что так
как у нас 4х сглаживание, то на каждый пиксель у нас будет 4 фрагмента).
#version 330 core
out vec3 color;
void main(){
color
= vec3(1,0,0);
}
Да, vec3(1,0,0) – это красный цвет. Так происходит потому,
что компьютер видит цвет, как совокупность Красного, Зеленого и Голубого. Поэтому
(1,0,0) это Полностью Красный, нет ни зеленого ни голубого.
Объединяем все вместе
Перед главным циклом вызываем функцию LoadShaders:
// Загружаем и компилируем
GLSL программу
GLuint programID = LoadShaders(
"SimpleVertexShader.vertexshader",
"SimpleFragmentShader.fragmentshader" );
А теперь внутри главного цикла сначала очищаем экран,
закрашивая его в темносиний(0,0f,0.0f,0.4f,0.0f).
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT);
А теперь говорим OpenGL, что нам нужно воспользоваться
услугами шейдера:
// Подключаем шейдер
glUseProgram(programID);
// Рисуем треугольник...
В следующем уроке мы будем изучать трансформации: как
установить камеру и двигать объекты...
Видимо, в примере без шейдеров ошибка: белый треугольник не нарисуется, так как нигде не выставлялся белый цвет. У меня на чёрном фоне рисовался чёрный треугольник. Который, соответственно, не было видно, пока я не сделал:
ОтветитьУдалитьglClearColor(0.1, 0.1, 0.1, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
так там и не надо выставлять белый цвет, если нет фрагментного шейдера, который отвечает за цвет пикселей, примитив будет либо черный либо белый
УдалитьСобственно дошел до второго урока (правда я пытаюсь использовать glfw 3 версии там у некоторых функций поменяли название и некоторые параметры но 1 урок получилось переделать под 3 версию)
ОтветитьУдалитьа вот со 2 ым пока проблема. Я так понимаю еще какая та зависимость пока добавлял к стандартным (glfw3.lib;glfw3dll.lib;opengl32.lib;glu32.lib;glew32.lib) эти либы но похоже нужно еще что то.
1>------ Построение начато: проект: windowOpenGLFW, Конфигурация: Debug Win32 ------
1>LINK : warning LNK4098: библиотека по умолчанию "MSVCRT" противоречит использованию других библиотек; используйте параметр /NODEFAULTLIB:library
1>mail.obj : error LNK2019: ссылка на неразрешенный внешний символ "unsigned int __cdecl LoadShaders(char const *,char const *)" (?LoadShaders@@YAIPBD0@Z) в функции _main
1>C:\Users\Admin\Dropbox\OpenGL\glfwWindow\windowOpenGLFW\Debug\windowOpenGLFW.exe : fatal error LNK1120: неразрешенных внешних элементов: 1
сначала добавил shader.cpp и shader.hpp в c:\Program Files\Microsoft Visual Studio 11.0\VC\include\common\
ОтветитьУдалитьбыла ошибка описанная ранее ^.
после чего добавил эти два файла рядом с main.cpp и в визуал студии добавил к проекту папку, а к папке 2 этих элемента после чего проект запустился но вылетело исключение на этой строчек
glGenVertexArrays(1, &VertexArrayID);
сообщение
Необработанное исключение по адресу 0x00000000 в windowOpenGLFW.exe: 0xC0000005: нарушение прав доступа при исполнении по адресу 0x00000000.
Судя по ошибке, что-то где-то записывается по нулевому неинициалицированному указателю.
ОтветитьУдалитьВряд ли ошибка конкретно в "glGenVertexArrays(1, &VertexArrayID);", скорее всего ты пропустил что-то где-то до этого. Не работал с glfw3, может быть там как-то нужно инициализировать его по другому? Почитай в доке.
https://yadi.sk/i/e9lqxDh6oJfps
ОтветитьУдалитьУ меня выдавалась такая ошибка. После каких-о манипуляций я вроде бы избавился от парочки ошибок. Уже и не помню каких.. Суть такова: я порыскал в интернете и нашёл следующий код:
Fragment:
#version 330 core
out vec4 color;
void main(){
color = vec4(1,0,0,1);
}
Vertex:
#version 330 core
layout(location = 0) in vec3 Position;
void main(){
gl_Position = vec4(0.5 * Position.x, 0.5 * Position.y, Position.z, 1.0);
}
и О ЧУДО! Треугольник перекрасился.
как правильно указать путь к шейдеру и куда сам шейдер ложить?
ОтветитьУдалить