Ответы пользователя по тегу OpenGL
  • Ошибка при ручном высчитывании перспективной проекции и точки на экране?

    @MarkusD
    все время мелю чепуху :)
    Для начала тут нужно разобраться с пространствами.
    В каком пространстве рисуется серая платформа? В каком пространстве задана область обзора камеры для платформы? В каком пространстве задана область проекции камеры для платформы?
    В каком пространстве рисуется красный квадрат? В каком пространстве задана область обзора камеры для квадрата? В каком пространстве задана область проекции камеры для квадрата?

    А следом надо разобраться с порядком переходов между этими пространствами.
    У тебя используется два пространства мировых координат и две камеры, результат проекции которых сводится в одну поверхность презентации. Чтобы из точки мирового пространства плоскости получить точку мирового пространства квадрата, тебе нужно взять прямую матрицу WVP пространства плоскости и умножить ее на обратную матрицу WVP пространства квадрата.
    Результирующая матрица будет матрицей перехода из мирового пространства плоскости в мировое пространство квадрата. Умножая вершины плоскости на эту матрицу, ты будешь переводить их координаты в пространство, в котором тебе в этих вершинах надо рисовать красные квадраты.
    Ответ написан
    Комментировать
  • C++ CMake Как исправить ошибку?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тут проблема не в CMake и не в CLion. Это проблема MinGW и кодировки файлов с исходным кодом.

    Файлы сейчас сохранены в какой-то другой кодировке, когда GCC в составе MinGW по умолчанию ожидает кодировку UTF-8.
    Достаточно будет сконвертировать файлы в кодировку UTF-8 и MinGW начнет их переваривать.
    Ответ написан
    Комментировать
  • Как получить данные из буфера глубины при включенном multisample в OpenGL?

    @MarkusD
    все время мелю чепуху :)
    Я конечно могу найти луч и потом искать пересечения со всеми объектами на сцене и рассчитывать координату z уже из этого, но мне кажется это слишком ресурсозатратно

    Однократное использование glReadPixels в сотни раз более ресурсозатратнее обычного оптимизированного алгоритма проверки на пересечение луча с объектами сцены.

    В разработке игр на мобильных устройствах алгоритм проверки пересечений запускается даже не на каждые 10 пикселей экрана, а просто постоянно в каждом кадре, пока игрок не поднимет палец с экрана. И у людей при этом проблем с производительностью нет даже на 96+ FPS.
    В продуктовых решениях мы никогда не используем glReadPixels в покадровых рутинах. Ни в мобильной разработке, ни в десктопной, ни под консоли.

    Вот ответ, который тебе поможет разобраться с переводом координат мышки в 3D пространство сцены.
    Чтобы снизить трудоемкость проверки на пересечение с объектами сцены, используй или Q-Tree, или Oc-Tree, или BSP, или Агломерацию.
    Чтобы снизить трудоемкость проверки на пересечение конкретного объекта, используй AABB, OOBB и все те же Q-Tree/Oc-Tree или BSP для отсечения лишних полигонов модели.

    Это все позволит определять объект сцены под мышкой значительно быстрее чем всего один вызов glReadPixels с проходом по матрице пикселей.
    Ответ написан
    1 комментарий
  • Ошибка в Visual studio opengl. Как решить?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ошибка LNK2019 относится к стадии линковки и означает что среди всех объектов линковки так и не нашлось определение обозначенной в ошибке функции.

    __imp_glClear и __imp_glDrawArrays - это стандартные функции OpenGL, определены они в библиотеке opengl32.lib, которую и требуется подключить как внешнюю зависимость к твоему проекту.

    Зависимости в проект подключаются через свойства проекта Visual Studio.
    Ответ написан
    5 комментариев
  • При добавлении рисунка с прозрачной областью неправильная цветопередача?

    @MarkusD
    все время мелю чепуху :)
    Изображение у тебя в файле хранится в формате BGRA, а в видеопамять ты его передаешь как RGBA.
    В результате каналы R и B оказываются поменяны местами. Поэтому и вывод такой.

    Тебе нужно или в видеопамять изображение передавать как BGRA, или руками поменять R и B каналы каждого пикселя после загрузки изображения из файла.
    Идеальный вариант - это сменить формат файла так, чтобы изображение в нем хранилось в формате RGBA.
    Ответ написан
  • Где взять заголовочные файлы и библиотеки opengl?

    @MarkusD
    все время мелю чепуху :)
    Khronos Group не предоставляет сами заголовки для OpenGL. Группа занимается только разработкой и поддержкой спецификаций API, а также предоставляет универсальные схемы для генерации биндингов библиотеки на любой требуемый язык.

    Файлы g.xml, wgl.xml и glx.xml предоставляют все необходимые описания для генерации биндингов OpenGL.
    Файл genheaders.py позволяет сгенерировать обычные заголовки с биндингами на C.

    Но на самом деле это все не нужно, потому что есть GLAD. Этот проект позволяет получить заголовки с биндингами вообще не напрягаясь, используя общедоступный сайт-генератор. Более того, GLAD может добавить свой код загрузчика в генерируемые заголовки, что может сильно упростить подключение сгенерированных заголовочных файлов.
    Поэтому самым верным способом будет воспользоваться функционалом GLAD для получения заголовков с биндингами OpenGL на C.
    Ответ написан
    Комментировать
  • Как сделать обработчик коллизий в OpenGL?

    @MarkusD
    все время мелю чепуху :)
    OpenGL - Open Graphics Library.
    Это - открытая библиотека работы с графикой. И в ее API содержатся только функции работы с графикой.
    Для обработки коллизий нужна отдельная библиотека обработки коллизий, в зависимости от мировой системы координат. Простую библиотеку можно и самому сделать.
    Для обработки коллизий используют библиотеки коллизий и физические движки.
    Ответ написан
    3 комментария
  • Как переиспользовать opengl-объекты в разных окнах?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для начала стоит разобраться в том, написан ли код в вопросе на OpenGL - нет, не написан.
    Код в вопросе использует обертку GLFW, которая довольно сильно абстрагирует пользователя от самого по себе OpenGL. Отсюда у тебя и непонимание самых основ работы с OpenGL, а ведь самым основным в этой работе является управление контекстом устройства.
    Коротко описать механизмы работы с контекстом не получится, в трех словах половину документации API не пересказать. Но есть источники, которые обязательно нужно изучить: [1], [2], [3], [4].

    Как, собственно, решить твою проблему с потерей ресурсов после удаления окна. По мере изучения приведенных мной ссылок можно заметить за контекстом некоторые особенности. Контекст привязывается к конкретному потоку исполнения, вызовы OpenGL проваливаются если к потоку где они вызваны не привязан контекст. Еще контекст обладает своей таблицей ресурсов. Все эти модели/текстуры/шейдеры локальны только для того контекста, который был привязан к потоку во время их создания. И еще - что контексты могут совместно владеть таблицей ресурсов, имея тем самым возможность работать с одними и теми же ресурсами.

    Эта информация и является решающей в твоем случае. Конструктор твоего класса Window не даром имеет третий параметр - reference. Ты не мог ввести его специально и не знать о том, что я сейчас напишу.
    Если обратиться к документации на glfwCreateWindow[?], то можно узнать что пятым параметром функции заявлено The window whose context to share resources with, т.е. окно, с контекстом которого следует разделить таблицу ресурсов.

    Решением для тебя будет создание главного окна, с которым первое и второе окна будут разделять таблицу ресурсов. Главное окно не нужно активировать, оно должно просто присутствовать чтобы выступать основным держателем таблицы ресурсов. Указатель на главное окно нужно передать третьим параметром в оба отображаемых окна. Больше ничего менять не надо, все заработает сразу.
    Ответ написан
  • Как получать и обрабатывать координаты мыши в окне OpenGL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    По координатам мышки пойдем от самого верха, операционной системы, и до мирового пространства, в котором рисуется твоя сцена.

    Самое первое - это не нужно именно получать координаты курсора в момент его обработки. Нужно реагировать на сообщения о перемещении курсора по окну и запоминать координаты в его клиентской области, в которой у тебя и происходит отрисовка сцены.
    Но если очень хочется, то на WinAPI можно использовать GetCursorPos[?] и ScreenToClient[?] чтобы на месте получить координаты курсора в клиентской области окна.
    На этом этапе нужно принять во внимание то, в какой координатной системе считаются координаты курсора.
    Например, в WinAPI координатная система окна выглядит так. И именно в ней считаются координаты курсора.

    Все следующие этапы стоит рассматривать как переходы из одного пространства в другое. Числа могут быть одинаковыми, но ценность перехода именно в том, чтобы перевести эти числа из одного пространства в другое.

    Следующим этапом будет переход от клиентской области к области вьюпорта. Этот переход делается через дополненную 2х2 матрицу трансформаций 2D-пространства. Ее важно использовать везде, т.к. это позволит заложить фундамент однообразного перехода из пространства клиентской области экрана в пространство координатной системы камеры.
    Что важно на этом этапе. Пространство вьюпорта и пространство камеры связаны через пространство нормализованных координат области отсечения. В разных GAPI пространство области отсечения представлено по-разному. И пространство вьюпорта тоже по-разному представлено в разных GAPI.
    Например, в OpenGL координатная система вьюпорта выглядит так.
    Еще на этом этапе нужно заложить переход от точки к лучу, т.к. вьюпорт уже имеет объем. Для этого координаты курсора нужно дополнить минимальной и максимальной глубиной вьюпорта, получив уже две точки координат курсора.
    В качестве примера такого преобразования можно взять код функции D3DXVec3Unproject прямо из Wine. Там делается правильный и простой переход из пространства клиентской области окна в пространство области отсечения OpenGL через пространство вьюпорта.

    От пространства области отсечения координаты курсора нужно перевести в пространство сцены.
    Матрица View-Projection переводит геометрию из системы координат пространства сцены в систему координат области отсечения. Значит, чтобы сделать переход из области отсечения в пространство сцены, нужно использовать инверсию матрицы View-Projection.

    Переведенная в пространство сцены пара точек формирует отрезок. Нужно запомнить точку с дополнением минимальной глубины вьюпорта, обозначить ее как источник луча курсора. Вторую точку нужно локализовать относительно первой и нормализовать. Это будет направление луча.
    Этим лучом можно будет щупать всю сцену, любой объект, переводя его координаты к локальной системе объекта и проверяя на пересечения.
    Переход от пространства сцены в локальное пространство объекта производится через умножение луча на обратную мировую матрицу этого объекта.
    Ответ написан
    Комментировать
  • Как правильно организовать соприкосновения поверхностей при инстансинге?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В такой постановке вопрос тянет уже на полноценную разработку игр. И суть вот в чем.

    Игровое пространство может быть единым и предлагать самые разные функции, однако это не значит что всех нужно делать каким-то одним способом.
    Да, к взаимодействию с пространством относятся как звук с графикой, так и физика с обработкой столкновений.
    Однако, OpenGL, как и любой другой GAPI, реализует только функционал вывода графики. Иными словами, у данных через OpenGL дорожка только в один конец - на монитор и все. Большего от него и требовать не нужно.
    А обработка столкновений подразумевает обратную связь и реализуется уже отдельными средствами.

    Это все должно работать слоями. Параллельно существующими математическими моделями. Параллельность существования тут означает только то, что между собой модели явным образом не взаимодействуют, но обмениваются результатами своей работы через свойства и атрибуты объектов игры, модифицируя их во время своей работы.
    Вот у тебя уже есть какая-то модель отображения мира. Тебе надо просто рядом реализовать параллельную модель обработки столкновений.
    В этой модели у тебя должны быть подвижные акторы и коллизионная геометрия. Для твоего случая достаточно это все посчитать на CPU в отдельной функции. В качестве геометрии коллизий можно взять прямо те же данные вершин, которыми ты рисуешь мир. Данные акторов можно взять прямо там, где готовятся позиции инстансов капель.
    Сама модель обработки столкновений - это набор математических формул из уроков геометрии в средней школе.
    Есть капля, с момента прошлого такта она пролетела какое-то расстояние. Проверяем пересечение капли с геометрией дома и генерируем событие столкновения когда оно зафиксировано.
    Для капель неподвижным можно даже человека считать.

    Ровно так же у тебя может существовать и еще одна модель - модель обработки физики, которая правильно посчитает движение капель с учетом ветра и гравитации. Еще эта модель у тебя может быть задействована для расчетов прыжков и падения персонажа.

    В сложных высокотехнологичных движках множество моделей используют собственную память для представления акторов, вводя многократную избыточность использования памяти, но позволяя работу в полностью параллельном режиме на нескольких потоках. В таких движках существует отдельная стадия сборки представлений из разных подсистем в единое представление актора с попутным разрешением коллизий, если они возникают.
    Тебе подобные сложности точно не нужны на текущем этапе. Реализовать можно просто парой-тройкой моделей (графика, столкновения и физика) поверх единой системы объектов мира, обработку которых пустить последовательно в одном потоке.
    Ответ написан
  • Почему не срабатывает смещение при инстансинге?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Что такое инстансинг по своей сути?
    Вот у нас есть геометрия и параметры ее обработки. Речь только о стадии обработки вершин.
    Самая суть инстансинга в том, что геометрия у нас как бы находится в состоянии суперпозиции. Примитив у нас один, DIP тоже один, но в атрибутах примитива заложено что-то особенное, что заставляет увидеть его сразу во многих местах.
    Это самое особенное - это атрибуты инстанса.

    По умолчанию все атрибуты вершины являются атрибутами примитива. Когда работает DIP, вершинный шейдер запускается для обработки каждой отдельной вершины примитива. Когда инстансинг отключен, можно считать что все вершины примитива принадлежат ровно одному инстансу, для которого атрибутов не задано.
    Но если примитив требуется отрисовать с использованием инстансинга, в его атрибутах должны находиться и атрибуты инстанса. Тогда во время работы уже DIPI вершинный шейдер будет запущен для каждой вершины примитива каждого инстанса.

    Поэтому самым первым этапом включения инстансинга будет именно новый буфер атрибутов в шейдере.
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec2 aOffset;


    aOffset не должен быть uniform-ом. Это должен быть именно атрибут инстанса.
    Uniform-ами стоит делать другие вещи, не уникальные между инстансами. Uniform-ы для инстанса имеются сразу и все. Поэтому рационально в них держать только то, что разные инстансы используют совместно.
    Например - это могут быть цвета фракций или параметры костей скелетов в разных позах.
    Примерный код вершинного шейдера
    #version 460 core
    
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec2 aOffset;
    
    void main()
    {
       gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
    }


    Для указания того, что в буфере находятся атрибуты инстанса, следует использовать glVertexAttribDivisor[?].
    У тебя, скорее всего, это будет выглядеть так: glVertexAttribDivisor( 1, 6 );.
    А внутри буфера у тебя должно быть то, что сейчас записывается в glm::vec2 translations[100].
    Как примерно может заполняться буфер атрибутов инстанса
    glm::vec2 translations[100];
    int index = 0;
    float offset = 0.1f;
    for (int y = -10; y < 10; y += 2)
    {
    	for (int x = -10; x < 10; x += 2)
    	{
    		glm::vec2 translation;
    		translation.x = (float)x / 10.0f + offset;
    		translation.y = (float)y / 10.0f + offset;
    		translations[index++] = translation;
    	}
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[3]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(translations), translations, GL_STATIC_DRAW);
    
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2), (GLvoid*)0);
    glVertexAttribDivisor(1, 6);
    glEnableVertexAttribArray(1);


    С этого момента инстансинг заведется как положено. Просто убираем uniform, меняем подготовку его значений на подготовку еще одного буфера вершин, добавляем код установки этого нового буфера, задействуем его значения в шейдере и все. Эту теорию тебе остается только адаптировать для своего кода.
    Чтобы разобраться почему у тебя конкретно сейчас рисуется неправильно, тебе нужно установить графический отладчик и проанализировать через него презентацию одного кадра. Советую поставить Renderdoc и научиться полноценно работать с ним. Без запуска твоего кода и анализа кадра выяснить причину неправильной работы практически невозможно.
    Ответ написан
  • [OpenGL]Почему получается такая фигура?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    GL_POLYGON предназначен для вывода только выпуклых многоугольников, а у тебя он выпуклым не является.

    Фигурки для тетриса рисовать стоит или в режиме GL_QUAD, или в режиме GL_TRIANGLES, явным образом выделяя отдельные квадратные участки.
    Так проще ориентироваться в точках.
    Ответ написан
    Комментировать
  • Каким образом одна и та же игра может рендериться за счёт разных графических библиотек?

    @MarkusD
    все время мелю чепуху :)
    Любой GAPI является инструментом прикладного уровня, который инженер может встроить в свой код с целью пользоваться его возможностями. Прямое использование любого API является примером тесной интеграции кода программы с кодом целевой платформы.
    Целевой платформой является аппаратно-программный комплекс, на котором планируется запускать разрабатываемый код. Целевой может быть Sony Playstation, ПК с виндой, Мак или мобила с Андроидом.

    Код с тесной интеграцией целевых API нередко пропитан их общей "атмосферой". Это выражается в стиле кода, в общих конструкциях, в заточенности кода проекта на использование совместно с конкретными API.
    Использующий DirectX8 код не так просто перевести на использование DirectX9, еще сложнее перевести на использование DirectX10/11 или DirectX12. Код с использованием OpenGL1.1 тяжело поддается модификации для использования OpenGL2.0, 3.0 или дальше. Любая кодовая база с тесной интеграцией DirectX/OpenGL очень тяжело переводится на Vulkan, Metal или профильные GAPI некоторых закрытых целевых платформ.

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

    В задачах кроссплатформенности решающую роль играет знание архитектуры П.О. Потому что кроссплатформенность требует разработать агностический слой, под которым будет находиться настраиваемая связь с целевым API. Когда речь идет о системных API целевых платформ, агностический слой представляет из себя фасад универсальной операционной системы. Когда речь идет о GAPI, агностический слой представляет собой API для универсального GPU. Когда речь идет о звуке, агностический слой представляется универсальной звуковой системой.
    Без всеобъемлющего знания архитектуры П.О. такие инструменты разработать крайне сложно. Без глубокой экспертизы во всех целевых API и целевых платформах разработать такой слой возможным не представляется. Без экспертных знаний используемых в разработке языков построение кроссплатформенного кода выливается в не распутываемый клубок взаимопереплетающихся проблем, решение каждой из которых на одной платформе гарантированно сломает поддержку другой.
    Я об этом пишу потому что знаю множество подходов к решению задач разработки кроссплатформенного кода. Я сталкивался с подходами которые не решают задачу кроссплатформенности и знаю пути более эффективного решения этих задач для конкретных случаев в конкретных проектах.

    Когда агностический слой выведен, весь кроссплатформенный код пользуется только им. Под агностическим слоем, тем или иным способом, весь код сводится к использованию целевого API.
    Таким образом, для того чтобы позволить игре рендериться и через DirectX, и через OpenGL, и через Vulkan, нужно в движке игры разработать универсальный внутренний GAPI, вызовы которого будут передаваться в выбранный пользователем целевой GAPI. Форматы данных, ресурсы и шейдеры, при этом, ровно так же выбираются исходя из выбранного пользователем целевого GAPI и могут пересекаться между разными GAPI.
    Те же шейдеры сегодня спокойно транслируются из HLSL в GLSL или SPIR-V специальными трансляторами. Геометрия может быть одинакова, форматы текстур на целевой платформе между всеми GAPI, как правило, одинаковы.
    Дело остается только за грамотной разработкой своего маленького виртуального GPU.
    Ответ написан
    Комментировать
  • Внесение данных в std::vector< GLfloat >?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Вектор перед работой с его индексами необходимо привести к требуемому размеру.
    Делается это с помощью метода resize[?].
    После этого можно обращаться к значению по индексу напрямую.

    Если размер вектора уже определен и необходимо именно вставить данные по индексу, то воспользоваться можно методом insert[?].
    Однако, первым параметром метод требует не индекс, а итератор внутри вектора, куда требуется выполнить вставку. Этот итератор можно получить через смещение итератора начала вектора на требуемый индекс.
    vertexBuffer.insert( vertexBuffer.begin() + 1, x );

    При этом важно контролировать чтобы индекс вставки не выходил за пределы размера вектора.
    Забывать не стоит и о том, что при вставке велика вероятность реаллокации памяти под элементы вектора, в следствии чего уже все итераторы и ссылки на элементы вектора будут инвалидированы.
    Ответ написан
    1 комментарий
  • Как загрузить картинку в OpenTK c# с прозрачным фоном, при чём сам фон и так прозрачный?

    @MarkusD
    все время мелю чепуху :)
    BitmapData data = bitmap2.LockBits(new System.Drawing.Rectangle(x, y, weight, height),
        ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);

    Тут получается память изображения. Формат пикселя в data будет XRGB с шириной 32 бита.
    Внимание здесь следует обратить на то, что формат указан как RGB32. Это значит что alpha-канал в формате никак не представлен и, скорее всего, у каждого пикселя будет нулевым т.к. место под него в формате заявлено.

    Далее.
    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
        OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, data.Scan0);

    PixelInternalFormat.Rgba означает что в памяти GPU текстура будет представлена в формате RGBA с каналами float с нормализацией.
    OpenTK.Graphics.OpenGL.PixelFormat.Rgba означает что формат data.Scan0 нужно воспринимать как RGBA, а PixelType.UnsignedByte означает что каналы в data.Scan0 нужно воспринимать как unsigned byte.

    Тут на лицо несовпадение формата считанного из файла изображения и формата создаваемой текстуры. И если размеры и число каналов между форматами совпадают, то трактовка самих каналов - нет. XRGB != RGBA.
    Все нужно привести к единому формату. Например, из файла читать формат Format32bppArgb. Но в этом случае есть вероятность ошибиться с порядком каналов между Format32bppArgb и OpenTK.Graphics.OpenGL.PixelFormat.Rgba.
    Еще, как вариант, можно инициализировать незадействованный канал в исходных данных.

    Одним словом, после чтения из файла, данные будущей текстуры нужно подогнать под желаемый формат текстуры.
    Ответ написан
  • Почему сплайны OpenGL прорисовываются не на всех компьютерах?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Дело в том, что функция glMap1[?] имеет довольно узкий коридор поддержки. Она была введена в OpenGL 1.0 и удалена из поддержки в OpenGL 3.2 Core Profile.

    Т.к. ты пользуешься wglCreateContext, система сама решает какой контекст ей для тебя создавать. Это может быть и контекст с версией 4.6, в котором уже нет поддержки функции glMap1f.
    Тебе стоит более точно указывать версию создаваемого контекста. Это можно сделать с помощью расширения WGL_ARB_create_context. Функция wglCreateContextAttribsARB позволяет задавать атрибуты для создаваемого контекста, среди которых ты можешь обозначить и требуемую версию.

    В качестве примера использования этого расширения можно взять такой код.
    Код примера
    // Sample code showing how to create a modern OpenGL window and rendering context on Win32.
    
    #include <windows.h>
    #include <gl/gl.h>
    #include <stdbool.h>
    
    typedef HGLRC WINAPI wglCreateContextAttribsARB_type(HDC hdc, HGLRC hShareContext,
            const int *attribList);
    wglCreateContextAttribsARB_type *wglCreateContextAttribsARB;
    
    // See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt for all values
    #define WGL_CONTEXT_MAJOR_VERSION_ARB             0x2091
    #define WGL_CONTEXT_MINOR_VERSION_ARB             0x2092
    #define WGL_CONTEXT_PROFILE_MASK_ARB              0x9126
    
    #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB          0x00000001
    
    typedef BOOL WINAPI wglChoosePixelFormatARB_type(HDC hdc, const int *piAttribIList,
            const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats);
    wglChoosePixelFormatARB_type *wglChoosePixelFormatARB;
    
    // See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt for all values
    #define WGL_DRAW_TO_WINDOW_ARB                    0x2001
    #define WGL_ACCELERATION_ARB                      0x2003
    #define WGL_SUPPORT_OPENGL_ARB                    0x2010
    #define WGL_DOUBLE_BUFFER_ARB                     0x2011
    #define WGL_PIXEL_TYPE_ARB                        0x2013
    #define WGL_COLOR_BITS_ARB                        0x2014
    #define WGL_DEPTH_BITS_ARB                        0x2022
    #define WGL_STENCIL_BITS_ARB                      0x2023
    
    #define WGL_FULL_ACCELERATION_ARB                 0x2027
    #define WGL_TYPE_RGBA_ARB                         0x202B
    
    static void
    fatal_error(char *msg)
    {
        MessageBoxA(NULL, msg, "Error", MB_OK | MB_ICONEXCLAMATION);
        exit(EXIT_FAILURE);
    }
    
    static void
    init_opengl_extensions(void)
    {
        // Before we can load extensions, we need a dummy OpenGL context, created using a dummy window.
        // We use a dummy window because you can only set the pixel format for a window once. For the
        // real window, we want to use wglChoosePixelFormatARB (so we can potentially specify options
        // that aren't available in PIXELFORMATDESCRIPTOR), but we can't load and use that before we
        // have a context.
        WNDCLASSA window_class = {
            .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
            .lpfnWndProc = DefWindowProcA,
            .hInstance = GetModuleHandle(0),
            .lpszClassName = "Dummy_WGL_djuasiodwa",
        };
    
        if (!RegisterClassA(&window_class)) {
            fatal_error("Failed to register dummy OpenGL window.");
        }
    
        HWND dummy_window = CreateWindowExA(
            0,
            window_class.lpszClassName,
            "Dummy OpenGL Window",
            0,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            0,
            0,
            window_class.hInstance,
            0);
    
        if (!dummy_window) {
            fatal_error("Failed to create dummy OpenGL window.");
        }
    
        HDC dummy_dc = GetDC(dummy_window);
    
        PIXELFORMATDESCRIPTOR pfd = {
            .nSize = sizeof(pfd),
            .nVersion = 1,
            .iPixelType = PFD_TYPE_RGBA,
            .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
            .cColorBits = 32,
            .cAlphaBits = 8,
            .iLayerType = PFD_MAIN_PLANE,
            .cDepthBits = 24,
            .cStencilBits = 8,
        };
    
        int pixel_format = ChoosePixelFormat(dummy_dc, &pfd);
        if (!pixel_format) {
            fatal_error("Failed to find a suitable pixel format.");
        }
        if (!SetPixelFormat(dummy_dc, pixel_format, &pfd)) {
            fatal_error("Failed to set the pixel format.");
        }
    
        HGLRC dummy_context = wglCreateContext(dummy_dc);
        if (!dummy_context) {
            fatal_error("Failed to create a dummy OpenGL rendering context.");
        }
    
        if (!wglMakeCurrent(dummy_dc, dummy_context)) {
            fatal_error("Failed to activate dummy OpenGL rendering context.");
        }
    
        wglCreateContextAttribsARB = (wglCreateContextAttribsARB_type*)wglGetProcAddress(
            "wglCreateContextAttribsARB");
        wglChoosePixelFormatARB = (wglChoosePixelFormatARB_type*)wglGetProcAddress(
            "wglChoosePixelFormatARB");
    
        wglMakeCurrent(dummy_dc, 0);
        wglDeleteContext(dummy_context);
        ReleaseDC(dummy_window, dummy_dc);
        DestroyWindow(dummy_window);
    }
    
    static HGLRC
    init_opengl(HDC real_dc)
    {
        init_opengl_extensions();
    
        // Now we can choose a pixel format the modern way, using wglChoosePixelFormatARB.
        int pixel_format_attribs[] = {
            WGL_DRAW_TO_WINDOW_ARB,     GL_TRUE,
            WGL_SUPPORT_OPENGL_ARB,     GL_TRUE,
            WGL_DOUBLE_BUFFER_ARB,      GL_TRUE,
            WGL_ACCELERATION_ARB,       WGL_FULL_ACCELERATION_ARB,
            WGL_PIXEL_TYPE_ARB,         WGL_TYPE_RGBA_ARB,
            WGL_COLOR_BITS_ARB,         32,
            WGL_DEPTH_BITS_ARB,         24,
            WGL_STENCIL_BITS_ARB,       8,
            0
        };
    
        int pixel_format;
        UINT num_formats;
        wglChoosePixelFormatARB(real_dc, pixel_format_attribs, 0, 1, &pixel_format, &num_formats);
        if (!num_formats) {
            fatal_error("Failed to set the OpenGL 3.3 pixel format.");
        }
    
        PIXELFORMATDESCRIPTOR pfd;
        DescribePixelFormat(real_dc, pixel_format, sizeof(pfd), &pfd);
        if (!SetPixelFormat(real_dc, pixel_format, &pfd)) {
            fatal_error("Failed to set the OpenGL 3.3 pixel format.");
        }
    
        // Specify that we want to create an OpenGL 3.3 core profile context
        int gl33_attribs[] = {
            WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
            WGL_CONTEXT_MINOR_VERSION_ARB, 3,
            WGL_CONTEXT_PROFILE_MASK_ARB,  WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
            0,
        };
    
        HGLRC gl33_context = wglCreateContextAttribsARB(real_dc, 0, gl33_attribs);
        if (!gl33_context) {
            fatal_error("Failed to create OpenGL 3.3 context.");
        }
    
        if (!wglMakeCurrent(real_dc, gl33_context)) {
            fatal_error("Failed to activate OpenGL 3.3 rendering context.");
        }
    
        return gl33_context;
    }
    
    static LRESULT CALLBACK
    window_callback(HWND window, UINT msg, WPARAM wparam, LPARAM lparam)
    {
        LRESULT result = 0;
    
        switch (msg) {
            case WM_CLOSE:
            case WM_DESTROY:
                PostQuitMessage(0);
                break;
            default:
                result = DefWindowProcA(window, msg, wparam, lparam);
                break;
        }
    
        return result;
    }
    
    static HWND
    create_window(HINSTANCE inst)
    {
        WNDCLASSA window_class = {
            .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
            .lpfnWndProc = window_callback,
            .hInstance = inst,
            .hCursor = LoadCursor(0, IDC_ARROW),
            .hbrBackground = 0,
            .lpszClassName = "WGL_fdjhsklf",
        };
    
        if (!RegisterClassA(&window_class)) {
            fatal_error("Failed to register window.");
        }
    
        // Specify a desired width and height, then adjust the rect so the window's client area will be
        // that size.
        RECT rect = {
            .right = 1024,
            .bottom = 576,
        };
        DWORD window_style = WS_OVERLAPPEDWINDOW;
        AdjustWindowRect(&rect, window_style, false);
    
        HWND window = CreateWindowExA(
            0,
            window_class.lpszClassName,
            "OpenGL",
            window_style,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            rect.right - rect.left,
            rect.bottom - rect.top,
            0,
            0,
            inst,
            0);
    
        if (!window) {
            fatal_error("Failed to create window.");
        }
    
        return window;
    }
    
    int WINAPI
    WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd_line, int show)
    {
        HWND window = create_window(inst);
        HDC gldc = GetDC(window);
        HGLRC glrc = init_opengl(gldc);
    
        ShowWindow(window, show);
        UpdateWindow(window);
    
        bool running = true;
        while (running) {
            MSG msg;
            while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) {
                if (msg.message == WM_QUIT) {
                    running = false;
                } else {
                    TranslateMessage(&msg);
                    DispatchMessageA(&msg);
                }
            }
    
            glClearColor(1.0f, 0.5f, 0.5f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
            // Do OpenGL rendering here
    
            SwapBuffers(gldc);
        }
    
        return 0;
    }
    Ответ написан
    Комментировать
  • Как создать окно OpenGL в окне полученном от CreateWindowEx (WINAPI)?

    @MarkusD
    все время мелю чепуху :)
    Использование glfw нередко приводит к тому, что обычные для OpenGL вещи средствами glfw сделать невозможно.
    В рамках glfw контекст OpenGL непосредственно объединен с окном операционной системы в типе GLFWwindow[?].
    Разделить их или использовать по отдельности уже не получится т.к. glfw не дает такого интерфейса.
    Так же, в функцию glfwCreateWindow[?] невозможно передать и дескриптор окна операционной системы если хочется создать дочернее окно в своем.

    Это такая ловушка использования glfw. Или пользователь использует glfw полностью с самого начала и мирится с ограничениями, или он отказывается от использования glfw и делает все руками.

    Максимум что можно сделать средствами glfw - это создать несколько разных окон, каждому из которых будет сопоставлен его уникальный контекст OpenGL. Но работать с такими окнами придется только и строго в разных потоках, т.к. привязка контекста OpenGL производится только для конкретного потока, в котором для этого контекста и нужно вызывать функции OpenGL. Иными словами, такой подход может означать большие трудности при реализации.
    Если присмотреться к сигнатуре glfwCreateWindow, то можно заметить последний параметр, который позволяет указать другое окно, ресурсы которого необходимо разделить с новым создаваемым окном. Используя этот параметр можно создать несколько окон с совместным управлением ресурсами, но рисовать в этих окнах все равно можно лишь в разных потоках.

    Для решения задачи более гибкого создания окон и рисования в них средствами OpenGL лучше зайти со стороны WGL[?] и прямой работы с Win32 API. В этом случае можно даже одним контекстом обойтись, привязывая его то к одному окну, то к другому на время отрисовки.

    Но если требуется просто добавить у себя элементы интерфейса в свое текущее окно, то лучше и удобнее будет взять imgui или подобную библиотеку. Для этого от glfw можно не отказываться.
    Ответ написан
    Комментировать
  • Как в OpenGL провести разноцветную ломаную линию без градиента?

    @MarkusD
    все время мелю чепуху :)
    А ответ будет очень простым.
    В точке смены цвета тебе надо поставить две вершины. Первой вершиной надо закончить линию прошлого цвета, а второй - начать линию следующего цвета.
    На производительности это никак не скажется.
    Ответ написан
    2 комментария
  • Почему одна текстура работает и загружается, а другие нет?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Как показало обсуждение в комментариях, исключение бросается из функции glTexImage2D.
    Подозрение в этом случае может пасть только на то, что размер переданной через data памяти меньше расчетного размера, который определяет функция исходя из параметров width, height, GL_RGBA, GL_UNSIGNED_BYTE.

    Это может означать что переданные в glTexImage2D параметры не соответствуют считанному изображению. А т.к. width и height получены напрямую при чтении изображения, вопросы возникать могут только относительно заявленного формата изображения - GL_RGBA, GL_UNSIGNED_BYTE.

    Собственно, именно так и оказалось на самом деле. Прочитанное изображение имеет другой формат и меньше каналов, а следовательно и меньший размер относительно расчетного.

    Касательно самой функции загрузки текстуры - она слишком простая. Настоящая функция загрузки учитывает массу возможных вариантов хранения изображения и всегда учитывает все возможные точки отказа. Обработка всех возможных ошибок гораздо важнее прикладного кода функции.
    Ответ написан
    Комментировать
  • Чем отличается mvpmatrix от тех которые заданы по отдельности и надо ли их подключать (по отдельности) и в каких случаях?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для лучшего понимания материала тебе стоит изучить, как минимум, эту статью.
    Если коротко
    ModelMatrix - это матрица преобразования локального пространства геометрии конкретного объекта.
    Внутри этого локального пространства геометрия объекта расположена относительно центра пространства. Чтобы геометрию модели отобразить в каком-либо целевом пространстве, необходимо произвести отображение локального пространства модели в целевое пространство. Именно таким отображением является ModelMatrix.

    ViewMatrix - матрица отображения любого внешнего пространства в пространство вида - пространство взора наблюдателя. Наблюдатель (камера) воспринимает объекты только внутри своего собственного локального пространства. Чтобы наблюдатель мог увидеть объект, объект необходимо отобразить из его родного пространства в пространство вида камеры, где он или попадет в область проекции, или не попадет.

    Projectionmatrix - матрица отображения видового пространства в пространство проекции вида. Фактически это отображение выполняет проецирование некоторого участка видового пространства на плоскость. Именно отображенная таким образом геометрия становится видимой при презентации кадра.

    Каждый объект сцены находится и анимируется внутри своего локального пространства. Чтобы объект на сцене встал в положенное ему место, его геометрия отображается из своего локального пространства в глобальное пространство сцены с помощью своей ModelMatrix.
    Чтобы сцена стала видна для камеры, всю ее геометрию требуется отобразить в пространство вида камеры с помощью матрицы ViewMatrix.
    Чтобы часть пространства вида камеры попала в область презентации кадра, всю геометрию видового пространства нужно отобразить в пространство проекции с помощью матрицы Projectionmatrix.
    При этом важно заметить, что матрицы Projectionmatrix и ViewMatrix уникальны для камеры, а вот матрицы ModelMatrix уникальны уже для каждого объекта отдельно.


    Отображение между пространствами выполняется через операцию перемножения позиции в исходном пространстве на матрицу преобразования пространства. Если подходить к этой задаче в лоб, то отображение каждой позиции потребует 16 операций перемножения над вещественными числами.
    Всего для заполнения области презентации кадра требуется выполнить, как минимум, три последовательных отображения геометрии, что будет означать по 48 операций умножения на каждую позицию отображаемой геометрии.

    Учитывая что Projectionmatrix и ViewMatrix в процессе презентации кадра являются неизменными, затраты в 48 операций на каждую позицию выглядят как расточительство. Если до презентации выполнить отображение самого пространства вида в пространство проекции, т.е. перемножить ViewMatrix и Projectionmatrix в правильном порядке, то с помощью результирующей матрицы viewProjectionMatrix число операций на одну позицию можно снизить до 32.
    Если же произвести отображение самого локального пространства модели в пространство презентации через перемножение матриц viewProjectionMatrix и ModelMatrix в правильном порядке, то благодаря полученной таким образом MVPMatrix число операций на одну позицию снизится до изначальных 16.

    Таким образом, матрицы viewProjectionMatrix и MVPMatrix просто позволяют снизить трудоемкость презентации кадра. Однако, во время презентации может потребоваться определение положения геометрии в каком-либо промежуточном пространстве, поэтому вдобавок к MVPMatrix в шейдер принято отдавать и ModelMatrix, и матрицы камеры.
    У каждой из матриц свой смысл. И если MVPMatrix бесспорно нужна всегда, то любую другую матрицу в шейдер добавлять стоит только исходя из осмысленной необходимости. Регистры GPU для шейдера не резиновые и могут очень быстро забиться избыточными данными. К тому же, чем меньше на каждом кадре в шейдер передается данных, тем быстрее выполняется презентация кадра.
    Ответ написан
    Комментировать