🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
🇮🇹 Italiano (Italian)
🇮🇹 Italiano (Italian)
Aspetto
This page is written for:
1.21
This page is written for:
1.21
WARNING
Although Minecraft is built using OpenGL, as of version 1.17+ you cannot use legacy OpenGL methods to render your own things. Instead, you must use the new BufferBuilder
system, which formats rendering data and uploads it to OpenGL to draw.
Per riassumere, devi usare il sistema di rendering di Minecraft, o crearne uno tuo che usa GL.glDrawElements()
.
Questa pagina tratterà le basi del rendering usando il nuovo sistema, presentando terminologia e concetti chiave.
Anche se molto del rendering in Minecraft viene astratto attraverso i vari metodi DrawContext
, e probabilmente non ti servirà toccare nulla di quel che viene menzionato qui, è comunque importante capire le basi di come funziona il rendering.
Tessellator
Il Tessellator
è la principale classe usata per renderizzare le cose in Minecraft. È un singleton, cioè solo un'istanza è presente in gioco. Puoi ottenere l'istanza usando Tessellator.getInstance()
.
BufferBuilder
Il BufferBuilder
è la classe usata per formattare e caricare i dati di rendering su OpenGL. Viene usata per creare un buffer, che viene caricato su OpenGL per essere disegnato.
Il Tessellator
viene usato per creare un BufferBuilder
, che viene usato per formattare e caricare i dati di rendering su OpenGL.
BufferBuilder
Prima di poter scrivere al BufferBuilder
, devi inizializzarlo. Questo viene fatto usando Tessellator#begin(...)
, che prende un VertexFormat
e una modalità di disegno e restituisce un BufferBuilder
.
Il VertexFormat
definisce gli elementi che includiamo nel nostro buffer di dati e precisa come questi elementi debbano essere trasmessi a OpenGL.
I seguenti elementi VertexFormat
sono disponibili:
Elemento | Formato |
---|---|
BLIT_SCREEN | { position (3 floats: x, y and z), uv (2 floats), color (4 ubytes) } |
POSITION_COLOR_TEXTURE_LIGHT_NORMAL | { position, color, texture uv, texture light (2 shorts), texture normal (3 sbytes) } |
POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL | { position, color, texture uv, overlay (2 shorts), texture light, normal (3 sbytes) } |
POSITION_TEXTURE_COLOR_LIGHT | { position, texture uv, color, texture light } |
POSITION | { position } |
POSITION_COLOR | { position, color } |
LINES | { position, color, normal } |
POSITION_COLOR_LIGHT | { position, color, light } |
POSITION_TEXTURE | { position, uv } |
POSITION_COLOR_TEXTURE | { position, color, uv } |
POSITION_TEXTURE_COLOR | { position, uv, color } |
POSITION_COLOR_TEXTURE_LIGHT | { position, color, uv, light } |
POSITION_TEXTURE_LIGHT_COLOR | { position, uv, light, color } |
POSITION_TEXTURE_COLOR_NORMAL | { position, uv, color, normal } |
La modalità di disegno definisce come sono disegnati i dati. Sono disponibili le seguenti modalità di disegno:
Modalità di Disegno | Descrizione |
---|---|
DrawMode.LINES | Ogni elemento è fatto da 2 vertici ed è rappresentato come una linea singola. |
DrawMode.LINE_STRIP | Il primo elemento richiede 2 vertici. Elementi addizionali vengono disegnati con un solo nuovo vertice, creando una linea continua. |
DrawMode.DEBUG_LINES | Simile a DrawMode.LINES , ma la linea è sempre esattamente larga un pixel sullo schermo. |
DrawMode.DEBUG_LINE_STRIP | Come DrawMode.LINE_STRIP , ma le linee sono sempre larghe un pixel. |
DrawMode.TRIANGLES | Ogni elemento è fatto da 3 vertici, formando un triangolo. |
DrawMode.TRIANGLE_STRIP | Inizia con 3 vertici per il primo triangolo. Ogni vertice aggiuntivo forma un nuovo triangolo con gli ultimi due vertici. |
DrawMode.TRIANGLE_FAN | Inizia con 3 vertici per il primo triangolo. Ogni vertice aggiuntivo forma un triangolo con il primo e l'ultimo vertice. |
DrawMode.QUADS | Ogni elemento è fatto da 4 vertici, formando un quadrilatero. |
BufferBuilder
Una volta che il BufferBuilder
è inizializzato, puoi scriverci dei dati.
Il BufferBuilder
permette di costruire il nostro buffer, un vertice dopo l'altro. Per aggiungere un vertice, usiamo il metodo buffer.vertex(matrix, float, float, float)
. Il parametro matrix
è la matrice di trasformazione, che discuteremo più dettagliatamente in seguito. I tre parametri float rappresentano le coordinate (x, y, z) della posizione del vertice.
Questo metodo restituisce un costruttore di vertice, che possiamo usare per specificare informazioni addizionali per il vertice. È cruciale seguire l'ordine del nostro VertexFormat
definito quando aggiungiamo questa informazione. Se non lo facciamo, OpenGL potrebbe non interpretare i nostri dati correttamente. Dopo aver finito la costruzione di un vertice, se vuoi puoi continuare ad aggiungere altri vertici e dati al buffer.
Importante è anche capire il concetto di culling. Il culling è il processo con cui si rimuovono facce di una forma 3D che non sono visibili dalla prospettiva dell'osservatore. Se i vertici per una faccia sono specificati nell'ordine sbagliato, la faccia potrebbe non essere renderizzata correttamente a causa del culling.
Una matrice di trasformazione è una matrice 4x4 che viene usata per trasformare un vettore. In Minecraft, la matrice di trasformazione sta solo trasformando le coordinate che diamo nella chiamata del vertice. Le trasformazioni possono scalare il nostro modello, muoverlo e ruotarlo.
A volte viene chiamata anche matrice di posizione, o matrice modello.
Solitamente è ottenuta dalla classe MatrixStack
, che può essere ottenuta attraverso l'oggetto DrawContext
:
drawContext.getMatrices().peek().getPositionMatrix();
Spiegare come scrivere al BufferBuilder
è più semplice con un esempio pratico. Immaginiamo di voler renderizzare qualcosa usando la modalità di disegno DrawMode.TRIANGLE_STRIP
e il formato vertice POSITION_COLOR
.
Disegneremo vertici nelle seguenti posizioni sul HUD (in ordine):
(20, 20)
(5, 40)
(35, 40)
(20, 60)
Questo dovrebbe darci un diamante carino - siccome stiamo usando la modalità di disegno TRIANGLE_STRIP
, il renderizzatore eseguirà i seguenti passaggi:
Siccome stiamo disegnando sulla HUD in questo esempio, useremo l'evento HudRenderCallback
:
HudRenderCallback.EVENT.register((drawContext, tickDeltaManager) -> {
// Get the transformation matrix from the matrix stack, alongside the tessellator instance and a new buffer builder.
Matrix4f transformationMatrix = drawContext.getMatrices().peek().getPositionMatrix();
Tessellator tessellator = Tessellator.getInstance();
// Begin a triangle strip buffer using the POSITION_COLOR vertex format.
BufferBuilder buffer = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR);
// Write our vertices, Z doesn't really matter since it's on the HUD.
buffer.vertex(transformationMatrix, 20, 20, 5).color(0xFF414141);
buffer.vertex(transformationMatrix, 5, 40, 5).color(0xFF000000);
buffer.vertex(transformationMatrix, 35, 40, 5).color(0xFF000000);
buffer.vertex(transformationMatrix, 20, 60, 5).color(0xFF414141);
// We'll get to this bit in the next section.
RenderSystem.setShader(GameRenderer::getPositionColorProgram);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
// Draw the buffer onto the screen.
BufferRenderer.drawWithGlobalProgram(buffer.end());
});
Questo risulta nel seguente disegno sul HUD:
TIP
Prova a giocare coi colori e le posizioni dei vertici per vedere che succede! Puoi anche provare a usare modalità di disegno e formati vertice differenti.
MatrixStack
Dopo aver imparato come scrivere al BufferBuilder
, ti starai chiedendo come trasformare il tuo modello - anche animarlo magari. Qui è dove entra in gioco la classe MatrixStack
.
La classe MatrixStack
ha i seguenti metodi:
push()
- Spinge una nuova matrice sullo stack.pop()
- Elimina la matrice in cima allo stack.peek()
- Restituisce la matrice in cima allo stack.translate(x, y, z)
- Trasla la matrice in cima allo stack.scale(x, y, z)
- Scala la matrice in cima allo stack.Puoi anche moltiplicare la matrice in cima allo stack usando i quaternioni, che tratteremo nella prossima sezione.
Usando l'esempio di prima, possiamo ingrandire e rimpicciolire il nostro diamante usando la MatrixStack
e il tickDelta
- che è il tempo passato dall'ultimo frame.
WARNING
You must first push the matrix stack and then pop it after you're done with it. If you don't, you'll end up with a broken matrix stack, which will cause rendering issues.
Assicurati di spingere lo stack di matrici prima di prendere una matrice di trasformazione!
MatrixStack matrices = drawContext.getMatrices();
// Store the total tick delta in a field, so we can use it later.
totalTickDelta += tickDeltaManager.getTickDelta(true);
// Push a new matrix onto the stack.
matrices.push();
// Scale the matrix by 0.5 to make the triangle smaller and larger over time.
float scaleAmount = MathHelper.sin(totalTickDelta / 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, 1F);
// ... write to the buffer.
// Pop our matrix from the stack.
matrices.pop();
I quaternioni sono un modo di rappresentare rotazioni in uno spazio 3D. Vengono usate per ruotare la matrice in cima al MatrixStack
usando il metodo multiply(Quaternion, x, y, z)
.
Difficilmente dovrai usare una classe Quaternion direttamente, siccome Minecraft fornisce varie istanze Quaternion pre-costruite nella sua classe di utilità RotationAxis
.
Immaginiamo di voler ruotare il nostro diamante attorno all'asse z. Possiamo farlo usando il MatrixStack
e il metodo multiply(Quaternion, x, y, z)
.
// Lerp between 0 and 360 degrees over time.
float rotationAmount = (float) (totalTickDelta / 50F % 360);
matrices.multiply(RotationAxis.POSITIVE_Z.rotation(rotationAmount));
// Shift entire diamond so that it rotates in its center.
matrices.translate(-20f, -40f, 0f);
Il risultato è il seguente: