diff --git a/TSE b/TSE index 55dce57..45501f1 160000 --- a/TSE +++ b/TSE @@ -1 +1 @@ -Subproject commit 55dce5776a6a076bfe7b3e4764f9693e1ec56ab89520cc7321027b9a1eb0774d +Subproject commit 45501f153de222598515fef62e04078a96a86a50a6e7bf670050bfd71fd3f12d diff --git a/TSE-RTS/src/game.cpp b/TSE-RTS/src/game.cpp index 1f537ca..df401f2 100644 --- a/TSE-RTS/src/game.cpp +++ b/TSE-RTS/src/game.cpp @@ -3,30 +3,80 @@ #include "BehaviourScripts/TileMap.hpp" #include "BehaviourScripts/Renderable.hpp" #include "elements/ShaderRegistry.hpp" +#include "Vector2.hpp" +#include +#include "PerlinNoise.hpp" + +#define circleRadius 32 +#define circleFallof 25 void game::setup(TSE::Scene* s) { using namespace TSE; s->AddLayer(&gameLayer); - Transformable* tileMap = new Transformable("tileMap"); - gameLayer.AddTransformable(tileMap); - Texture* setTexture = new Texture("tiles.png"); - TileSet* set = new TileSet(setTexture, 10, 10); - TileMap* map = new TileMap(); - map->set = set; - - for (int x = -64; x < 64; x++) + TileMap* maps[4]; + Material* tileMapMaterial = new Material("tileSetMat", ShaderRegistry::GetShader("TileMapShader v2")); + + for (int i = 0; i < 4; i++) { - for (int y = -64; y < 64; y++) + Transformable* tileMap = new Transformable("tileMapL" + std::to_string(i)); + tileMap->position += {0,0.5f * i,0}; + gameLayer.AddTransformable(tileMap); + Texture* setTexture = new Texture("tiles.png"); + TileSet* set = new TileSet(setTexture, 10, 10); + TileMap* map = new TileMap(); + map->chunkSize = 12; + map->set = set; + + Renderable* rnd = new Renderable(tileMapMaterial); + + tileMap->AddBehaviourScript(map); + tileMap->AddBehaviourScript(rnd); + + maps[i] = map; + } + + + + const siv::PerlinNoise::seed_type seed = 123456u; + const siv::PerlinNoise perlin{ seed }; + + for (int x = circleRadius * -4; x < circleRadius * 4; x++) + { + for (int y = circleRadius * -4; y < circleRadius * 4; y++) { - map->SetTile(Vector2(x, y), {0,9}); + float noise = (float)perlin.octave2D_01(x * 0.05f,y * 0.05f, 4); + float noiseFallof = 1; + Vector2 realpos = maps[0]->TileMapToRealPos(Vector2(x,y)); + realpos *= {1,1.5f}; + float dist = realpos.Length(); + if(dist <= circleRadius && dist >= circleFallof) + { + float reldist = dist - circleFallof; + noiseFallof = (reldist / (circleRadius - circleFallof) - 1) * -1; + } + noise *= noiseFallof; + if(dist <= circleRadius && noise >= 0.2f) + { + if(noise >= 0.8f) + { + maps[3]->SetTile(Vector2(x, y), {0,9}); + } + else if(noise >= 0.6f) + { + maps[2]->SetTile(Vector2(x, y), {0,9}); + } + else if(noise >= 0.4f) + { + maps[1]->SetTile(Vector2(x, y), {0,9}); + } + else + { + maps[0]->SetTile(Vector2(x, y), {0,9}); + } + } } } - Material* tileMapMaterial = new Material("tileSetMat", ShaderRegistry::GetShader("Basic Unlit TileMap Shader")); - Renderable* rnd = new Renderable(tileMapMaterial); - - tileMap->AddBehaviourScript(map); - tileMap->AddBehaviourScript(rnd); } \ No newline at end of file diff --git a/TSE-RTS/src/main.cpp b/TSE-RTS/src/main.cpp index 0a72ff1..8755779 100644 --- a/TSE-RTS/src/main.cpp +++ b/TSE-RTS/src/main.cpp @@ -15,6 +15,7 @@ #include "elements/Scene.hpp" #include "EditorSubsystem.hpp" #include "game.hpp" +#include "shaders/TileMapShader.hpp" #define USE_EDITOR @@ -38,6 +39,8 @@ void SetupWindow() wnd = new WindowGlfw(PROJECT_NAME, 800, 600, new OpenGLRenderingBackend(backColor, false, 8, false), WindowType::Maximized); #endif LoadBasicShaders(wnd->GetSize().x, wnd->GetSize().y); + TileMapShader::Init(wnd->GetSize().x, wnd->GetSize().y); + ShaderRegistry::SetShader("TileMapShader v2", TileMapShader::Instance()); rend = new DefaultRendererOpenGL(*BasicShader::Instance()); currentScene = new Scene(); diff --git a/TSE-RTS/src/shaders/TileMapShader.cpp b/TSE-RTS/src/shaders/TileMapShader.cpp new file mode 100644 index 0000000..eda4a56 --- /dev/null +++ b/TSE-RTS/src/shaders/TileMapShader.cpp @@ -0,0 +1,219 @@ +#include "TileMapShader.hpp" +#include "BehaviourScripts/Renderable.hpp" +#include "BehaviourScripts/TileMap.hpp" +#include "Color.hpp" +#include "TileMapShaderGLSL.hpp" + +using namespace TSE; +using namespace TSE::GLFW; + +#define SHADER_MESH_INDEX 0 +#define SHADER_POS_INDEX 1 +#define SHADER_SPRITE_INDEX 2 + +#define SHADER_PACKAGE_SIZE sizeof(float) * (3 + 1) + +TileMapShader* TileMapShader::instance = nullptr; + +TileMapShader *TileMapShader::Instance() +{ + return instance; +} + +void TileMapShader::Destroy() +{ + if(instance != nullptr) + delete instance; + instance = nullptr; +} + +void TileMapShader::Init(float width, float height) +{ + std::vector> parts; + parts.push_back(ShaderPart::LoadFromString(vertTile2, GL_VERTEX_SHADER)); + parts.push_back(ShaderPart::LoadFromString(fragTile2, GL_FRAGMENT_SHADER)); + instance = new TileMapShader(std::move(parts)); + + instance->Enable(); + int texIDs[] = { 0 }; + instance->SetUniform("atlas", 0); + instance->Disable(); +} + +TileMapShader::TileMapShader(std::vector> &&parts) : Shader(parts) +{ + PackageSize = SHADER_PACKAGE_SIZE; +} + +TileMapShader::~TileMapShader() +{ + if (meshVBO) glDeleteBuffers(1, &meshVBO); + if (meshIBO) glDeleteBuffers(1, &meshIBO); +} + +void TileMapShader::SetMesh(const void *verts, int vertCount, int stride, int floatCountPerVertex, int posOffsetBytes, GLenum primitive, const void *indices, int indexCount, GLenum indexType) +{ + GLint prevVAO = 0, prevArrayBuffer = 0, prevElementBuffer = 0; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &prevVAO); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer); + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prevElementBuffer); + + if (!meshVBO) glGenBuffers(1, &meshVBO); + glBindBuffer(GL_ARRAY_BUFFER, meshVBO); + glBufferData(GL_ARRAY_BUFFER, vertCount * stride, verts, GL_STATIC_DRAW); + + if (indices && indexCount > 0) + { + if (!meshIBO) glGenBuffers(1, &meshIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshIBO); + GLsizeiptr idxSize = + (indexType == GL_UNSIGNED_INT ? 4 : + indexType == GL_UNSIGNED_SHORT? 2 : 1) * indexCount; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idxSize, indices, GL_STATIC_DRAW); + meshIndexCount = indexCount; + meshIndexType = indexType; + } + else + { + // Kein Index-Buffer + if (meshIBO) { glDeleteBuffers(1, &meshIBO); meshIBO = 0; } + meshIndexCount = 0; + } + + meshVertexCount = vertCount; + meshStride = stride; + meshPosOffset = posOffsetBytes; + meshPosSize = floatCountPerVertex; + meshPrimitive = primitive; + meshReady = true; + + glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prevElementBuffer); + glBindVertexArray(prevVAO); +} + +void TileMapShader::OnEnable() const +{ + if (!meshReady) + { + // Fallback: unit-Quad als TRIANGLE_FAN (4 Vertices, 2D Positionen) + const float quad[8] = { -0.5f,-0.5f, 0.5f,-0.5f, 0.5f,0.5f, -0.5f,0.5f }; + const_cast(this)->SetMesh( + quad, 4, sizeof(float)*2, 2, 0, GL_TRIANGLE_FAN + ); + } + + GLint prevArrayBuffer = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer); + + glBindBuffer(GL_ARRAY_BUFFER, meshVBO); + glEnableVertexAttribArray(SHADER_MESH_INDEX); // LOC_QUAD/pos + glVertexAttribPointer(SHADER_MESH_INDEX, meshPosSize, GL_FLOAT, GL_FALSE, meshStride, (void*)meshPosOffset); + glVertexAttribDivisor(SHADER_MESH_INDEX, 0); // per-vertex (Mesh) + + glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer); + + // layout 1: position (vec3) + glEnableVertexAttribArray(SHADER_POS_INDEX); + glVertexAttribPointer(SHADER_POS_INDEX, 3, GL_FLOAT, GL_FALSE, PackageSize, (void*)0); + glVertexAttribDivisor(SHADER_POS_INDEX, 1); + + // layout 2: spriteindex (float) + glEnableVertexAttribArray(SHADER_SPRITE_INDEX); + glVertexAttribPointer(SHADER_SPRITE_INDEX, 1, GL_FLOAT, GL_FALSE, PackageSize, (void*)(sizeof(float)*3)); + glVertexAttribDivisor(SHADER_SPRITE_INDEX, 1); +} + +void TileMapShader::OnDisable() const +{ + glDisableVertexAttribArray(SHADER_MESH_INDEX); + glDisableVertexAttribArray(SHADER_POS_INDEX); + glDisableVertexAttribArray(SHADER_SPRITE_INDEX); +} + +void TileMapShader::OnFlush() +{ + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, TextureID); +} + +void TileMapShader::OnDrawCall(int indexCount) +{ + if (instanceCount <= 0) return; + SetUniform("spriteCount", &SpriteCount); + SetUniform("spriteScale", &SpriteScale); + SetUniform("layerHeight", LayerHeight); + + GLint prevElementBuffer = 0; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prevElementBuffer); + + if (meshIBO && meshIndexCount > 0) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshIBO); + glDrawElementsInstanced(meshPrimitive, meshIndexCount, meshIndexType, (void*)0, instanceCount); + } + else + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDrawArraysInstanced(meshPrimitive, 0, meshVertexCount, instanceCount); + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)prevElementBuffer); + instanceCount = 0; +} + +void TileMapShader::OnSubmit(const Transformable &t, float *&target, TransformationStack &stack, void (*restartDrawcall)(IRenderer &), IRenderer &rnd) +{ + auto* r = dynamic_cast(t.GetBehaviourScript(RENDERABLE)); + if (!r) return; + + auto* tm = dynamic_cast(t.GetBehaviourScript(TILE_MAP)); + if (!tm) return; + + LayerHeight = t.position.y / 1.5f; + + auto tileSet = tm->GetTileSet(); + TextureID = tileSet->GetTextueID(); + SpriteCount = tileSet->GetCount(); + SpriteScale = tm->SpriteScale; + + const int chunkcount = tm->GetChunkCount(); + Vector2 orderedChunks[chunkcount]; + tm->GetChunkPositionsInOrder(orderedChunks); + + Matrix4x4 matr = t.GetLocalMatrix(); + + stack.Push(matr); + + for(auto chunkPos : orderedChunks) + { + auto chunk = tm->GetChunk(chunkPos); + const int spriteCount = chunk->GetSpriteCount(); + Vector2 spritePositions[spriteCount]; + int spriteIds[spriteCount]; + chunk->GetOrderedPositions(spritePositions); + chunk->GetOrderedSpriteIds(spriteIds); + int chunkSize = chunk->GetChunksize(); + + for (int i = 0; i < spriteCount; i++) + { + Matrix4x4 mat = Matrix4x4::ToTranslationMatrix((chunkPos - chunk->nextLine * chunkPos.y) + spritePositions[i]) * Matrix4x4::ToRotationMatrix(Quaternion()) * Matrix4x4::ToScaleMatrix({1,1,1}); + stack.Push(mat); + Vector3 pos = stack.Top() * Vector3(0,0,0); + + *target++ = pos.x; + *target++ = pos.y; + *target++ = pos.z; + *target++ = spriteIds[i]; + + ++instanceCount; + stack.Pop(); + + if(instanceCount >= 20000) + restartDrawcall(rnd); + } + } + + stack.Pop(); + restartDrawcall(rnd); +} diff --git a/TSE-RTS/src/shaders/TileMapShader.hpp b/TSE-RTS/src/shaders/TileMapShader.hpp new file mode 100644 index 0000000..8722e7a --- /dev/null +++ b/TSE-RTS/src/shaders/TileMapShader.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "GL/gl3w.h" +#include "GL/gl.h" +#include "shader/Shader.hpp" +#include "Types.hpp" + +class TileMapShader : public TSE::GLFW::Shader +{ +private: + static TileMapShader* instance; + mutable bool meshReady = false; + GLuint meshVBO = 0; + GLuint meshIBO = 0; + GLsizei meshVertexCount = 0; // für DrawArraysInstanced + GLsizei meshIndexCount = 0; // für DrawElementsInstanced + GLenum meshPrimitive = GL_TRIANGLES; + GLenum meshIndexType = GL_UNSIGNED_SHORT; + int instanceCount = 0; // eigener Instanzzähler + GLint meshPosSize = 2; // 2D (Billboard-Formen), für 3D Meshes: 3 + GLsizei meshStride = sizeof(float) * 2; + size_t meshPosOffset = 0; + GLuint TextureID; + TSE::Vector2 SpriteCount; + TSE::Vector2 SpriteScale; + float LayerHeight; + +public: + static TileMapShader* Instance(); + static void Destroy(); + static void Init(float width, float height); + TileMapShader(std::vector>&& parts); + ~TileMapShader(); + void SetMesh(const void* verts, int vertCount, int stride, int floatCountPerVertex, int posOffsetBytes, GLenum primitive, const void* indices = nullptr, int indexCount = 0, GLenum indexType = GL_UNSIGNED_SHORT); + +protected: + void OnEnable() const override; + void OnDisable() const override; + void OnFlush() override; + void OnDrawCall(int indexCount) override; + void OnSubmit(const TSE::Transformable& t, float*& target, TSE::TransformationStack& stack, void (*restartDrawcall)(TSE::IRenderer&), TSE::IRenderer& rnd) override; +}; diff --git a/TSE-RTS/src/shaders/TileMapShaderGLSL.hpp b/TSE-RTS/src/shaders/TileMapShaderGLSL.hpp new file mode 100644 index 0000000..ebb1099 --- /dev/null +++ b/TSE-RTS/src/shaders/TileMapShaderGLSL.hpp @@ -0,0 +1,65 @@ +#pragma once + +inline const char* vertTile2 = R"( + #version 330 core + + layout(location = 0) in vec2 aPos; + + layout(location = 1) in vec3 iTilePos; + layout(location = 2) in float iSpriteId; + + uniform mat4 prMatrix; + uniform mat4 camMatrix; + uniform vec2 spriteCount; + uniform vec2 spriteScale; + + out vec2 vUV; + flat out int vSpriteId; + + void main() + { + vec3 local = vec3(aPos.x, aPos.y, 0); + vec2 baseUV = aPos + vec2(0.5); + vec3 tileSize = vec3(spriteScale.x, spriteScale.y, 1); + + vec3 worldPos = (iTilePos * tileSize) + (local * tileSize); + + gl_Position = prMatrix * camMatrix * vec4(worldPos.x, worldPos.y, worldPos.z, 1.0); + + vUV = baseUV; + vSpriteId = int(iSpriteId + 0.5); + } +)"; + +inline const char* fragTile2 = R"( + #version 330 core + + in vec2 vUV; + flat in int vSpriteId; + + uniform sampler2D atlas; + uniform vec2 spriteCount; + uniform float layerHeight; + + layout(location = 0) out vec4 FragColor; + layout(location = 1) out vec4 FragHeight; + + void main() + { + vec2 tileUVSize = 1.0 / spriteCount; + + int cols = int(spriteCount.x); + int sx = vSpriteId % cols; + int sy = vSpriteId / cols; + + vec2 atlasOffset = vec2(float(sx), float(sy)) * tileUVSize; + vec2 atlasUV = atlasOffset + (vUV * tileUVSize); + vec4 c = texture(atlas, atlasUV); + if (c.a < 0.01) discard; + float colorScaler = 1 - ((layerHeight - 1) * -1) * 0.3; + c = vec4(c.x * colorScaler,c.y * colorScaler,c.z * colorScaler,c.w); + + FragColor = c; + FragHeight = vec4(0,0,layerHeight,1); + } +)";