diff --git a/assets/gfx/skybox-nx.jpg b/assets/gfx/skybox-nx.jpg new file mode 100644 index 0000000..a8f6326 Binary files /dev/null and b/assets/gfx/skybox-nx.jpg differ diff --git a/assets/gfx/skybox-ny.jpg b/assets/gfx/skybox-ny.jpg new file mode 100644 index 0000000..208489f Binary files /dev/null and b/assets/gfx/skybox-ny.jpg differ diff --git a/assets/gfx/skybox-nz.jpg b/assets/gfx/skybox-nz.jpg new file mode 100644 index 0000000..6f13b48 Binary files /dev/null and b/assets/gfx/skybox-nz.jpg differ diff --git a/assets/gfx/skybox-px.jpg b/assets/gfx/skybox-px.jpg new file mode 100644 index 0000000..f9d6af2 Binary files /dev/null and b/assets/gfx/skybox-px.jpg differ diff --git a/assets/gfx/skybox-py.jpg b/assets/gfx/skybox-py.jpg new file mode 100644 index 0000000..66b4c1b Binary files /dev/null and b/assets/gfx/skybox-py.jpg differ diff --git a/assets/gfx/skybox-pz.jpg b/assets/gfx/skybox-pz.jpg new file mode 100644 index 0000000..5829f71 Binary files /dev/null and b/assets/gfx/skybox-pz.jpg differ diff --git a/assets/glsl/batch-cubemap.frag b/assets/glsl/batch-cubemap.frag new file mode 100644 index 0000000..157fdb9 --- /dev/null +++ b/assets/glsl/batch-cubemap.frag @@ -0,0 +1,49 @@ +#version 450 core + +layout(location = 0) out vec4 color; + +in vec4 v_color; +in vec3 v_textureCoordinates; +in flat float v_textureIndex; + +uniform samplerCube u_textures[32]; + +void main() +{ + vec4 textureColor = v_color; + switch(int(v_textureIndex)) { + case 0: break; // Texture unit 0 is reserved for no texture + case 1: textureColor *= texture(u_textures[1], v_textureCoordinates); break; + case 2: textureColor *= texture(u_textures[2], v_textureCoordinates); break; + case 3: textureColor *= texture(u_textures[3], v_textureCoordinates); break; + case 4: textureColor *= texture(u_textures[4], v_textureCoordinates); break; + case 5: textureColor *= texture(u_textures[5], v_textureCoordinates); break; + case 6: textureColor *= texture(u_textures[6], v_textureCoordinates); break; + case 7: textureColor *= texture(u_textures[7], v_textureCoordinates); break; + case 8: textureColor *= texture(u_textures[8], v_textureCoordinates); break; + case 9: textureColor *= texture(u_textures[9], v_textureCoordinates); break; + case 10: textureColor *= texture(u_textures[10], v_textureCoordinates); break; + case 11: textureColor *= texture(u_textures[11], v_textureCoordinates); break; + case 12: textureColor *= texture(u_textures[12], v_textureCoordinates); break; + case 13: textureColor *= texture(u_textures[13], v_textureCoordinates); break; + case 14: textureColor *= texture(u_textures[14], v_textureCoordinates); break; + case 15: textureColor *= texture(u_textures[15], v_textureCoordinates); break; + case 16: textureColor *= texture(u_textures[16], v_textureCoordinates); break; + case 17: textureColor *= texture(u_textures[17], v_textureCoordinates); break; + case 18: textureColor *= texture(u_textures[18], v_textureCoordinates); break; + case 19: textureColor *= texture(u_textures[19], v_textureCoordinates); break; + case 20: textureColor *= texture(u_textures[20], v_textureCoordinates); break; + case 21: textureColor *= texture(u_textures[21], v_textureCoordinates); break; + case 22: textureColor *= texture(u_textures[22], v_textureCoordinates); break; + case 23: textureColor *= texture(u_textures[23], v_textureCoordinates); break; + case 24: textureColor *= texture(u_textures[24], v_textureCoordinates); break; + case 25: textureColor *= texture(u_textures[25], v_textureCoordinates); break; + case 26: textureColor *= texture(u_textures[26], v_textureCoordinates); break; + case 27: textureColor *= texture(u_textures[27], v_textureCoordinates); break; + case 28: textureColor *= texture(u_textures[28], v_textureCoordinates); break; + case 29: textureColor *= texture(u_textures[29], v_textureCoordinates); break; + case 30: textureColor *= texture(u_textures[30], v_textureCoordinates); break; + case 31: textureColor *= texture(u_textures[31], v_textureCoordinates); break; + } + color = textureColor; +} diff --git a/assets/glsl/batch-cubemap.vert b/assets/glsl/batch-cubemap.vert new file mode 100644 index 0000000..81ba6ca --- /dev/null +++ b/assets/glsl/batch-cubemap.vert @@ -0,0 +1,20 @@ +#version 450 core + +layout(location = 0) in vec3 a_position; +layout(location = 1) in vec4 a_color; +layout(location = 2) in float a_textureIndex; + +out vec4 v_color; +out vec3 v_textureCoordinates; +out flat float v_textureIndex; + +uniform mat4 u_projectionView; + +void main() +{ + v_color = a_color; + v_textureCoordinates = a_position; + v_textureIndex = a_textureIndex; + // Vclip = Camera projection * Camera view * Model transform * Vlocal + gl_Position = u_projectionView * vec4(a_position, 1.0f); +} diff --git a/assets/scene/scene1.json b/assets/scene/scene1.json index 70e7871..6a57417 100644 --- a/assets/scene/scene1.json +++ b/assets/scene/scene1.json @@ -14,6 +14,19 @@ { "path": "assets/lua/cameracontroller.lua" } ] }, + { + "id": { "id": 212563732 }, + "tag": { "tag": "Skybox" }, + "transform" : { + "translate": [0.0, 0.0, 0.0], + "rotate": [0.0, 0.0, 0.0], + "scale": [100.0, 100.0, 100.0] + }, + "cubemap": { + "color": [ 1.0, 1.0, 1.0, 1.0 ], + "texture": "assets/gfx/skybox.jpg" + } + }, { "id": { "id": 564564564 }, "tag": { "tag": "Quad" }, diff --git a/src/inferno/application.cpp b/src/inferno/application.cpp index 511c23f..2496035 100644 --- a/src/inferno/application.cpp +++ b/src/inferno/application.cpp @@ -1,9 +1,11 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ +#include // std::pair + #include "glm/gtc/type_ptr.hpp" // glm::make_mat4 #include "ruc/format/log.h" #include "ruc/meta/assert.h" @@ -78,6 +80,7 @@ Application::~Application() FontManager::destroy(); RendererCharacter::destroy(); Renderer2D::destroy(); + RendererCubemap::destroy(); RenderCommand::destroy(); TextureManager::destroy(); ShaderManager::destroy(); @@ -164,12 +167,15 @@ int Application::run() RenderCommand::clearColor({ 0.2f, 0.3f, 0.3f, 1.0f }); RenderCommand::clear(); - Renderer2D::the().beginScene(m_scene->cameraProjectionView()); // camera, lights, environment + std::pair projectionView = m_scene->cameraProjectionView(); + RendererCubemap::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment + Renderer2D::the().beginScene(projectionView.first, projectionView.second); // camera, lights, environment RendererCharacter::the().beginScene(); m_scene->render(); // RendererCharacter::the().drawCharacter(character, f->texture()); + RendererCubemap::the().endScene(); Renderer2D::the().endScene(); RendererCharacter::the().endScene(); diff --git a/src/inferno/component/cubemap-component.cpp b/src/inferno/component/cubemap-component.cpp new file mode 100644 index 0000000..14fcbd4 --- /dev/null +++ b/src/inferno/component/cubemap-component.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#include "ruc/json/json.h" + +#include "inferno/component/cubemap-component.h" +#include "inferno/component/spritecomponent.h" // TODO: Move glm::x toJson/fromJson to separate file +#include "inferno/render/texture.h" + +namespace Inferno { + +void fromJson(const ruc::Json& json, CubemapComponent& value) +{ + VERIFY(json.type() == ruc::Json::Type::Object); + + if (json.exists("color")) { + json.at("color").getTo(value.color); + } + if (json.exists("texture") && json.at("texture").type() == ruc::Json::Type::String) { + value.texture = TextureManager::the().load(json.at("texture").asString(), Texture::Type::Cubemap); + } +} + +} // namespace Inferno diff --git a/src/inferno/component/cubemap-component.h b/src/inferno/component/cubemap-component.h new file mode 100644 index 0000000..4c68c6e --- /dev/null +++ b/src/inferno/component/cubemap-component.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 Riyyi + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include // std::shared_ptr + +#include "glm/ext/vector_float4.hpp" // glm::vec4 +#include "ruc/json/json.h" + +#include "inferno/render/texture.h" + +namespace Inferno { + +struct CubemapComponent { + glm::vec4 color { 1.0f }; + std::shared_ptr texture; +}; + +void fromJson(const ruc::Json& json, CubemapComponent& value); + +} // namespace Inferno diff --git a/src/inferno/render/font.cpp b/src/inferno/render/font.cpp index 921c00d..daea19e 100644 --- a/src/inferno/render/font.cpp +++ b/src/inferno/render/font.cpp @@ -32,7 +32,7 @@ Font::Font(const std::string& name) std::string font = ruc::File(path).data(); parseFont(font); - m_texture = std::make_shared(image); + m_texture = Texture2D::create(image); } // TODO: Move this to ruc diff --git a/src/inferno/render/renderer.cpp b/src/inferno/render/renderer.cpp index 9af41fd..908f68d 100644 --- a/src/inferno/render/renderer.cpp +++ b/src/inferno/render/renderer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -216,10 +216,10 @@ Renderer2D::~Renderer2D() Renderer::destroy(); } -void Renderer2D::beginScene(glm::mat4 cameraProjectionView) +void Renderer2D::beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView) { m_shader->bind(); - m_shader->setFloat("u_projectionView", cameraProjectionView); + m_shader->setFloat("u_projectionView", cameraProjection * cameraView); m_shader->unbind(); } @@ -311,6 +311,184 @@ void Renderer2D::nextBatch() // ----------------------------------------- +RendererCubemap::RendererCubemap(s) +{ + Renderer::initialize(); + + // CPU + // --------------------------------- + + // Create array for storing quads vertices + m_vertexBufferBase = std::make_unique(vertexCount); + m_vertexBufferPtr = m_vertexBufferBase.get(); + + // Set default cubemap vertex positions + + // Back face - v + m_vertexPositions[0] = { 0.5f, -0.5f, 0.5f, 1.0f }; + m_vertexPositions[1] = { -0.5f, -0.5f, 0.5f, 1.0f }; + m_vertexPositions[2] = { -0.5f, 0.5f, 0.5f, 1.0f }; + m_vertexPositions[3] = { 0.5f, 0.5f, 0.5f, 1.0f }; + + // Left face - v + m_vertexPositions[7] = { -0.5f, 0.5f, 0.5f, 1.0f }; + m_vertexPositions[6] = { -0.5f, 0.5f, -0.5f, 1.0f }; + m_vertexPositions[5] = { -0.5f, -0.5f, -0.5f, 1.0f }; + m_vertexPositions[4] = { -0.5f, -0.5f, 0.5f, 1.0f }; + + // Right face - v + m_vertexPositions[8] = { 0.5f, -0.5f, -0.5f, 1.0f }; + m_vertexPositions[9] = { 0.5f, -0.5f, 0.5f, 1.0f }; + m_vertexPositions[10] = { 0.5f, 0.5f, 0.5f, 1.0f }; + m_vertexPositions[11] = { 0.5f, 0.5f, -0.5f, 1.0f }; + + // Front face - v + m_vertexPositions[12] = { -0.5f, -0.5f, -0.5f, 1.0f }; + m_vertexPositions[13] = { 0.5f, -0.5f, -0.5f, 1.0f }; + m_vertexPositions[14] = { 0.5f, 0.5f, -0.5f, 1.0f }; + m_vertexPositions[15] = { -0.5f, 0.5f, -0.5f, 1.0f }; + + // Top face + m_vertexPositions[16] = { -0.5f, 0.5f, -0.5f, 1.0f }; + m_vertexPositions[17] = { 0.5f, 0.5f, -0.5f, 1.0f }; + m_vertexPositions[18] = { 0.5f, 0.5f, 0.5f, 1.0f }; + m_vertexPositions[19] = { -0.5f, 0.5f, 0.5f, 1.0f }; + + // Bottom face + m_vertexPositions[20] = { -0.5f, -0.5f, -0.5f, 1.0f }; + m_vertexPositions[21] = { -0.5f, -0.5f, 0.5f, 1.0f }; + m_vertexPositions[22] = { 0.5f, -0.5f, 0.5f, 1.0f }; + m_vertexPositions[23] = { 0.5f, -0.5f, -0.5f, 1.0f }; + + // Generate indices + + uint32_t* indices = new uint32_t[indexCount]; + + uint32_t offset = 0; + for (uint32_t i = 0; i < indexCount; i += indexPerQuad) { + indices[i + 0] = offset + 0; + indices[i + 1] = offset + 1; + indices[i + 2] = offset + 2; + indices[i + 3] = offset + 2; + indices[i + 4] = offset + 3; + indices[i + 5] = offset + 0; + + offset += vertexPerQuad; + } + + // GPU + // --------------------------------- + + // Create vertex buffer + auto vertexBuffer = std::make_shared(sizeof(CubemapVertex) * vertexCount); + vertexBuffer->setLayout({ + { BufferElementType::Vec3, "a_position" }, + { BufferElementType::Vec4, "a_color" }, + { BufferElementType::Float, "a_textureIndex" }, + }); + m_vertexArray->addVertexBuffer(vertexBuffer); + + // Create index buffer + auto indexBuffer = std::make_shared(indices, sizeof(uint32_t) * indexCount); + m_vertexArray->setIndexBuffer(indexBuffer); + delete[] indices; + + ruc::info("RendererCubemap initialized"); +} + +RendererCubemap::~RendererCubemap() +{ + Renderer::destroy(); +} + +void RendererCubemap::beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView) +{ + // We want the skybox fixed in position, so only retain the rotation and scale. + // Set the translation of the camera's view matrix to 0, meaning: + // x x x 0 + // x x x 0 + // x x x 0 + // 0 0 0 1 + cameraView = glm::mat4(glm::mat3(cameraView)); + + m_shader->bind(); + m_shader->setFloat("u_projectionView", cameraProjection * cameraView); + m_shader->unbind(); +} + +void RendererCubemap::endScene() +{ + nextBatch(); +} + +void RendererCubemap::drawCubemap(const TransformComponent& transform, glm::vec4 color, std::shared_ptr texture) +{ + drawCubemap(transform, glm::mat4(color, color, color, color), texture); +} + +void RendererCubemap::drawCubemap(const TransformComponent& transform, glm::mat4 color, std::shared_ptr texture) +{ + // Create a new batch if the quad limit has been reached + if (m_quadIndex >= quadCount) { + nextBatch(); + } + + uint32_t textureUnitIndex = addTextureUnit(texture); + + // Add the quads 4 vertices + for (uint32_t i = 0; i < vertexPerQuad * quadPerCube; i++) { + m_vertexBufferPtr->position = transform.transform * m_vertexPositions[i]; + m_vertexBufferPtr->color = color[i % 4]; + m_vertexBufferPtr->textureIndex = (float)textureUnitIndex; + m_vertexBufferPtr++; + } + + m_quadIndex += quadPerCube; +} + +void RendererCubemap::loadShader() +{ + m_shader = ShaderManager::the().load("assets/glsl/batch-cubemap"); +} + +void RendererCubemap::flush() +{ + if (m_quadIndex == 0) { + return; + } + + // Upload vertex data to GPU + m_vertexArray->getVertexBuffers().at(0)->uploadData( + m_vertexBufferBase.get(), + m_quadIndex * vertexPerQuad * sizeof(CubemapVertex)); + + bind(); + + // Render + bool depthTest = RenderCommand::depthTest(); + RenderCommand::setDepthTest(false); + RenderCommand::drawIndexed(*m_vertexArray, m_quadIndex * indexPerQuad); + RenderCommand::setDepthTest(depthTest); + + unbind(); +} + +void RendererCubemap::startBatch() +{ + m_quadIndex = 0; + m_vertexBufferPtr = m_vertexBufferBase.get(); + + m_textureUnitIndex = 1; +} + +void RendererCubemap::nextBatch() +{ + flush(); + startBatch(); +} + +// ----------------------------------------- + RendererCharacter::RendererCharacter(s) { Renderer::initialize(); diff --git a/src/inferno/render/renderer.h b/src/inferno/render/renderer.h index a63ab57..77ea0eb 100644 --- a/src/inferno/render/renderer.h +++ b/src/inferno/render/renderer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -27,7 +27,13 @@ struct QuadVertex { glm::vec3 position { 0.0f, 0.0f, 0.0f }; glm::vec4 color { 1.0f, 1.0f, 1.0f, 1.0f }; glm::vec2 textureCoordinates { 0.0f, 0.0f }; - float textureIndex = 0; // @Todo get int to pass to fragment correctly + float textureIndex = 0; // TODO: get int to pass to fragment correctly +}; + +struct CubemapVertex { + glm::vec3 position { 0.0f, 0.0f, 0.0f }; + glm::vec4 color { 1.0f, 1.0f, 1.0f, 1.0f }; + float textureIndex = 0; // TODO: get int to pass to fragment correctly }; struct CharacterVertex { @@ -66,9 +72,10 @@ public: class Renderer { public: - static const uint32_t vertexPerQuad = 4; - static const uint32_t indexPerQuad = 6; - static const uint32_t textureUnitPerBatch = 32; + static constexpr const uint32_t vertexPerQuad = 4; + static constexpr const uint32_t indexPerQuad = 6; + static constexpr const uint32_t quadPerCube = 6; + static constexpr const uint32_t textureUnitPerBatch = 32; protected: Renderer() {} @@ -108,11 +115,12 @@ public: using Singleton::destroy; - static const uint32_t quadCount = 1000; - static const uint32_t vertexCount = quadCount * vertexPerQuad; - static const uint32_t indexCount = quadCount * indexPerQuad; + // When to start a new batch + static constexpr const uint32_t quadCount = 1000; + static constexpr const uint32_t vertexCount = quadCount * vertexPerQuad; + static constexpr const uint32_t indexCount = quadCount * indexPerQuad; - void beginScene(glm::mat4 cameraProjectionView); + void beginScene(glm::mat4 cameraProjectionView, glm::mat4 cameraView); void endScene(); void drawQuad(const TransformComponent& transform, glm::vec4 color); @@ -133,6 +141,42 @@ private: // Default quad vertex positions glm::vec4 m_vertexPositions[vertexPerQuad]; }; +// ------------------------------------- + +class RendererCubemap final + : public Renderer + , public ruc::Singleton { +public: + RendererCubemap(s); + virtual ~RendererCubemap(); + + using Singleton::destroy; + + // When to start a new batch + static constexpr const uint32_t cubemapCount = 166; + static constexpr const uint32_t quadCount = cubemapCount * quadPerCube; + static constexpr const uint32_t vertexCount = quadCount * vertexPerQuad; + static constexpr const uint32_t indexCount = quadCount * indexPerQuad; + + void beginScene(glm::mat4 cameraProjectionView, glm::mat4 cameraView); + void endScene(); + + void drawCubemap(const TransformComponent& transform, glm::vec4 color, std::shared_ptr texture); + void drawCubemap(const TransformComponent& transform, glm::mat4 color, std::shared_ptr texture); + +private: + void loadShader() override; + void flush() override; + void startBatch() override; + void nextBatch() override; + + // CPU quad vertices + std::unique_ptr m_vertexBufferBase; + CubemapVertex* m_vertexBufferPtr { nullptr }; + + // Default cubemap vertex positions + glm::vec4 m_vertexPositions[vertexPerQuad * quadPerCube]; +}; // ------------------------------------- diff --git a/src/inferno/render/texture.cpp b/src/inferno/render/texture.cpp index d2aa0d2..861fdc7 100644 --- a/src/inferno/render/texture.cpp +++ b/src/inferno/render/texture.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -19,36 +19,47 @@ namespace Inferno { -Texture::Texture(const std::string& path) - : m_path(std::move(path)) +Texture::~Texture() { - unsigned char* data = nullptr; + glDeleteTextures(1, &m_id); +} + +void Texture::init(uint32_t width, uint32_t height, uint8_t channels) +{ + m_width = width; + m_height = height; + + m_internalFormat = (channels == 3) ? GL_RGB8 : GL_RGBA8; + m_dataFormat = (channels == 3) ? GL_RGB : GL_RGBA; +} + +// ----------------------------------------- + +std::shared_ptr Texture2D::create(const std::string& path) +{ + auto result = std::shared_ptr(new Texture2D); + result->m_path = path; + int width = 0; int height = 0; int channels = 0; + unsigned char* data = nullptr; // Load image data stbi_set_flip_vertically_on_load(1); data = stbi_load(path.c_str(), &width, &height, &channels, STBI_default); VERIFY(data, "failed to load image: '{}'", path); - init(data, width, height, channels); + result->init(width, height, channels); + result->create(data); // Clean resources stbi_image_free(data); -} -Texture::Texture(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels) -{ - init(data, width, height, channels); -} - -Texture::~Texture() -{ - glDeleteTextures(1, &m_id); + return result; } -void Texture::bind(uint32_t unit) const +void Texture2D::bind(uint32_t unit) const { // Set active unit glActiveTexture(GL_TEXTURE0 + unit); @@ -59,31 +70,12 @@ void Texture::bind(uint32_t unit) const glActiveTexture(GL_TEXTURE0); } -void Texture::unbind() const +void Texture2D::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); } -// ----------------------------------------- - -void Texture::init(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels) -{ - m_width = width; - m_height = height; - - if (channels == 4) { - m_internalFormat = GL_RGBA8; - m_dataFormat = GL_RGBA; - } - else if (channels == 3) { - m_internalFormat = GL_RGB8; - m_dataFormat = GL_RGB; - } - - create(data); -} - -void Texture::create(unsigned char* data) +void Texture2D::create(unsigned char* data) { m_id = UINT_MAX; @@ -109,10 +101,10 @@ void Texture::create(unsigned char* data) data); // Image data // Set the texture wrapping / filtering options + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Magnify + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Minify glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // X glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Y - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Minify - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Magnify // Automatically generate all mipmap levels glGenerateMipmap(GL_TEXTURE_2D); @@ -123,6 +115,94 @@ void Texture::create(unsigned char* data) // ----------------------------------------- +std::shared_ptr TextureCubemap::create(const std::string& path) +{ + auto result = std::shared_ptr(new TextureCubemap); + result->m_path = path; + + result->create(); + + return result; +} + +void TextureCubemap::bind(uint32_t unit) const +{ + // Set active unit + glActiveTexture(GL_TEXTURE0 + unit); + + glBindTexture(GL_TEXTURE_CUBE_MAP, m_id); + + // Reset unit + glActiveTexture(GL_TEXTURE0); +} + +void TextureCubemap::unbind() const +{ + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +} + +void TextureCubemap::create() +{ + m_id = UINT_MAX; + + // Create texture object + glGenTextures(1, &m_id); + + // Bind texture object + glBindTexture(GL_TEXTURE_CUBE_MAP, m_id); + + // Set unpacking of pixel data to byte-alignment, + // this prevents alignment issues when using a single byte for color + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + stbi_set_flip_vertically_on_load(0); + + static constexpr const char* cubemapPaths[6] { "-px", "-nx", "-py", "-ny", "-pz", "-nz" }; + size_t dotIndex = m_path.find_last_of('.'); + std::string path = m_path.substr(0, dotIndex); + std::string extension = m_path.substr(dotIndex); + + int width = 0; + int height = 0; + int channels = 0; + unsigned char* data = nullptr; + for (size_t i = 0; i < 6; ++i) { + std::string facePath = path + cubemapPaths[i] + extension; + + // Load image data + data = stbi_load(facePath.c_str(), &width, &height, &channels, STBI_default); + VERIFY(data, "failed to load image: '{}'", facePath.c_str()); + + init(width, height, channels); + + // Generate texture face + glTexImage2D( + GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, // Texture target + 0, // Midmap level, base starts at level 0 + m_internalFormat, // Internal format + m_width, m_height, // Image width/height + 0, // Always 0 (legacy) + m_dataFormat, // Texture source format + GL_UNSIGNED_BYTE, // Texture source datatype + data); // Image data + + // Clean resources + stbi_image_free(data); + } + + // Set the texture wrapping / filtering options + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Magnify + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Minify + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // X + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Y + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Z + + // Unbind texture object + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +} + +// ----------------------------------------- + TextureManager::TextureManager(s) { ruc::info("TextureManager initialized"); @@ -138,15 +218,16 @@ void TextureManager::add(const std::string& path, std::shared_ptr textu m_textureList.emplace(std::move(path), std::move(texture)); } -std::shared_ptr TextureManager::load(const std::string& path) +std::shared_ptr TextureManager::load(const std::string& path, Texture::Type type) { if (exists(path)) { return get(path); } - std::shared_ptr texture = std::make_shared(path); + auto texture = (type == Texture::TwoDimensional) ? Texture2D::create(path) : TextureCubemap::create(path); add(path, texture); - return get(path); + + return texture; } std::shared_ptr TextureManager::get(const std::string& path) diff --git a/src/inferno/render/texture.h b/src/inferno/render/texture.h index a75f379..0185cdb 100644 --- a/src/inferno/render/texture.h +++ b/src/inferno/render/texture.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -15,33 +15,88 @@ namespace Inferno { +class Texture2D; +class TextureCubemap; + class Texture { public: - Texture(const std::string& path); - Texture(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels = 3); virtual ~Texture(); - void bind(uint32_t unit = 0) const; - void unbind() const; + enum Type : uint8_t { + TwoDimensional = 0, + Cubemap, + }; + + void init(uint32_t width, uint32_t height, uint8_t channels); + + virtual void bind(uint32_t unit = 0) const = 0; + virtual void unbind() const = 0; + + std::string path() const { return m_path; } + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + uint32_t id() const { return m_id; } + uint32_t internalFormat() const { return m_internalFormat; } + uint32_t dataFormat() const { return m_dataFormat; } - inline std::string path() const { return m_path; } - inline uint32_t width() const { return m_width; } - inline uint32_t height() const { return m_height; } - inline uint32_t id() const { return m_id; } - inline uint32_t internalFormat() const { return m_internalFormat; } - inline uint32_t dataFormat() const { return m_dataFormat; } + virtual bool is2D() const { return false; } + virtual bool isCubeMap() const { return false; } + + friend Texture2D; + friend TextureCubemap; + +protected: + Texture() {} protected: - void init(unsigned char* data, uint32_t width, uint32_t height, uint8_t channels); + std::string m_path; + uint32_t m_width { 0 }; + uint32_t m_height { 0 }; + uint32_t m_id { 0 }; + uint32_t m_internalFormat { 0 }; + uint32_t m_dataFormat { 0 }; +}; + +// ------------------------------------- + +class Texture2D final : public Texture { +public: + virtual ~Texture2D() = default; + + // Factory function + static std::shared_ptr create(const std::string& path); + + virtual void bind(uint32_t unit = 0) const override; + virtual void unbind() const override; + +private: + Texture2D() {} + + virtual bool is2D() const override { return true; } + +private: void create(unsigned char* data); +}; + +// ------------------------------------- + +class TextureCubemap final : public Texture { +public: + virtual ~TextureCubemap() = default; + + // Factory function + static std::shared_ptr create(const std::string& path); + + virtual void bind(uint32_t unit = 0) const override; + virtual void unbind() const override; private: - std::string m_path; - uint32_t m_width; - uint32_t m_height; - uint32_t m_id; - uint32_t m_internalFormat; - uint32_t m_dataFormat; + TextureCubemap() {}; + + virtual bool isCubeMap() const override { return true; } + +private: + void create(); }; // ------------------------------------- @@ -52,7 +107,7 @@ public: ~TextureManager(); void add(const std::string& path, std::shared_ptr texture); - std::shared_ptr load(const std::string& path); + std::shared_ptr load(const std::string& path, Texture::Type type = Texture::Type::TwoDimensional); std::shared_ptr get(const std::string& path); bool exists(const std::string& path); diff --git a/src/inferno/scene/scene.cpp b/src/inferno/scene/scene.cpp index d408988..96f20b9 100644 --- a/src/inferno/scene/scene.cpp +++ b/src/inferno/scene/scene.cpp @@ -7,6 +7,7 @@ #include // size_t #include // uint32_t #include // std::numeric_limits +#include // std::pair #include "entt/entity/entity.hpp" // ent::entity #include "ruc/file.h" @@ -15,6 +16,7 @@ #include "ruc/meta/assert.h" #include "inferno/component/cameracomponent.h" +#include "inferno/component/cubemap-component.h" #include "inferno/component/id-component.h" #include "inferno/component/luascriptcomponent.h" #include "inferno/component/nativescriptcomponent.h" @@ -156,6 +158,10 @@ uint32_t Scene::loadEntity(ruc::Json components, uint32_t parentEntity) auto& sprite = addComponent(entity); components.at("sprite").getTo(sprite); } + if (components.exists("cubemap")) { + auto& cubemap = addComponent(entity); + components.at("cubemap").getTo(cubemap); + } if (components.exists("text")) { auto& text = addComponent(entity); components.at("text").getTo(text); @@ -192,7 +198,7 @@ void Scene::destroyEntity(uint32_t entity) // ----------------------------------------- -glm::mat4 Scene::cameraProjectionView() +std::pair Scene::cameraProjectionView() { return CameraSystem::the().projectionView(); } diff --git a/src/inferno/scene/scene.h b/src/inferno/scene/scene.h index 3bf5590..cd26e09 100644 --- a/src/inferno/scene/scene.h +++ b/src/inferno/scene/scene.h @@ -9,6 +9,7 @@ #include // size_t #include // uint32_t #include // std::shared_ptr +#include // std::pair #include "entt/entity/registry.hpp" // entt::entity, entt::registry #include "glm/ext/matrix_float4x4.hpp" // glm::mat4 @@ -35,7 +36,10 @@ public: uint32_t findEntity(std::string_view name); void destroyEntity(uint32_t entity); - glm::mat4 cameraProjectionView(); + /** + * @brief Return a pair from the camera component: { projection, view } + */ + std::pair cameraProjectionView(); void validEntity(uint32_t entity) const; diff --git a/src/inferno/system/camerasystem.cpp b/src/inferno/system/camerasystem.cpp index 049b58d..a7fb14c 100644 --- a/src/inferno/system/camerasystem.cpp +++ b/src/inferno/system/camerasystem.cpp @@ -1,9 +1,11 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ +#include // std::pair + #include "glm/ext/matrix_clip_space.hpp" // glm::perspective, glm::ortho #include "glm/ext/matrix_transform.hpp" // glm::radians, glm::lookAt #include "ruc/format/log.h" @@ -42,17 +44,17 @@ void CameraSystem::update() } } -glm::mat4 CameraSystem::projectionView() +std::pair CameraSystem::projectionView() { auto view = m_registry->view(); for (auto [entity, transform, camera] : view.each()) { - return camera.projection * transform.transform; + return { camera.projection, transform.transform }; } VERIFY_NOT_REACHED(); - return glm::mat4 { 1.0f }; + return {}; } void CameraSystem::updateOrthographic(TransformComponent& transform, CameraComponent& camera) diff --git a/src/inferno/system/camerasystem.h b/src/inferno/system/camerasystem.h index d08c0d6..466a85b 100644 --- a/src/inferno/system/camerasystem.h +++ b/src/inferno/system/camerasystem.h @@ -1,12 +1,13 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ #pragma once -#include //std::shared_ptr +#include //std::shared_ptr +#include // std::pair #include "entt/entity/registry.hpp" // entt::entity, entt::registry #include "glm/ext/matrix_float4x4.hpp" // glm::mat4 @@ -27,7 +28,10 @@ public: void update(); - glm::mat4 projectionView(); + /** + * @brief Return a pair from the camera component: { projection, view } + */ + std::pair projectionView(); void setRegistry(std::shared_ptr registry) { m_registry = registry; }; diff --git a/src/inferno/system/rendersystem.cpp b/src/inferno/system/rendersystem.cpp index 3d33c84..4cdc995 100644 --- a/src/inferno/system/rendersystem.cpp +++ b/src/inferno/system/rendersystem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Riyyi + * Copyright (C) 2022,2024 Riyyi * * SPDX-License-Identifier: MIT */ @@ -7,6 +7,7 @@ #include "glm/ext/matrix_transform.hpp" // glm::translate, glm::rotate, glm::scale, glm::radians #include "ruc/format/log.h" +#include "inferno/component/cubemap-component.h" #include "inferno/component/spritecomponent.h" #include "inferno/component/transformcomponent.h" #include "inferno/render/renderer.h" @@ -25,11 +26,17 @@ RenderSystem::~RenderSystem() void RenderSystem::render() { - auto group = m_registry->group(); + auto quadView = m_registry->view(); - for (auto [entity, transform, sprite] : group.each()) { + for (auto [entity, transform, sprite] : quadView.each()) { Renderer2D::the().drawQuad(transform, sprite.color, sprite.texture); } + + auto cubemapView = m_registry->view(); + + for (auto [entity, transform, cubemap] : cubemapView.each()) { + RendererCubemap::the().drawCubemap(transform, cubemap.color, cubemap.texture); + } } } // namespace Inferno