Ответы пользователя по тегу OpenGL
  • Как переиспользовать 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 для шейдера не резиновые и могут очень быстро забиться избыточными данными. К тому же, чем меньше на каждом кадре в шейдер передается данных, тем быстрее выполняется презентация кадра.
    Ответ написан
    Комментировать
  • Можно ли использовать sfml без openGL в C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Дело в том, что OpenGL является т.н. GAPI, т.е. интерфейсом прикладного программирования графики.
    На базе такого GAPI строятся движки, фреймворки и простые проекты. SFML, будучи фреймворком, точно так же базируется на OpenGL для работы с графикой.

    В связи с этим, если ты не планируешь работать с графикой, то можешь просто не использовать модули Window и Graphics. Как следствие, у тебя не будет возникать зависимости от OpenGL.
    Но если для тебя важно пользоваться графикой SFML, то в его стандартной поставке без OpenGL твой проект не соберется.
    Ответ написан
  • Почему не рисует ось z?

    @MarkusD
    все время мелю чепуху :)
    Для начала стоит учесть общую систему опознания осей в 3D-пространстве. Оси X, Y, Z помечаются каналами цветов R, G, B в однозначном соответствии. Ось X всегда и везде красная. Ось Y - зеленая. А ось Z - синяя.

    glVertex3f(0, 0,znear);
    glVertex3f(0, 0,zfar);

    Этот код говорит рисовать коллинеарную со взглядом линию. GPU не станет тратить ресурсы на всю линию и нарисует только одну точку, которая с высокой долей вероятности будет затерта при растеризации других примитивов. Ведь все три линии у тебя рисуются из нулевых координат.

    Сдвинь начальные координаты осей X и Y от нуля (хоть на 1pt), измени цвет оси Z, тогда ее станет немного лучше видно.
    Ответ написан
    Комментировать
  • Как влияет перекрыетие объектов на скорость отрисовки?

    @MarkusD
    все время мелю чепуху :)
    Абсолютно точным ответом на твой вопрос является: смотря как настроишь контекст.

    Здесь стоит припомнить некоторые базовые этапы графического конвейера.

    Первым этапом является обработка вершинных данных. Она выполняется для каждой вершины в примитиве. Отказаться от обработки какой-либо вершины пользователь не в праве. С другой стороны, обработка вершинных данных, обычно, выполняется быстро и вершин, обычно, куда меньше чем фрагментов.
    Поэтому, чтобы загружать GPU только правильными вершинам, эти вершины стоит правильно выбрать для передачи ему. За это должна отвечать стадия отсечения примитивов при подготовке кадра к презентации.

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

    Поэтому, при правильной настройке контекста, при выборе только важной для кадра геометрии, при работе с буфером глубины, при использовании Adaptive Shading и вовремя отбрасывая фрагменты из стадии растеризации скорость презентации будет выше чем без всех этих настроек.
    Если ты сделаешь у себя z-prepass, то сортировка геометрии по расстоянию до камеры для тебя станет избыточной.
    Ответ написан
    Комментировать
  • Как возможно задать параметры, а именно текстуру отдельно для каждого треугольника?

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

    Развернуто:

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

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

    Под материалом понимается комплекс из программы и ресурсов, которые отображаются на геометрии.
    Материал отображает на геометрию заданный своими свойствами набор текстур и параметров. С точки зрения геометрии, материал - это DIP (Draw Indexed Primitive - твой glDrawElements). С точки зрения шейдера, материал - это комплекс из программы шейдера и набора входных/выходных параметров этой программы.
    Один материал одной геометрии - это один DIP с установкой набора текстур и параметров материала, а так же - с установкой нужного буфера индексов, вершин и инстансов.

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

    Таким образом ты реализуешь очень простую систему поддержки материалов и выполнишь свою задачу.
    Ответ написан
    Комментировать
  • Как нарисовать такую звезду openGL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Фигура состоит из 10 вершин, соединенных 10 ребрами.
    На первом шаге тебе надо равномерно распределить по единичной окружности 10 вершин. Сделать это нужно в специально отведенном массиве вершин, т.к. индексированные примитивы в этом старом режиме вывести нельзя.
    На втором этапе переключаем контекст на вывод замкнутой линии (GL_LINE_LOOP) и выводим все 10 точек из массива вершин, но со смещением на 3 точки и по модулю 10.

    Псевдокод:
    void display()
    {
       glClear( GL_COLOR_BUFFER_BIT );
       glBegin( GL_LINE_LOOP );
    
       for( size_t index = 0; index < vertices.size(); ++index )
       {
          const Vertex& vertex = vertices[ ( index * 3 ) % vertices.size() ];
          glVertex2i( vertex.x, vertex.y );
       }
    
       glEnd();
       glFlush(); 
    }
    Ответ написан
    1 комментарий
  • OpenGL и C++. Как лучше отсортировать полигоны?

    @MarkusD
    все время мелю чепуху :)
    Честно говоря, странно видеть в заголовке и тегах вопрос об OpenGL, а в теле вопроса - про коллизии и, возможно, физику.

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

    Тебе надо иметь два набора вершин. Один - набор вершин для визуализации объекта. Второй - набор вершин для симуляции его физики, не только коллизий. И таких наборов может быть много. Для симуляции повреждений и деформаций, для скиннинга, для хитбоксов, для постройки маршрутов перемещений, для звуков...
    BSP подходят больше для отсечения видимой геометрии. В наши дни этот подход не оправдывает своих затрат, его не используют. Для эффективной симуляции коллизий лучше подходят QTree, OTree или KD-списки.

    Почитать начать можно отсюда: Study path for game programmer. Твой раздел: 9. Game Physics and Animation.
    Далее, тебе сюда: https://gamedev.ru/code/articles/?physics
    Да, геймдев все еще жив, хоть уже и не принадлежит Сереге Ваткину.
    Следом лучше пойти на хабр: [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11].
    У Фабьена Сангларда есть набор статей о внутреннем устройстве DOOM 3 и Quake 3, там есть материал и про столкновения.

    На этом этапе у тебя уже должно сформироваться представление о предметной области симуляции коллизий.
    Ответ написан
    Комментировать
  • Как ограничить FPS в OpenGL и glut?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Само ограничение частоты кадров делается очень просто. Снаружи idle-функции нужно содержать переменную с временем последнего вызова idle-функции, а в самой функции нужно просто накопить дельту частоты кадров и вызвать glutPostRedisplay.
    double GetCurrentTime()
    {
    	using Duration = std::chrono::duration<double>;
    	return std::chrono::duration_cast<Duration>( 
    		std::chrono::high_resolution_clock::now().time_since_epoch() 
    	).count();
    }
    
    const double frame_delay = 1.0 / 60.0; // 60 FPS
    double last_render = 0;
    void OnIdle()
    {
    	const double current_time = GetCurrentTime();
    	if( ( current_time - last_render ) > frame_delay )
    	{
    		last_render = current_time;
    		glutPostRedisplay();
    	}
    }


    Данный код является сильно упрощенным механизмом, лишь показывающим общий принцип ограничения частоты кадров. Его можно применять там, где не требуется точный контроль дельты между кадрами. К слову, представление времени в типе double помогает легче находить дельту между кадрами и контролировать ошибку дельты.
    Для более точного контроля частоты кадров стоит обратиться, например, к такому материалу.
    Ответ написан
    1 комментарий