About 3D graphics in GMS2. Part 1 of 2

What is a vertex buffer? How do you create a 3D object and render it to the screen? What is the vertex format for and how does a vertex shader work with it? How does a depth buffer work and what is depth struggle? How does this affect translucency and why is the order in which objects are rendered to the screen important? How do you calculate camera coordinates and set perspective? What are matrices for and how do you use them? What is clipping and why is it needed?

The screenshots in this tutorial are not from GMS2, ​​but from GMEdit for clarity. As I explain, I will add small notes about GMEdit to explain constructs that are not familiar to GMS2.

The first note concerns adding a project to GMEdit. To do this, I create a project in GMS2, ​​add one object and its instance in the room. After saving the project, I open it in GMEdit.

I want to display a cube on the screen. For clarity, I will first draw it on paper.

-2

About 3D graphics in GMS2. Part 1 of 2The figure shows that the cube has 8 vertices, designated by the letters A, B, C, D, E, F, G, H. I will describe the position of each vertex in the Create event.

-3

s — is half the length of the edge of the cube. Axes XYZ are located in the center of the cube and are directed to the right, up and forward, respectively. The coordinates of each point are described as an array with values [x, y, z]Pluses before numbers are added for ease of reading, they can be omitted.

A cube has six faces, each of which consists of two triangles. To draw a cube on the screen, you need to create a vertex buffer containing a description of all twelve triangles. I'll start by creating this buffer.

-4

First, we need to create a vertex format that will be used when creating the buffer. Lines 12–16 create the vertex format. This is the base format for GMS2I will not change it to make the guide easier to read. All vertex data should be added between calls vertex_format_begin() And vertex_format_end() . The function returns a reference to the format structure, which cannot be deleted as long as it is used in vertex buffers. After finishing working with the format, it must be deleted using the function vertex_format_delete() .

The composition of the created format in lines 13–15: The position of the vertex in space, described by three values ​​of the type float(x, y, z); Vertex texture coordinates (u, v)described by two values ​​of the type float ; Top color – uint .

The order in which data is added to the format is important – it must match the order in the shader for correct rendering on all target platforms.

Next, in line 18, I create a vertex buffer, and in line 19, I start adding vertices to it. In this function, you need to specify the vertex format created earlier. In line 21, I finish adding vertices, and in line 22, I transfer the buffer to video memory to speed up rendering. While the buffer is empty, vertices will be added further, between lines 19 and 21.

-5

I need to add all six faces of a cube to the vertex buffer. In this screenshot, the faces are added using the function plane_add . It receives a vertex buffer, four points describing the face, and the face color. For clarity, I will make the faces different colors. In lines 21–26, the faces are added in the following order: front (A, B, C, D), right (D, C, G, H), back (H, G, F, E), left (E, F, B, A), top (B, F, G, C), bottom (E, A, D, H). The vertices of each face are added in the order: bottom left, top left, top right, bottom right. You can check the cube diagram above.

-6

Lines 6–10 create the function vert_add with which I will add vertices to the buffer. In the seventh line, spatial coordinates are added, in the eighth – texture coordinates, and in the ninth – color. The data must be added in the same sequence as in the vertex format. The cube will be textureless, so the texture coordinates are always the same.

Please note that in the second line I added enum POS describing the coordinates of the vertex in space. In the sixth line, the type POS is used for the parameter containing the coordinates of the vertex. This allows values ​​to be written as vx, vy, vz rather than using array indices ( v[0] v[1] v[2] ), which improves the readability of the code. Such capabilities are provided by GMEdit.

In lines 12–17, using vert_add triangles are added that describe the face. The first triangle is v1, v2, v3; the second is v3, v4, v1. For example, for the front face, these are triangles ABC and CDA. It is important that the vertices of the triangles are arranged clockwise to optimize rendering. If you enable the clipping function, triangles drawn counterclockwise will not be drawn. For example, from the back of the cube, the faces will not be visible, since their triangles will be drawn counterclockwise.

I'll show you how lines 2-10 look in GMS2.

-7

Type POS in line 10 it is commented out, and in line 7 instead vx accessing an array via indices: v[POS.x] that is v[0] . The code will always be written like this, but GMEdit displays it more concisely.

Here are the current lines from the Create event:

-8

In line 36 I deleted var so that the variable vert_buffer was available in the Draw event.

Now with the help of vertex_submit(vert_buffer, pr_trianglelist, -1) in the Draw event I will draw the cube on the screen. Parameter pr_trianglelist indicates that triangles are drawn, and -1 means that there is no texture.

-9

On the screen, the cube is displayed in the upper left corner, which is logical, since its coordinates are set relative to the origin. To move the cube, you need to change the world matrix using the function matrix_set .

-10

Line 4 creates the matrix. The function is passed 9 parameters: translation, rotation, and scaling for the XYZ axes. The cube is tilted 45 degrees along the X axis for better visibility, rotated along the Y axis, and offset to coordinates 300, 300 on the screen. Line 5 sets the world matrix. This is passed to the vertex shader, where each vertex is multiplied by it to calculate new coordinates on the screen. Line 7 resets the world matrix to the identity matrix so that the rest of the graphics display correctly.

-11

The animation shows that the top and bottom edges are swapped, and the front and side edges disappear. This is due to the direction of the Y axis, which is directed downwards on the screen. The missing edges can be returned by enabling the depth buffer. This buffer stores the depth of each pixel, and if the depth of a new pixel is greater than the one already recorded, it will not be drawn. To do this, I add two functions to the Create event:

gpu_set_zwriteenable — enables writing to the depth buffer; gpu_set_ztestenable – enables comparison of pixel depth with data in the depth buffer.

-12

Now the cube is drawn correctly, and rotation occurs around its center.

This concludes the first part of the article. If you liked it, you can read the article about shaders.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *