In-Practice/2D-Game/Rendering-Sprites

To bring some life to lớn the currently black abyss of our game world, we will render sprites lớn fill the void. A sprite has many definitions, but it"s effectively not much more than a 2D image used together with some data to position it in a larger world (e.g. position, rotation, and size). Basically, sprites are the render-able image/texture objects we use in a 2 chiều game.

Bạn đang xem: Graphics card

We can, just like we did in previous chapters, create a 2D shape out of vertex data, pass all data to the GPU, and transkhung it all by hvà. However, in a larger application like this we rather have some abstractions on rendering 2D shapes. If we were to manually define these shapes và transformations for each object, it"ll quickly get messy.

In this chapter we"ll define a rendering class that allows us lớn render a large amount of chất lượng sprites with a minimal amount of code. This way, we"re abstracting the gameplay code from the gritty OpenGL rendering code as is commonly done in larger projects. First, we have lớn phối up a proper projection matrix though.

## 2D projection matrix

We know from the coordinate systems chapter that a projection matrix converts all view-space coordinates to clip-space (and then lớn normalized device) coordinates. By generating the appropriate projection matrix we can work with different coordinates that are easier to work with, compared to lớn directly specifying all coordinates as normalized device coordinates.

We don"t need any perspective applied to lớn the coordinates, since the game is entirely in 2D, so an orthographic projection matrix would suit the rendering quite well. Because an orthographic projection matrix directly transforms all coordinates khổng lồ normalized device coordinates, we can choose lớn specify the world coordinates as screen coordinates by defining the projection matrix as follows:

glm::mat4 projection = glm::ortho(0.0f, 800.0f, 600.0f, 0.0f, -1.0f, 1.0f); The first four arguments specify in order the left, right, bottom, and top part of the projection frustum. This projection matrix transforms all x coordinates between 0 và 800 lớn -1 và 1, & all y coordinates between 0 và 600 to -1 và 1. Here we specified that the top of the frustum has a y coordinate of 0, while the bottom has a y coordinate of 600. The result is that the top-left coordinate of the scene will be at (0,0) & the bottom-right part of the screen is at coordinate (800,600), just like screen coordinates; the world-space coordinates directly correspond to lớn the resulting pixel coordinates. This allows us to lớn specify all vertex coordinates equal lớn the px coordinates they over up in on the screen, which is rather intuitive sầu for 2 chiều games.

## Rendering sprites

Rendering an actual sprite shouldn"t be too complicated. We create a textured quad that we can transform with a model matrix, after which we project it using the previously defined orthographic projection matrix.

Since Breakout is a single-scene game, there is no need for a view/camera matrix. Using the projection matrix we can directly transkhung the world-space coordinates lớn normalized device coordinates.

To transkhung a sprite, we use the following vertex shader:

#version 330 corelayout (location = 0) in vec4 vertex; // out vec2 TexCoords;unisize mat4 model;unisize mat4 projection;void main() TexCoords = vertex.zw; gl_Position = projection * mã sản phẩm * vec4(vertex.xy, 0.0, 1.0); cảnh báo that we store both the position và texture-coordinate data in a single vec4 variable. Because both the position and texture coordinates contain two floats, we can combine them in a single vertex attribute.

The fragment shader is relatively straightforward as well. We take a texture & a color vector that both affect the final color of the fragment. By having a unikhung color vector, we can easily change the color of sprites from the game-code:

#version 330 corein vec2 TexCoords;out vec4 color;unikhung sampler2 chiều image;uniform vec3 spriteColor;void main() color = vec4(spriteColor, 1.0) * texture(image, TexCoords); To make the rendering of sprites more organized, we define a SpriteRenderer class that is able to render a sprite with just a single function. Its definition is as follows:

class SpriteRenderer public: SpriteRenderer(Shader &shader); ~SpriteRenderer(); void DrawSprite(Texture2 chiều &texture, glm::vec2 position, glm::vec2 kích thước = glm::vec2(10.0f, 10.0f), float rotate = 0.0f, glm::vec3 color = glm::vec3(1.0f)); private: Shader shader; unsigned int quadVAO; void initRenderData();; The SpriteRenderer class hosts a shader object, a single vertex array object, và a render and initialization function. Its constructor takes a shader object that it uses for all future rendering.

### Initialization

First, let"s delve sầu into lớn the initRenderData function that configures the quadVAO:

void SpriteRenderer::initRenderData() // configure VAO/VBO unsigned int VBO; float vertices<> = // pos // tex 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f ; glGenVertexArrays(1, &this->quadVAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindVertexArray(this->quadVAO); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); Here we first define a phối of vertices with (0,0) being the top-left corner of the quad. This means that when we apply translation or scaling transformations on the quad, they"re transformed from the top-left position of the quad. This is commonly accepted in 2 chiều graphics and/or GUI systems where elements" positions correspond lớn the top-left corner of the elements.

Next we simply sent the vertices khổng lồ the GPU và configure the vertex attributes, which in this case is a single vertex attribute. We only have sầu khổng lồ define a single VAO for the sprite renderer since all sprites tóm tắt the same vertex data.

### Rendering

Rendering sprites is not too difficult; we use the sprite renderer"s shader, configure a Model matrix, & mix the relevant uniforms. What is important here is the order of transformations:

void SpriteRenderer::DrawSprite(Texture2 chiều &texture, glm::vec2 position, glm::vec2 form size, float rotate, glm::vec3 color) // prepare transformations this->shader.Use(); glm::mat4 Mã Sản Phẩm = glm::mat4(1.0f); mã sản phẩm = glm::translate(model, glm::vec3(position, 0.0f)); Mã Sản Phẩm = glm::translate(mã sản phẩm, glm::vec3(0.5f * kích thước.x, 0.5f * size.y, 0.0f)); model = glm::rotate(model, glm::radians(rotate), glm::vec3(0.0f, 0.0f, 1.0f)); Mã Sản Phẩm = glm::translate(mã sản phẩm, glm::vec3(-0.5f * form size.x, -0.5f * kích cỡ.y, 0.0f)); mã sản phẩm = glm::scale(model, glm::vec3(kích cỡ, 1.0f)); this->shader.SetMatrix4("model", model); this->shader.SetVector3f("spriteColor", color); glActiveTexture(GL_TEXTURE0); texture.Bind(); glBindVertexArray(this->quadVAO); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); When trying khổng lồ position objects somewhere in a scene with rotation và scaling transformations, it is advised to lớn first scale, then rotate, and finally translate the object. Because multiplying matrices occurs from right khổng lồ left, we transkhung the matrix in reverse order: translate, rotate, and then scale.

The rotation transformation may still seem a bit daunting. We know from the transformations chapter that rotations always revolve around the origin (0,0). Because we specified the quad"s vertices with (0,0) as the top-left coordinate, all rotations will rotate around this point of (0,0). The origin of rotation is at the top-left of the quad, which produces undesirable results. What we want lớn bởi vì is move the origin of rotation lớn the center of the quad so the quad neatly rotates around this origin, instead of rotating around the top-left of the quad. We solve sầu this by translating the quad by half its size first, so its center is at coordinate (0,0) before rotating. Since we first scale the quad, we have lớn take the size of the sprite into account when translating to the sprite"s center, which is why we multiply with the sprite"s kích cỡ vector. Once the rotation transformation is applied, we reverse the previous translation.

Combining all these transformations, we can position, scale, và rotate each sprite in any way we lượt thích. Below you can find the complete source code of the sprite renderer:

## Hello sprite

With the SpriteRenderer class we finally have sầu the ability to lớn render actual images lớn the screen! Let"s initialize one within the game code và load our favorite texture while we"re at it: 