From 6b60c55af7fd601e18cc95ea83c81a17c1970a0a Mon Sep 17 00:00:00 2001 From: s473577 Date: Mon, 22 Jan 2024 00:43:08 +0100 Subject: [PATCH] Add sprite renderer and sprite shaders --- grk/project/SpriteRenderer.cpp | 96 ++++++++++++++++++++++++ grk/project/SpriteRenderer.h | 27 +++++++ grk/project/grk-project.vcxproj | 5 ++ grk/project/grk-project.vcxproj.filters | 17 +++++ grk/project/shaders/shader_sprite.frag | 16 ++++ grk/project/shaders/shader_sprite.vert | 21 ++++++ grk/project/textures/blinky1.png | Bin 0 -> 13644 bytes 7 files changed, 182 insertions(+) create mode 100644 grk/project/SpriteRenderer.cpp create mode 100644 grk/project/SpriteRenderer.h create mode 100644 grk/project/shaders/shader_sprite.frag create mode 100644 grk/project/shaders/shader_sprite.vert create mode 100644 grk/project/textures/blinky1.png diff --git a/grk/project/SpriteRenderer.cpp b/grk/project/SpriteRenderer.cpp new file mode 100644 index 0000000..b91d26e --- /dev/null +++ b/grk/project/SpriteRenderer.cpp @@ -0,0 +1,96 @@ +#include "SpriteRenderer.h" + +Core::SpriteRenderer::SpriteRenderer() +{ + this->initRenderData(); +} + +Core::SpriteRenderer::~SpriteRenderer() +{ + glDeleteVertexArrays(1, &this ->VAO); + glDeleteBuffers(1, &this->VBO); + glDeleteBuffers(1, &this->EBO); +} +void Core::SpriteRenderer::DrawSprite(GLuint spriteTexture, const glm::mat4 modelMatrix, GLuint program) { + + Spaceship* spaceship = Spaceship::getInstance(); + + // Pobierz pozycję kamery + glm::vec3 cameraPosition = spaceship->cameraPos; + + // Twórz macierz lookAt między kwadratem a kamerą + glm::mat4 billboardModelMatrix = glm::lookAt(glm::vec3(modelMatrix[3]), cameraPosition, glm::vec3(0.0f, 1.0f, 0.0f)); + + // Reset the rotation part of the matrix to make it face the camera + //billboardModelMatrix[0][0] = 1.0f; + //billboardModelMatrix[0][1] = 0.0f; + //billboardModelMatrix[0][2] = 0.0f; + + billboardModelMatrix[1][0] = 0.0f; + billboardModelMatrix[1][1] = 1.0f; + billboardModelMatrix[1][2] = 0.0f; + + billboardModelMatrix[2][0] = 0.0f; + billboardModelMatrix[2][1] = 0.0f; + billboardModelMatrix[2][2] = 1.0f; + + // Utwórz macierz widoku-projekcji + glm::mat4 viewProjectionMatrix = Core::createPerspectiveMatrix() * spaceship->createCameraMatrix(); + + // Kombinuj macierze transformacji + glm::mat4 transformation = viewProjectionMatrix * billboardModelMatrix * modelMatrix; + + // Przekaż macierze do shadera + glUniformMatrix4fv(glGetUniformLocation(program, "transformation"), 1, GL_FALSE, glm::value_ptr(transformation)); + glUniformMatrix4fv(glGetUniformLocation(program, "modelMatrix"), 1, GL_FALSE, glm::value_ptr(billboardModelMatrix)); + Core::SetActiveTexture(spriteTexture, "colorTexture", program, 0); + + glBindVertexArray(this->VAO); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); +} + +void Core::SpriteRenderer::initRenderData() +{ + // Definicja wierzchołków kwadratu + float vertices[] = { + // Postions //colors // texture coordiantes + -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // Left down + -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,1.0f, // Left up + 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,// Right up + 0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,0.0f// Right down + }; + + // Definicja indeksów wierzchołków dla kwadratu + unsigned int indices[] = { + 0, 2, 1, // Upper triangle + 0, 3, 2 // Lower triangle + }; + + glGenVertexArrays(1, &this->VAO); + glGenBuffers(1, &this->VBO); + glGenBuffers(1, &this->EBO); + + glBindVertexArray(this->VAO); + + // Wypełnij bufor wierzchołków + glBindBuffer(GL_ARRAY_BUFFER, this->VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Wypełnij bufor indeksów + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); + + glEnableVertexAttribArray(1); + // Konfiguracja atrybutu koordynatow kolorow + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); + + glEnableVertexAttribArray(2); + // Konfiguracja atrybutu koordynatow tekstury + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); + + glBindVertexArray(0); +} \ No newline at end of file diff --git a/grk/project/SpriteRenderer.h b/grk/project/SpriteRenderer.h new file mode 100644 index 0000000..e83640a --- /dev/null +++ b/grk/project/SpriteRenderer.h @@ -0,0 +1,27 @@ +#include "src/Render_Utils.h" +#include "src/Texture.h" +#include "Spaceship.h" +#include "./Enemy.h" + +#pragma once +namespace Core { + class SpriteRenderer + { + public: + SpriteRenderer(); + ~SpriteRenderer(); + void DrawSprite(GLuint spriteTexture, const glm::mat4 modelMatrix, GLuint program); + + private: + + unsigned int VAO; + unsigned int VBO; + unsigned int EBO; + // Initializes and configures the quad's buffer and vertex attributes + void initRenderData(); + + + + }; +} + diff --git a/grk/project/grk-project.vcxproj b/grk/project/grk-project.vcxproj index 16a703f..abb491b 100644 --- a/grk/project/grk-project.vcxproj +++ b/grk/project/grk-project.vcxproj @@ -13,6 +13,7 @@ + @@ -26,10 +27,12 @@ + + @@ -53,6 +56,8 @@ + + diff --git a/grk/project/grk-project.vcxproj.filters b/grk/project/grk-project.vcxproj.filters index c8486a9..8109db6 100644 --- a/grk/project/grk-project.vcxproj.filters +++ b/grk/project/grk-project.vcxproj.filters @@ -57,6 +57,9 @@ Source Files + + Source Files + @@ -113,6 +116,12 @@ Header Files + + Header Files + + + Header Files + @@ -139,5 +148,13 @@ Shader Files + + + + Shader Files + + + Shader Files + \ No newline at end of file diff --git a/grk/project/shaders/shader_sprite.frag b/grk/project/shaders/shader_sprite.frag new file mode 100644 index 0000000..8cf8107 --- /dev/null +++ b/grk/project/shaders/shader_sprite.frag @@ -0,0 +1,16 @@ +#version 430 core + +uniform sampler2D colorTexture; + +in vec3 worldPos; +in vec2 texCoord; +in vec3 color; + +out vec4 outColor; +void main() +{ + + outColor = texture(colorTexture, texCoord); + outColor *= vec4(color, 1.0); + +} \ No newline at end of file diff --git a/grk/project/shaders/shader_sprite.vert b/grk/project/shaders/shader_sprite.vert new file mode 100644 index 0000000..bb00932 --- /dev/null +++ b/grk/project/shaders/shader_sprite.vert @@ -0,0 +1,21 @@ +#version 430 core + +layout(location = 0) in vec3 vertexPosition; +layout(location = 1) in vec3 aColor; +layout(location = 2) in vec2 vertexTexCoord; + +uniform mat4 transformation; +uniform mat4 modelMatrix; + +out vec3 worldPos; +out vec2 texCoord; +out vec3 color; + +void main() +{ + worldPos = (modelMatrix* vec4(vertexPosition,1)).xyz; + gl_Position = transformation * vec4(worldPos, 1.0); + texCoord = vertexTexCoord; + texCoord.y = 1.0 - texCoord.y; // so that it is turned correctly + color = aColor; +} \ No newline at end of file diff --git a/grk/project/textures/blinky1.png b/grk/project/textures/blinky1.png new file mode 100644 index 0000000000000000000000000000000000000000..83ca8e6a299c85670770dc8b6fecc235545780bb GIT binary patch literal 13644 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