Wednesday, February 4, 2026
spot_img

Easy methods to optimize rendering logic for lights and fluids in a 2D tile-based sport?


I need assistance with strategies for optimizing my rendering code.

Based mostly on perf, it takes an amazing period of time to run, more often than not is spent on the sunshine rendering and never as a lot, however nonetheless fairly a big portion is spent on liquids.

I actually have no idea what to do. The sunshine viewport is 2 occasions smaller than the dimensions of the window and something much less appears to be like uneven. As might be seen within the code, I do not use chunks and simply replace every part within the display screen boundaries. I eliminated some pointless code within the instance.

// Render capabilities

void Map::renderLight(const Camera2D &digital camera, Texture2D &texture, float x, float y, const Vector2 &dimension, const Shade &shade) const {
   drawTexture(texture, {(((x + 0.5f - digital camera.goal.x) * digital camera.zoom) + digital camera.offset.x) / 2.0f, (((y + 0.5f - digital camera.goal.y) * digital camera.zoom) + digital camera.offset.y) / 2.0f}, dimension, 0, shade);
}

void Map::render(const std::vector<DroppedItem> &droppedItems, const Participant &participant, float accumulator, const Rectangle &cameraBounds, const Camera2D &digital camera) const {
   // Render background partitions
   for (int y = cameraBounds.y; y <= cameraBounds.peak; ++y) {
      for (int x = cameraBounds.x; x <= cameraBounds.width; ++x) {
         const Block &wall = partitions[y][x];
         if ((wall.sort & BlockType::empty) || !(blocks[y][x].sort & BlockType::clear)) {
            proceed;
         }

         int oldX = x;
         whereas (x <= cameraBounds.width && partitions[y][x].id == wall.id && (blocks[y][x].sort & BlockType::clear)) {
            x += 1;
         }

         drawTextureBlock(*wall.texture, {(float)oldX, (float)y, float(x - oldX), 1}, wallTint);
         x -= 1;
      }
   }

   // Render blocks
   for (int y = cameraBounds.y; y <= cameraBounds.peak; ++y) {
      for (int x = cameraBounds.x; x <= cameraBounds.width; ++x) {
         const Block &block = blocks[y][x];
         if (block.sort & BlockType::empty) {
            proceed;
         }

         // Render torches
         else if (block.sort & BlockType::torch) {
            constexpr static float torchLightOffsetsY[] = {-1.0f, -1.0f * (5.0f / 8.0f), -0.75f, -0.75f, -1.0f * (5.0f / 8.0f)};

            float textureSize = block.texture->peak / 2.0f;
            DrawTexturePro(*block.texture, {textureSize * block.value2, 0, textureSize, textureSize}, {(float)x, (float)y, 1, 1}, {0, 0}, 0, WHITE);
            DrawTexturePro(*block.texture, {textureSize * block.worth, textureSize, textureSize, textureSize}, {(float)x, (float)y + torchLightOffsetsY[block.value2], 1, 1}, {0, 0}, 0, WHITE);
            proceed;
         }

         // Render common blocks
         int oldX = x;
         whereas (x <= cameraBounds.width && blocks[y][x].id == block.id) {
            x += 1;
         }

         drawTextureBlock(*block.texture, {(float)oldX, (float)y, float(x - oldX), 1});
         x -= 1;
      }
   }

   // Render fluids
   Shader &waterShader = getShader("water");
   float time = GetTime();
   SetShaderValue(waterShader, timeShaderLocation, &time, SHADER_UNIFORM_FLOAT);

   // Render fluids
   BeginShaderMode(waterShader);
   for (int y = cameraBounds.y; y <= cameraBounds.peak; ++y) {
      for (int x = cameraBounds.x; x <= cameraBounds.width; ++x) {
         if (!isLiquid(x, y)) {
            proceed;
         }
         
         float peak = (float)getLiquidHeight(x, y) / (float)maxLiquidLayers;
         Shade liquidFlags;
         liquidFlags.r = (is(x, y - 1, BlockType::strong) && !is(x, y - 1, BlockType::platform) ? 255 : 0);
         liquidFlags.g = (!isLiquidAtAll(x, y + 1) || !isLiquidOfType(x, y + 1, liquidTypes[y][x]) ? 255 : 0);

         drawFluidBlock(getLiquidTexture(x, y), {(float)x, (float)y + (1 - peak), 1, peak}, Fade(liquidFlags, peak));
      }
   }
   EndShaderMode();

   // Render lights
   BeginTextureMode(lightmap);
   ClearBackground(BLACK);
   BeginBlendMode(BLEND_ADDITIVE);

   int lightBoundsMinX = std::max<int>(0, cameraBounds.x - 8);
   int lightBoundsMinY = std::max<int>(0, cameraBounds.y - 8);
   int lightBoundsMaxX = std::min<int>(sizeX - 1, cameraBounds.width + 8);
   int lightBoundsMaxY = std::min<int>(sizeY - 1, cameraBounds.peak + 8);

   Shade airLightColor   = getLightBasedOnTime();
   Shade waterLightColor = Fade(airLightColor, 0.1f);

   // const perform hack
   static float counter = 0.0f;
   counter += GetFrameTime();

   float sizeOffset     = std::sin(counter * 1.5f) * digital camera.zoom * 0.4f;
   float positionOffset = std::cos(counter * 0.8f) * digital camera.zoom * 0.0075f;

   Vector2 lightSize      = {3.5f * digital camera.zoom, 3.5f * digital camera.zoom};
   Vector2 lightLargeSize = {lightSize.x + lightSize.x, lightSize.y + lightSize.y};
   Vector2 lightHugeSize  = {lightLargeSize.x + lightSize.x, lightLargeSize.y + lightSize.y};
   Vector2 liquidSize     = {lightSize.x + sizeOffset, lightSize.y + sizeOffset};

   Texture2D &lightHugeTexture  = getTexture("lightsource_6x");
   Texture2D &lightLargeTexture = getTexture("lightsource_4x");
   Texture2D &lightTexture      = getTexture("lightsource_2x");

   for (int y = lightBoundsMinY; y <= lightBoundsMaxY; ++y) {
      for (int x = lightBoundsMinX; x <= lightBoundsMaxX; ++x) {
         BlockType sort = blocks[y][x].sort;
         if (!(sort & BlockType::lightsource) && !(sort & BlockType::clear)) {
            proceed;
         }

         // Direct gentle sources, ones who don't require the wall behind to be clear
         if (isLiquidAtAll(x, y) && isLiquidOfType(x, y, LiquidType::lava)) {
            if (isLiquid(x, y)) {
               renderLight(digital camera, lightTexture, x + positionOffset, y + positionOffset, liquidSize, {255, 125, 0, 255});
            }
            proceed;
         } else if (sort & BlockType::torch) {
            renderLight(digital camera, lightHugeTexture, x + positionOffset, y + positionOffset, lightHugeSize, {255, 200, 160, 255}); // Gentle orange
         } else if (sort & BlockType::lightsource) {
            renderLight(digital camera, lightLargeTexture, x, y, lightLargeSize, {255, 255, 0, 255});
         }

         if (!(partitions[y][x].sort & BlockType::clear)) {
            proceed;
         }

         // Oblique gentle sources, these require to background to be empty
         if (isLiquid(x, y)) {
            renderLight(digital camera, lightTexture, x, y, lightSize, waterLightColor);
         } else {
            renderLight(digital camera, lightTexture, x, y, lightSize, airLightColor);
         }
      }
   }

   EndBlendMode();
   EndTextureMode();

   BeginBlendMode(BLEND_MULTIPLIED);
   DrawTexturePro(lightmap.texture, {0, 0, (float)lightmap.texture.width, -(float)lightmap.texture.peak}, {0, 0, (float)GetScreenWidth(), (float)GetScreenHeight()}, {0, 0}, 0, WHITE);
   EndBlendMode();

   BeginMode2D(digital camera); // EndTextureMode disables it for some purpose
}

Easy methods to optimize rendering logic for lights and fluids in a 2D tile-based sport?

First, partitions and blocks get rendered. Comparable blocks are grouped on the X axis. After that liquids are rendered, a distinct container is used for them in order that merely will get iterated. The water shader makes use of the draw shade as two attributes – is prime block strong and is backside block strong. Lastly, I render the lights, once more iterating by the map. I render them to a viewport two occasions smaller than the display screen dimension in additive mix mode and at last draw the viewport in multiplicative mix mode.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisement -spot_img

Latest Articles