Гайд Клиент с нуля | Рендер + OpenGL + Шейдеры

PoC Life
Пользователь
Статус
Оффлайн
Регистрация
22 Авг 2022
Сообщения
331
Реакции[?]
47
Поинты[?]
37K

Перед прочтением основного контента ниже, пожалуйста, обратите внимание на обновление внутри секции Майна на нашем форуме. У нас появились:

  • бесплатные читы для Майнкрафт — любое использование на свой страх и риск;
  • маркетплейс Майнкрафт — абсолютно любая коммерция, связанная с игрой, за исключением продажи читов (аккаунты, предоставления услуг, поиск кодеров читов и так далее);
  • приватные читы для Minecraft — в этом разделе только платные хаки для игры, покупайте группу "Продавец" и выставляйте на продажу свой софт;
  • обсуждения и гайды — всё тот же раздел с вопросами, но теперь модернизированный: поиск нужных хаков, пати с игроками-читерами и другая полезная информация.

Спасибо!

Список:
1. Создаём главный класс + снимаем ограничения - https://yougame.biz/threads/325113
2. Автобус евентов - https://yougame.biz/threads/325114
3. OpenGL + Шейдеры + Рендер - https://yougame.biz/threads/325115
4. Аим - https://yougame.biz/threads/325188
5. GlowESP aka OpenGL Framebuffer - https://yougame.biz/threads/325211/
6. Текст, шрифты, атлас - https://yougame.biz/threads/325292

OpenGL
Конкретно в рендере мы остановимся по подробнее. Майнкрафт использует для рендера API OpenGL, считайте, чтобы напрямую не обращаться к видеокарте мы используем движок для отрисовки.

У данного API есть своя особенность, у него есть так скажем "переключатели". Каждый переключатель за что-то отвечает.
Java:
GL11.glEnable(константа); // Включить переключатель
GL11.glDisable(константа); // Выключить переключатель
Сейчас расскажу про каждый.

Переключатель глубины (Depth test)
Константа: GL11.GL_DEPTH_TEST
Выкл:
Pasted image 20240705011107.png
Вкл:
Pasted image 20240705011123.png

Отсечение граней
Константа: GL11.GL_CULL_FACE
Если переключатель включен, то отрисовка будет только с одной стороны.

Вкл:
С одной стороны:
Pasted image 20240705010705.png
С другой:

Выкл - означает, что рисует всегда.

Blend (Смешивание)
Константа: GL11.GL_BLEND
Самая важный переключатель для нас, он позволяет смешивать цвета при наложении. Аргументов там много, можете посмотреть их в интернете, но мы будем использовать такие:
Java:
GL11.blendFuncSeparate(GL11.SRC_ALPHA, GL11.ONE_MINUS_SRC_ALPHA, GL11.ONE, GL11.ZERO);
Вкл:

Выкл:


Отрисовка примитивов
Будем продолжать изучение OpenGL постепенно. Раньше чтобы отрисовывать примитивы нужно было использовать glBegin и glEnd, но это максимально не оптимизированно и начиная с хуй пойми какой версии OpenGL 3 их убрали полностью, поэтому стоит использовать либо glDrawArrays, либо glDrawElements. Но за нас уже это сделали разработчики майнкрафта в удобном классе Tessellator и BufferBuilder.

Для начала нужно получить их:
Java:
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Далее выбрать примитив и формат вершин (максимально странное название):
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
Вот все примитивы:
  • GL_POINTS — каждая вершина задает точку
  • GL_LINES — каждая отдельная пара вершин задает линию
  • GL_LINE_STRIP — каждая пара вершин задает линию (т.е. конец предыдущей линии является началом следующей)
  • GL_LINE_LOOP — аналогично предыдущему за исключением того, что последняя вершина соединяется с первой и получается замкнутая фигура
  • GL_TRIANGLES — каждая отдельная тройка вершин задает треугольник
  • GL_TRIANGLE_STRIP — каждая следующая вершина задает треугольник вместе с двумя предыдущими (получается лента из треугольников)
  • GL_TRIANGLE_FAN — каждый треугольник задается первой вершиной и последующими парами (т.е. треугольники строятся вокруг первой вершины, образуя нечто похожее на диафрагму)
  • GL_QUADS — каждые четыре вершины образуют четырехугольник
  • GL_QUAD_STRIP — каждая следующая пара вершин образует четырехугольник вместе с парой предыдущих
  • GL_POLYGON — задает многоугольник с количеством углов равным количеству заданных вершин

И форматы вершин:
  • POSITION - все вершины имеют один цвет
  • POSITION_COLOR - каждая вершина имеет свой цвет
  • POSITION_TEX - каждая вершина имеет свою позицию текстуры (UV)
  • POSITION_COLOR_TEX - каждая вершина имеет свой цвет, который смешивается с текстурой
Взято с
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad)

Далее обозначить вершины и матрицу (вот только попробуйте забыть про матрицу, я потом покажу зачем это и какие приколы можно делать):
Java:
buffer.pos(matrix, 0, 0, 0).color(255, 0, 0, 255).endVertex();
buffer.pos(matrix, 0, 50, 0).color(0, 255, 0, 255).endVertex();
buffer.pos(matrix, 100, 50, 0).color(0, 0, 255, 255).endVertex();
buffer.pos(matrix, 100, 0, 0).color(255, 255, 255, 255).endVertex();
И отрисовать:
tessellator.draw();

Получается сейчас мы должны увидеть прямоугольник с 4 цветами, но мы идём нахуй:

А всё потому, что помимо blend нужно включить ещё и shade, для создания градиента:
Java:
// Включить
GL11.glShadeModel(GL11.GL_SMOOTH);

// Отрисовать

// Отключить
GL11.glShadeModel(GL11.GL_FLAT);

Вот теперь красота

Шейдеры
В коде, который вы видели ранее мы использовали "форматы вершин", в которых выбирали, что же конкретно нам рендерить, цвет, текстуру или всё сразу. На самом деле они просто переключали шейдер, вы даже можете посмотреть их код открыв .jar файл версии через архиватор и посмотреть в ассетах файлы с расширениями .fsh и .vsh. Но что же это и зачем нам это нужно?

Представим ситуацию, вы хотите нарисовать круг. Сейчас вы думаете, вроде всё логично, я буду рисовать линии (GL_LINE_STRIP) а точки по формуле круга (x = cos(radians(angle)) * radius, y = sin(radians(angle)) * radius). Я сделал это за вас, чтобы показать проблему:

P.S. Если не видите проблемы - приблизьтесь к монитору

Проблема заключается в том, что на элементы не накладывается сглаживание (antialiasing) и создаётся эффект "пиксельности". Очень давно был придуман лайфхак в котором нужно было рисовать точками (GL_POINTS) и включить GL_POINT_SMOOTH, но при настройках жирности как на моём скриншоте он всё равно также пиксельный.
UPD: Поправочка, пиксельность и означает "алиасинг", когда в процессе растеризации вектор проходит сквозь несколько пикселей, процесс фикса этого - "анти" (алиасинг)

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

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

P.S. Данный способ работает на версиях 1.20
Давайте напишем наш первый шейдер, который будет просто красить пиксель в указанный цвет:
Код:
#version 330 core // Версия шейдера, 330 означает OpenGL 3.3 с профилем core

uniform vec4 col; // Юниформа цвета, это параметр, который передаётся из кода в шейдер

out vec4 fragColor; // Переменная, в которую мы запишем цвет пикселя

void main() { // Основная функция
    fragColor = col; // Устанавливаем цвет пикселя на uniform
}
Теперь нужно шейдер создать, для этого воспользуемся уже существующими методами создания из майнкрафта, а именно классом ShaderInstance. Данный класс требует, чтобы мы создали дополнительные 2 папки: shaders и внутри core. Внутри папки core создадим .json файл с названием шейдера. Внутри него вставляем шаблон (вы можете найти шаблон сами в шейдерах майнкрафта):
JSON:
{
    "blend": {
        "func": "add",
        "srcrgb": "srcalpha",
        "dstrgb": "1-srcalpha"
    },
    "vertex": "",
    "fragment": "",
    "attributes": [
    ],
    "samplers": [
    ],
    "uniforms": [
        { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
        { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }
    ]
}
Сейчас расскажу, что к чему. Как вы могли догадаться - blend это аргументы для функции blendFuncSeparate. Далее идёт vertex и fragment - это название файлов для вершинного (vertex) и фрагментного (fragment) шейдера. Туда указываете свои названия, кстати, вершинный шейдер вам явно не придётся изменять, так что также возьмите шаблон:
Код:
#version 330 core

in vec3 Position; // Позиция вершины

uniform mat4 ModelViewMat; // Матрица model * view
uniform mat4 ProjMat; // Матрица проекции

void main() {
    mat4 mvp = ModelViewMat * ProjMat; // Матрица modelViewProjection.
    gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
}
Подробнее об этом можно ознакомится здесь (noad):
Пожалуйста, авторизуйтесь для просмотра ссылки.


Далее по желанию вы можете указать юниформы, у нас это цвет:
JSON:
{ "name": "color", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }
И атрибуты:
JSON:
"attributes": [
    "Color"
]
Приступаем к коду на джаве. Создадим ShaderInstance:
Java:
ShaderInstance shader = new ShaderInstance(Minecraft.getInstance().getResourceManager(), new ResourceLocation("название шейдера без .json"), DefaultVertexFormat.POSITION); // Подсказка: Если вы в дальнейшем будете писать мод, то у forge свой менеджер ресурсов
Рисуем прямоугольник как обычно, но есть нюанс, перед begin следует включить шейдер, а после отрисовки выключить.
На версиях 1.20 и выше это делается с помощью:
RenderSystem.setShader(() -> shader);
На версиях ниже используется:
GL20.glUseProgram(shader.getProgram());

До включения шейдера требуется установить значения юниформам (если они есть).

1.20+:
shader.getUniform("название").set(1.0f, 0.0f, 0.0f, 1.0f);
Ниже 1.17:
glUniform4f(glGetUniformLocation(shader.getProgram(), "название"), 1.0f, 0.0f, 0.0f, 1.0f);

Ну и ниже 1.17 после отрисовки следует отключить шейдер (на версиях выше это происходит автоматически):
GL20.glUseProgram(0);

Ну вот и всё, давайте проверим (кстати тестировал на forge 1.20.1):

Да, действительно всё работает. Всё же, мы хотели круг, гуглим: circle sdf shadertoy и натыкаемся на первую ссылочку (noad):
Пожалуйста, авторизуйтесь для просмотра ссылки.
Переносим на язык glsl (отличий очень мало) и получаем результат:

Вот вам круг, немного я проебался, но и хуй с ним :)
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
17 Май 2023
Сообщения
227
Реакции[?]
2
Поинты[?]
1K
Благодарю, вот это нормальная темка, для закрепление пойдет )
 
PoC Life
Пользователь
Статус
Оффлайн
Регистрация
22 Авг 2022
Сообщения
331
Реакции[?]
47
Поинты[?]
37K
Начинающий
Статус
Оффлайн
Регистрация
17 Май 2023
Сообщения
227
Реакции[?]
2
Поинты[?]
1K
я добавил нумерацию к каждой теме. следующие темы будут чуть позже, мб через час
Такими темами, югейм станет онли селфкодом, ну если кто то реально захочет разобраться, ладно не буду мешать, жду новенькую тему 😘
 
Пользователь
Статус
Оффлайн
Регистрация
23 Авг 2021
Сообщения
521
Реакции[?]
53
Поинты[?]
20K
Список:
1. Создаём главный класс + снимаем ограничения - https://yougame.biz/threads/325113
2. Автобус евентов - https://yougame.biz/threads/325114
3. OpenGL + Шейдеры + Рендер - https://yougame.biz/threads/325115

OpenGL
Конкретно в рендере мы остановимся по подробнее. Майнкрафт использует для рендера API OpenGL, считайте, чтобы напрямую не обращаться к видеокарте мы используем движок для отрисовки.

У данного API есть своя особенность, у него есть так скажем "переключатели". Каждый переключатель за что-то отвечает.
Java:
GL11.glEnable(константа); // Включить переключатель
GL11.glDisable(константа); // Выключить переключатель
Сейчас расскажу про каждый.

Переключатель глубины (Depth test)
Константа: GL11.GL_DEPTH_TEST
Выкл:
Посмотреть вложение 281649
Вкл:
Посмотреть вложение 281650

Отсечение граней
Константа: GL11.GL_CULL_FACE
Если переключатель включен, то отрисовка будет только с одной стороны.

Вкл:
С одной стороны:
Посмотреть вложение 281651
С другой:

Выкл - означает, что рисует всегда.

Blend (Смешивание)
Константа: GL11.GL_BLEND
Самая важный переключатель для нас, он позволяет смешивать цвета при наложении. Аргументов там много, можете посмотреть их в интернете, но мы будем использовать такие:
Java:
GL11.blendFuncSeparate(GL11.SRC_ALPHA, GL11.ONE_MINUS_SRC_ALPHA, GL11.ONE, GL11.ZERO);
Вкл:

Выкл:


Отрисовка примитивов
Будем продолжать изучение OpenGL постепенно. Раньше чтобы отрисовывать примитивы нужно было использовать glBegin и glEnd, но это максимально не оптимизированно и начиная с хуй пойми какой версии OpenGL 3 их убрали полностью, поэтому стоит использовать либо glDrawArrays, либо glDrawElements. Но за нас уже это сделали разработчики майнкрафта в удобном классе Tessellator и BufferBuilder.

Для начала нужно получить их:
Java:
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Далее выбрать примитив и формат вершин (максимально странное название):
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
Вот все примитивы:
  • GL_POINTS — каждая вершина задает точку
  • GL_LINES — каждая отдельная пара вершин задает линию
  • GL_LINE_STRIP — каждая пара вершин задает линию (т.е. конец предыдущей линии является началом следующей)
  • GL_LINE_LOOP — аналогично предыдущему за исключением того, что последняя вершина соединяется с первой и получается замкнутая фигура
  • GL_TRIANGLES — каждая отдельная тройка вершин задает треугольник
  • GL_TRIANGLE_STRIP — каждая следующая вершина задает треугольник вместе с двумя предыдущими (получается лента из треугольников)
  • GL_TRIANGLE_FAN — каждый треугольник задается первой вершиной и последующими парами (т.е. треугольники строятся вокруг первой вершины, образуя нечто похожее на диафрагму)
  • GL_QUADS — каждые четыре вершины образуют четырехугольник
  • GL_QUAD_STRIP — каждая следующая пара вершин образует четырехугольник вместе с парой предыдущих
  • GL_POLYGON — задает многоугольник с количеством углов равным количеству заданных вершин

И форматы вершин:
  • POSITION - все вершины имеют один цвет
  • POSITION_COLOR - каждая вершина имеет свой цвет
  • POSITION_TEX - каждая вершина имеет свою позицию текстуры (UV)
  • POSITION_COLOR_TEX - каждая вершина имеет свой цвет, который смешивается с текстурой
Взято с
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad)

Далее обозначить вершины и матрицу (вот только попробуйте забыть про матрицу, я потом покажу зачем это и какие приколы можно делать):
Java:
buffer.pos(matrix, 0, 0, 0).color(255, 0, 0, 255).endVertex();
buffer.pos(matrix, 0, 50, 0).color(0, 255, 0, 255).endVertex();
buffer.pos(matrix, 100, 50, 0).color(0, 0, 255, 255).endVertex();
buffer.pos(matrix, 100, 0, 0).color(255, 255, 255, 255).endVertex();
И отрисовать:
tessellator.draw();

Получается сейчас мы должны увидеть прямоугольник с 4 цветами, но мы идём нахуй:

А всё потому, что помимо blend нужно включить ещё и shade, для создания градиента:
Java:
// Включить
GL11.glShadeModel(GL11.GL_SMOOTH);

// Отрисовать

// Отключить
GL11.glShadeModel(GL11.GL_FLAT);

Вот теперь красота

Шейдеры
В коде, который вы видели ранее мы использовали "форматы вершин", в которых выбирали, что же конкретно нам рендерить, цвет, текстуру или всё сразу. На самом деле они просто переключали шейдер, вы даже можете посмотреть их код открыв .jar файл версии через архиватор и посмотреть в ассетах файлы с расширениями .fsh и .vsh. Но что же это и зачем нам это нужно?

Представим ситуацию, вы хотите нарисовать круг. Сейчас вы думаете, вроде всё логично, я буду рисовать линии (GL_LINE_STRIP) а точки по формуле круга (x = cos(radians(angle)) * radius, y = sin(radians(angle)) * radius). Я сделал это за вас, чтобы показать проблему:

P.S. Если не видите проблемы - приблизьтесь к монитору

Проблема заключается в том, что на элементы не накладывается сглаживание (antialiasing) и создаётся эффект "пиксельности". Очень давно был придуман лайфхак в котором нужно было рисовать точками (GL_POINTS) и включить GL_POINT_SMOOTH, но при настройках жирности как на моём скриншоте он всё равно также пиксельный. Для этого приходится использовать шейдер. Шейдеры бывают двух типов: Фрагментные - под каждый пиксель на экране вызывается функция, которая должна установить цвет данному пикселю и Вершинный - шейдер, который определяет позицию пикселю, это нужно, например: чтобы быстро домножать точки на матрицу.

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

P.S. Данный способ работает на версиях 1.20
Давайте напишем наш первый шейдер, который будет просто красить пиксель в указанный цвет:
Код:
#version 330 core // Версия шейдера, 330 означает OpenGL 3.3 с профилем core

uniform vec4 col; // Юниформа цвета, это параметр, который передаётся из кода в шейдер

out vec4 fragColor; // Переменная, в которую мы запишем цвет пикселя

void main() { // Основная функция
    fragColor = col; // Устанавливаем цвет пикселя на uniform
}
Теперь нужно шейдер создать, для этого воспользуемся уже существующими методами создания из майнкрафта, а именно классом ShaderInstance. Данный класс требует, чтобы мы создали дополнительные 2 папки: shaders и внутри core. Внутри папки core создадим .json файл с названием шейдера. Внутри него вставляем шаблон (вы можете найти шаблон сами в шейдерах майнкрафта):
JSON:
{
    "blend": {
        "func": "add",
        "srcrgb": "srcalpha",
        "dstrgb": "1-srcalpha"
    },
    "vertex": "",
    "fragment": "",
    "attributes": [
    ],
    "samplers": [
    ],
    "uniforms": [
        { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
        { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }
    ]
}
Сейчас расскажу, что к чему. Как вы могли догадаться - blend это аргументы для функции blendFuncSeparate. Далее идёт vertex и fragment - это название файлов для вершинного (vertex) и фрагментного (fragment) шейдера. Туда указываете свои названия, кстати, вершинный шейдер вам явно не придётся изменять, так что также возьмите шаблон:
Код:
#version 330 core

in vec3 Position; // Позиция вершины

uniform mat4 ModelViewMat; // Матрица model * view
uniform mat4 ProjMat; // Матрица проекции

void main() {
    mat4 mvp = ModelViewMat * ProjMat; // Матрица modelViewProjection.
    gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
}
Подробнее об этом можно ознакомится здесь (noad):
Пожалуйста, авторизуйтесь для просмотра ссылки.


Далее по желанию вы можете указать юниформы, у нас это цвет:
JSON:
{ "name": "color", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }
И атрибуты:
JSON:
"attributes": [
    "Color"
]
Приступаем к коду на джаве. Создадим ShaderInstance:
Java:
ShaderInstance shader = new ShaderInstance(Minecraft.getInstance().getResourceManager(), new ResourceLocation("название шейдера без .json"), DefaultVertexFormat.POSITION); // Подсказка: Если вы в дальнейшем будете писать мод, то у forge свой менеджер ресурсов
Рисуем прямоугольник как обычно, но есть нюанс, перед begin следует включить шейдер, а после отрисовки выключить.
На версиях 1.20 и выше это делается с помощью:
RenderSystem.setShader(() -> shader);
На версиях ниже используется:
GL20.glUseProgram(shader.getProgram());

До включения шейдера требуется установить значения юниформам (если они есть).

1.20+:
shader.getUniform("название").set(1.0f, 0.0f, 0.0f, 1.0f);
Ниже 1.17:
glUniform4f(glGetUniformLocation(shader.getProgram(), "название"), 1.0f, 0.0f, 0.0f, 1.0f);

Ну и ниже 1.17 после отрисовки следует отключить шейдер (на версиях выше это происходит автоматически):
GL20.glUseProgram(0);

Ну вот и всё, давайте проверим (кстати тестировал на forge 1.20.1):

Да, действительно всё работает. Всё же, мы хотели круг, гуглим: circle sdf shadertoy и натыкаемся на первую ссылочку (noad):
Пожалуйста, авторизуйтесь для просмотра ссылки.
Переносим на язык glsl (отличий очень мало) и получаем результат:

Вот вам круг, немного я проебался, но и хуй с ним :)
лучшая тема из всех трёх, которые ты написал. молодец.
иди лучше в рендер, тебе он легко даётся
 
Начинающий
Статус
Оффлайн
Регистрация
17 Май 2023
Сообщения
227
Реакции[?]
2
Поинты[?]
1K
Кстати, хочу ещё написать, то что в основном рендер поломан на амд видео картах, он там пиксельнье, даже если взять трейсера, то мы увидим большуб разницу...
 
Начинающий
Статус
Оффлайн
Регистрация
13 Июл 2024
Сообщения
58
Реакции[?]
1
Поинты[?]
2K
Я не знаю кем надо быть чтобы не разобраться в теселятора, сука в теселятора в котором блять невозможно заблудиться. Это пиздец
 
Начинающий
Статус
Оффлайн
Регистрация
26 Янв 2024
Сообщения
73
Реакции[?]
1
Поинты[?]
1K
Список:
1. Создаём главный класс + снимаем ограничения - https://yougame.biz/threads/325113
2. Автобус евентов - https://yougame.biz/threads/325114
3. OpenGL + Шейдеры + Рендер - https://yougame.biz/threads/325115
4. Аим - https://yougame.biz/threads/325188
5. GlowESP aka OpenGL Framebuffer - https://yougame.biz/threads/325211/
6. Текст, шрифты, атлас - https://yougame.biz/threads/325292

OpenGL
Конкретно в рендере мы остановимся по подробнее. Майнкрафт использует для рендера API OpenGL, считайте, чтобы напрямую не обращаться к видеокарте мы используем движок для отрисовки.

У данного API есть своя особенность, у него есть так скажем "переключатели". Каждый переключатель за что-то отвечает.
Java:
GL11.glEnable(константа); // Включить переключатель
GL11.glDisable(константа); // Выключить переключатель
Сейчас расскажу про каждый.

Переключатель глубины (Depth test)
Константа: GL11.GL_DEPTH_TEST
Выкл:
Посмотреть вложение 281649
Вкл:
Посмотреть вложение 281650

Отсечение граней
Константа: GL11.GL_CULL_FACE
Если переключатель включен, то отрисовка будет только с одной стороны.

Вкл:
С одной стороны:
Посмотреть вложение 281651
С другой:

Выкл - означает, что рисует всегда.

Blend (Смешивание)
Константа: GL11.GL_BLEND
Самая важный переключатель для нас, он позволяет смешивать цвета при наложении. Аргументов там много, можете посмотреть их в интернете, но мы будем использовать такие:
Java:
GL11.blendFuncSeparate(GL11.SRC_ALPHA, GL11.ONE_MINUS_SRC_ALPHA, GL11.ONE, GL11.ZERO);
Вкл:

Выкл:


Отрисовка примитивов
Будем продолжать изучение OpenGL постепенно. Раньше чтобы отрисовывать примитивы нужно было использовать glBegin и glEnd, но это максимально не оптимизированно и начиная с хуй пойми какой версии OpenGL 3 их убрали полностью, поэтому стоит использовать либо glDrawArrays, либо glDrawElements. Но за нас уже это сделали разработчики майнкрафта в удобном классе Tessellator и BufferBuilder.

Для начала нужно получить их:
Java:
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Далее выбрать примитив и формат вершин (максимально странное название):
buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
Вот все примитивы:
  • GL_POINTS — каждая вершина задает точку
  • GL_LINES — каждая отдельная пара вершин задает линию
  • GL_LINE_STRIP — каждая пара вершин задает линию (т.е. конец предыдущей линии является началом следующей)
  • GL_LINE_LOOP — аналогично предыдущему за исключением того, что последняя вершина соединяется с первой и получается замкнутая фигура
  • GL_TRIANGLES — каждая отдельная тройка вершин задает треугольник
  • GL_TRIANGLE_STRIP — каждая следующая вершина задает треугольник вместе с двумя предыдущими (получается лента из треугольников)
  • GL_TRIANGLE_FAN — каждый треугольник задается первой вершиной и последующими парами (т.е. треугольники строятся вокруг первой вершины, образуя нечто похожее на диафрагму)
  • GL_QUADS — каждые четыре вершины образуют четырехугольник
  • GL_QUAD_STRIP — каждая следующая пара вершин образует четырехугольник вместе с парой предыдущих
  • GL_POLYGON — задает многоугольник с количеством углов равным количеству заданных вершин

И форматы вершин:
  • POSITION - все вершины имеют один цвет
  • POSITION_COLOR - каждая вершина имеет свой цвет
  • POSITION_TEX - каждая вершина имеет свою позицию текстуры (UV)
  • POSITION_COLOR_TEX - каждая вершина имеет свой цвет, который смешивается с текстурой
Взято с
Пожалуйста, авторизуйтесь для просмотра ссылки.
(noad)

Далее обозначить вершины и матрицу (вот только попробуйте забыть про матрицу, я потом покажу зачем это и какие приколы можно делать):
Java:
buffer.pos(matrix, 0, 0, 0).color(255, 0, 0, 255).endVertex();
buffer.pos(matrix, 0, 50, 0).color(0, 255, 0, 255).endVertex();
buffer.pos(matrix, 100, 50, 0).color(0, 0, 255, 255).endVertex();
buffer.pos(matrix, 100, 0, 0).color(255, 255, 255, 255).endVertex();
И отрисовать:
tessellator.draw();

Получается сейчас мы должны увидеть прямоугольник с 4 цветами, но мы идём нахуй:

А всё потому, что помимо blend нужно включить ещё и shade, для создания градиента:
Java:
// Включить
GL11.glShadeModel(GL11.GL_SMOOTH);

// Отрисовать

// Отключить
GL11.glShadeModel(GL11.GL_FLAT);

Вот теперь красота

Шейдеры
В коде, который вы видели ранее мы использовали "форматы вершин", в которых выбирали, что же конкретно нам рендерить, цвет, текстуру или всё сразу. На самом деле они просто переключали шейдер, вы даже можете посмотреть их код открыв .jar файл версии через архиватор и посмотреть в ассетах файлы с расширениями .fsh и .vsh. Но что же это и зачем нам это нужно?

Представим ситуацию, вы хотите нарисовать круг. Сейчас вы думаете, вроде всё логично, я буду рисовать линии (GL_LINE_STRIP) а точки по формуле круга (x = cos(radians(angle)) * radius, y = sin(radians(angle)) * radius). Я сделал это за вас, чтобы показать проблему:

P.S. Если не видите проблемы - приблизьтесь к монитору

Проблема заключается в том, что на элементы не накладывается сглаживание (antialiasing) и создаётся эффект "пиксельности". Очень давно был придуман лайфхак в котором нужно было рисовать точками (GL_POINTS) и включить GL_POINT_SMOOTH, но при настройках жирности как на моём скриншоте он всё равно также пиксельный.
UPD: Поправочка, пиксельность и означает "алиасинг", когда в процессе растеризации вектор проходит сквозь несколько пикселей, процесс фикса этого - "анти" (алиасинг)

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

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

P.S. Данный способ работает на версиях 1.20
Давайте напишем наш первый шейдер, который будет просто красить пиксель в указанный цвет:
Код:
#version 330 core // Версия шейдера, 330 означает OpenGL 3.3 с профилем core

uniform vec4 col; // Юниформа цвета, это параметр, который передаётся из кода в шейдер

out vec4 fragColor; // Переменная, в которую мы запишем цвет пикселя

void main() { // Основная функция
    fragColor = col; // Устанавливаем цвет пикселя на uniform
}
Теперь нужно шейдер создать, для этого воспользуемся уже существующими методами создания из майнкрафта, а именно классом ShaderInstance. Данный класс требует, чтобы мы создали дополнительные 2 папки: shaders и внутри core. Внутри папки core создадим .json файл с названием шейдера. Внутри него вставляем шаблон (вы можете найти шаблон сами в шейдерах майнкрафта):
JSON:
{
    "blend": {
        "func": "add",
        "srcrgb": "srcalpha",
        "dstrgb": "1-srcalpha"
    },
    "vertex": "",
    "fragment": "",
    "attributes": [
    ],
    "samplers": [
    ],
    "uniforms": [
        { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
        { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }
    ]
}
Сейчас расскажу, что к чему. Как вы могли догадаться - blend это аргументы для функции blendFuncSeparate. Далее идёт vertex и fragment - это название файлов для вершинного (vertex) и фрагментного (fragment) шейдера. Туда указываете свои названия, кстати, вершинный шейдер вам явно не придётся изменять, так что также возьмите шаблон:
Код:
#version 330 core

in vec3 Position; // Позиция вершины

uniform mat4 ModelViewMat; // Матрица model * view
uniform mat4 ProjMat; // Матрица проекции

void main() {
    mat4 mvp = ModelViewMat * ProjMat; // Матрица modelViewProjection.
    gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
}
Подробнее об этом можно ознакомится здесь (noad):
Пожалуйста, авторизуйтесь для просмотра ссылки.


Далее по желанию вы можете указать юниформы, у нас это цвет:
JSON:
{ "name": "color", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }
И атрибуты:
JSON:
"attributes": [
    "Color"
]
Приступаем к коду на джаве. Создадим ShaderInstance:
Java:
ShaderInstance shader = new ShaderInstance(Minecraft.getInstance().getResourceManager(), new ResourceLocation("название шейдера без .json"), DefaultVertexFormat.POSITION); // Подсказка: Если вы в дальнейшем будете писать мод, то у forge свой менеджер ресурсов
Рисуем прямоугольник как обычно, но есть нюанс, перед begin следует включить шейдер, а после отрисовки выключить.
На версиях 1.20 и выше это делается с помощью:
RenderSystem.setShader(() -> shader);
На версиях ниже используется:
GL20.glUseProgram(shader.getProgram());

До включения шейдера требуется установить значения юниформам (если они есть).

1.20+:
shader.getUniform("название").set(1.0f, 0.0f, 0.0f, 1.0f);
Ниже 1.17:
glUniform4f(glGetUniformLocation(shader.getProgram(), "название"), 1.0f, 0.0f, 0.0f, 1.0f);

Ну и ниже 1.17 после отрисовки следует отключить шейдер (на версиях выше это происходит автоматически):
GL20.glUseProgram(0);

Ну вот и всё, давайте проверим (кстати тестировал на forge 1.20.1):

Да, действительно всё работает. Всё же, мы хотели круг, гуглим: circle sdf shadertoy и натыкаемся на первую ссылочку (noad):
Пожалуйста, авторизуйтесь для просмотра ссылки.
Переносим на язык glsl (отличий очень мало) и получаем результат:

Вот вам круг, немного я проебался, но и хуй с ним :)
лучшее что видел в югейме)
 
Сверху Снизу