WARNING
Попри те, що Minecraft створено з використанням OpenGL, починаючи з версії 1.17+ ви не можете використовувати застарілі методи OpenGL для рендеру власних речей. Замість цього ви повинні використовувати нову систему BufferBuilder, яка форматує дані рендера та завантажує їх до OpenGL для малювання.
Загалом, ви можете використовувати систему рендера Minecraft або створити власну, яка використовує GL.glDrawElements().
ВАЖЛИВЕ ОНОВЛЕННЯ
Починаючи з 1.21.6, у конвеєр рендера впроваджуються значні зміни, такі як перехід до RenderTypes і RenderPipelines і, що більш важливо, RenderState, з кінцевою метою можливості підготувати наступний кадр під час малювання поточного кадру. На етапі «підготовки» всі дані гри, які використовуються для рендеру, витягуються до RenderState, тому інший потік може працювати над малюванням цього кадру, поки видобувається наступний кадр.
Наприклад, у версії 1.21.8 рендер інтерфейсу використовує цю модель, а методи GuiGraphics просто додають до стану промальовування. Фактичне завантаження до BufferBuilder відбувається наприкінці підготовчого етапу, після того, як усі елементи були додані до RenderState. Див. GuiRenderer#prepare.
Ця стаття охоплює основи рендера та, попри те, що вона все ще є певною мірою актуальною, у більшості випадків існують вищі рівні абстракцій для кращої продуктивності та сумісності. Для отримання додаткової інформації див. рендер у світі.
На цій сторінці описано основи рендера за допомогою нової системи, а також ключову термінологію та поняття.
Хоча більша частина рендера в Minecraft абстрагується за допомогою різних методів GuiGraphics, і вам, ймовірно, не потрібно чіпати нічого, згаданого тут, все одно важливо розуміти основи того, як працює рендер.
Tesselator
Tesselator — це основний клас, який використовується для рендера речей у Minecraft. В грі є лише один такий. Ви можете отримати екземпляр за допомогою Tesselator.getInstance().
BufferBuilder
BufferBuilder — це клас, який використовується для форматування та завантаження даних рендера в OpenGL. Він використовується для створення буфера, який потім завантажується в OpenGL для малювання.
Tesselator використовується для створення BufferBuilder, який використовується для форматування та завантаження даних рендера в OpenGL.
Ініціалізація BufferBuilder
Перш ніж ви зможете записати щось у BufferBuilder, ви повинні ініціалізувати його. Це робиться за допомогою методу Tesselator#begin(…), який використовує VertexFormat і режим малювання та повертає BufferBuilder.
Формати вершин
VertexFormat визначає елементи, які ми включаємо в наш буфер даних, і описує, як ці елементи мають бути передані в OpenGL.
У DefaultVertexFormat доступні наступні типові елементи VertexFormat:
| Елемент | Формат |
|---|---|
EMPTY | { } |
BLOCK | { position, color, texture uv, texture light (2 shorts), texture normal (3 sbytes) } |
NEW_ENTITY | { position, color, texture uv, overlay (2 shorts), texture light, normal (3 sbytes) } |
PARTICLE | { position, texture uv, color, texture light } |
POSITION | { position } |
POSITION_COLOR | { position, color } |
POSITION_COLOR_NORMAL | { position, color, normal } |
POSITION_COLOR_LIGHTMAP | { position, color, light } |
POSITION_TEX | { position, uv } |
POSITION_TEX_COLOR | { position, uv, color } |
POSITION_COLOR_TEX_LIGHTMAP | { position, color, uv, light } |
POSITION_TEX_LIGHTMAP_COLOR | { position, uv, light, color } |
POSITION_TEX_COLOR_NORMAL | { position, uv, color, normal } |
Режими малювання
Режим малювання визначає спосіб малювання даних. У VertexFormat.Mode доступні такі режими малювання:
| Режим малювання | Опис |
|---|---|
LINES | Кожен елемент складається з 2 вершин і представлений у вигляді однієї лінії. |
LINE_STRIP | Для першого елемента потрібно 2 вершини. Додаткові елементи малюються лише з 1 новою вершиною, створюючи суцільну лінію. |
DEBUG_LINES | Подібно до Mode.LINES, але ширина лінії на екрані завжди становить рівно один піксель. |
DEBUG_LINE_STRIP | Те саме, що Mode.LINE_STRIP, але ширина ліній завжди один піксель. |
TRIANGLES | Кожен елемент складається з 3 вершин, які утворюють трикутник. |
TRIANGLE_STRIP | Починається з 3 вершин для першого трикутника. Кожна додаткова вершина утворює новий трикутник із двома останніми вершинами. |
TRIANGLE_FAN | Починається з 3 вершин для першого трикутника. Кожна додаткова вершина утворює новий трикутник з першою вершиною та останньою вершиною. |
QUADS | Кожен елемент складається з 4 вершин, що утворюють чотирикутник. |
Запис до BufferBuilder
Після ініціалізації BufferBuilder ви можете записати дані в нього.
BufferBuilder дозволяє нам побудувати наш буфер, вершина за вершиною. Щоб додати вершину, ми використовуємо метод buffer.addVertex(Matrix4f, float, float, float). Параметр Matrix4f — це матриця перетворення, яку ми обговоримо більш детально пізніше. Три параметри float представляють (x, y, z) координати положення вершини.
Цей метод повертає конструктор вершин, за допомогою якого ми можемо вказати додаткову інформацію для вершини. Під час додавання цієї інформації вкрай важливо дотримуватися порядку визначеного нами VertexFormat. Якщо ми цього не зробимо, OpenGL може неправильно інтерпретувати наші дані. Після того, як ми закінчили побудову вершини, просто продовжуйте додавати інші вершини та дані до буфера, доки не закінчите.
Також варто зрозуміти концепцію вибракування. Вибракування — це процес видалення меж тривимірної форми, які не видно з точки зору глядача. Якщо вершини межі вказано в неправильному порядку, межа може не рендеритися належним чином через вибракування.
Що таке матриця перетворення?
Матриця перетворення — це матриця 4x4, яка використовується для перетворення вектора. У Minecraft матриця трансформації просто перетворює координати, які ми надаємо, у виклик addVertex. Перетворення можуть масштабувати нашу модель, переміщувати її та обертати.
Її іноді називають матрицею позиції або матрицею моделі.
Зазвичай його отримують за допомогою класу Matrix3x2fStack, який можна отримати за допомогою об’єкта GuiGraphics, викликавши метод GuiGraphics#pose().
Рендер трикутної смуги
Простіше пояснити, як писати в BufferBuilder, використовуючи практичний приклад. Скажімо, ми хочемо щось відрендерити за допомогою режиму малювання VertexFormat.Mode.TRIANGLE_STRIP і формату вершини POSITION_COLOR.
Ми збираємося намалювати вершини в наступних точках на HUD (по черзі):
text
(20, 20)
(5, 40)
(35, 40)
(20, 60)Це має дати нам чудовий ромб — оскільки ми використовуємо режим малювання TRIANGLE_STRIP, рендер виконає наступні кроки:

Оскільки в цьому прикладі ми малюємо HUD, ми використаємо подію HudRenderCallback:
ВАЖЛИВЕ ОНОВЛЕННЯ
Починаючи з 1.21.8, стек матриць, який передається для рендеру HUD, було змінено з PoseStack на Matrix3x2fStack. Більшість методів дещо відрізняються й більше не приймають параметр z, але поняття ті самі.
Крім того, наведений нижче код не повністю відповідає поясненню вище: вам не потрібно вручну писати в BufferBuilder, оскільки методи GuiGraphics автоматично записують у BufferBuilder HUD під час підготовки.
Прочитайте важливе оновлення вище, щоб дізнатися більше.
Реєстрація елемента:
java
HudElementRegistry.addLast(Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "last_element"), hudLayer());1
Реалізація hudLayer():
java
private HudElement hudLayer() {
return (graphics, deltaTracker) -> {
// :::2
Matrix3x2fStack matrices = graphics.pose();
// Store the total tick delta in a field, so we can use it later.
totalTickProgress += deltaTracker.getGameTimeDeltaPartialTick(true);
// Push a new matrix onto the stack.
matrices.pushMatrix();
// :::2
// :::2
// Scale the matrix by 0.5 to make the triangle smaller and larger over time.
float scaleAmount = Mth.sin(totalTickProgress / 10F) / 2F + 1.5F;
// Apply the scaling amount to the matrix.
// We don't need to scale the Z axis since it's on the HUD and 2D.
matrices.scale(scaleAmount, scaleAmount);
// :::2
matrices.scale(1 / scaleAmount, 1 / scaleAmount);
matrices.translate(60f, 60f);
// :::3
// Lerp between 0 and 360 degrees over time.
float rotationAmount = totalTickProgress / 50F % 360;
matrices.rotate(rotationAmount);
// Shift entire square so that it rotates in its center.
matrices.translate(-20f, -40f);
// :::3
// :::2
// We do not need to manually write to the buffer. GuiGraphics methods write to GUI buffer in `GuiRenderer` at the end of preparation.
// Pop our matrix from the stack.
matrices.popMatrix();
// :::2
};
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Це призводить до того, що на HUD малюється наступне:

TIP
Спробуйте повозитися з кольорами та розташуванням вершин, щоб побачити, що вийде! Ви також можете спробувати використовувати різні режими малювання та формати вершин.
PoseStack
WARNING
Код цього розділу та текст обговорюють різні речі!
Код демонструє Matrix3x2fStack, який використовується для рендера HUD з 1.21.8, тоді як текст описує PoseStack, який має дещо інші методи.
Прочитайте важливе оновлення вище, щоб дізнатися більше.
Навчившись писати в BufferBuilder, ви можете вдаватися в питання, як трансформувати вашу модель — або навіть анімувати її. Тут на допомогу приходить клас PoseStack.
Клас PoseStack має такі методи:
pushPose()— надсилає нову матрицю в стек.popPose()— знімає верхню матрицю зі стек.last()— повертає верхню матрицю в стеку.translate(x, y, z)— перекладає верхню матрицю в стек.translate(vec3)scale(x, y, z)— масштабує верхню матрицю в стеці.
Ви також можете помножити верхню матрицю в стеці за допомогою кватерніонів, які ми розглянемо в наступному розділі.
Беручи з нашого прикладу вище, ми можемо збільшити та зменшити масштаб ромбу за допомогою PoseStack і tickDelta — це «прогрес» між останнім ігровим тактом і наступним ігровим тактом. Ми роз’яснимо це пізніше на сторінці рендера в HUD.
WARNING
Ви повинні спочатку проштовхнути стек матриць, а потім витягнути його, коли закінчите з ним. Якщо ви цього не зробите, ви отримаєте зламаний стек матриць, що спричинить проблеми з рендером.
Переконайтеся, що натиснули стек матриць, перш ніж отримати матрицю перетворення!
java
Matrix3x2fStack matrices = graphics.pose();
// Store the total tick delta in a field, so we can use it later.
totalTickProgress += deltaTracker.getGameTimeDeltaPartialTick(true);
// Push a new matrix onto the stack.
matrices.pushMatrix();
// Scale the matrix by 0.5 to make the triangle smaller and larger over time.
float scaleAmount = Mth.sin(totalTickProgress / 10F) / 2F + 1.5F;
// Apply the scaling amount to the matrix.
// We don't need to scale the Z axis since it's on the HUD and 2D.
matrices.scale(scaleAmount, scaleAmount);
// We do not need to manually write to the buffer. GuiGraphics methods write to GUI buffer in `GuiRenderer` at the end of preparation.
// Pop our matrix from the stack.
matrices.popMatrix();1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Кватерніони (обертові речі)
WARNING
Код цього розділу та текст обговорюють різні речі!
Код демонструє рендер на HUD, тоді як текст описує рендер простору 3D-світу.
Прочитайте важливе оновлення вище, щоб дізнатися більше.
Кватерніони — це спосіб представлення обертання в тривимірному просторі. Вони використовуються для обертання верхньої матриці на PoseStack за допомогою методу rotateAround(quaternionfc, x, y, z).
Дуже малоймовірно, що вам коли-небудь доведеться використовувати клас Quaternion безпосередньо, оскільки Minecraft надає різні попередньо зібрані екземпляри Quaternion у своєму класі утиліт Axis.
Скажімо, ми хочемо повернути наш квадрат навколо осі z. Ми можемо зробити це за допомогою методів PoseStack і rotateAround(quaternionfc, x, y, z).
java
// Lerp between 0 and 360 degrees over time.
float rotationAmount = totalTickProgress / 50F % 360;
matrices.rotate(rotationAmount);
// Shift entire square so that it rotates in its center.
matrices.translate(-20f, -40f);1
2
3
4
5
2
3
4
5
Результатом цього є наступне:




