diff --git a/targets/minecraft/client/renderer/Chunk.cpp b/targets/minecraft/client/renderer/Chunk.cpp index d89a7385f..c5d2b9589 100644 --- a/targets/minecraft/client/renderer/Chunk.cpp +++ b/targets/minecraft/client/renderer/Chunk.cpp @@ -3,28 +3,34 @@ #include #include +#include #include #include #include #include #include -#include "platform/sdl2/Render.h" #include "LevelRenderer.h" -#include "app/linux/Stubs/winapi_stubs.h" -#include "app/include/FrameProfiler.h" #include "TileRenderer.h" +#include "app/include/FrameProfiler.h" +#include "app/linux/LinuxGame.h" +#include "app/linux/Stubs/winapi_stubs.h" +#include "minecraft/SharedConstants.h" +#include "minecraft/client/renderer/GameRenderer.h" #include "minecraft/client/renderer/Tesselator.h" #include "minecraft/client/renderer/culling/Culler.h" #include "minecraft/client/renderer/tileentity/TileEntityRenderDispatcher.h" +#include "minecraft/world/Icon.h" #include "minecraft/world/entity/Entity.h" #include "minecraft/world/level/Level.h" #include "minecraft/world/level/LevelSource.h" #include "minecraft/world/level/Region.h" #include "minecraft/world/level/chunk/LevelChunk.h" +#include "minecraft/world/level/tile/LiquidTile.h" #include "minecraft/world/level/tile/Tile.h" #include "minecraft/world/level/tile/entity/TileEntity.h" #include "minecraft/world/phys/AABB.h" +#include "platform/sdl2/Render.h" int Chunk::updates = 0; @@ -428,6 +434,88 @@ void Chunk::rebuild() { // 4J - optimisation ends //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Binary Mesh Greedy implementation + // https://gedge.ca/blog/2014-08-17-greedy-voxel-meshing/ + // https://github.com/cgerikj/binary-greedy-meshing/tree/master + // https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/ + // also https://github.com/whleucka/voxel/blob/main/src/chunk/chunk_mesh.cpp + // ^ real useful thanks + // also known as the "spongebob! why doesn't my AO work?" + int greedyEligibleCount = 0; + std::vector greedyEligible(16 * 16 * 16, 0); + std::vector greedyLiquidTop(16 * 16 * 16, 0); +#if defined(ENABLE_GREEDY_MESHING) + { + const double greedyEps = 1e-6; + auto greedyIndex = [](int lx, int ly, int lz) { + return (ly << 8) | (lz << 4) | lx; + }; + + for (int z = z0; z < z1; z++) { + for (int x = x0; x < x1; x++) { + for (int y = y0; y < y1; y++) { + int indexY = y; + int offset = 0; + if (indexY >= Level::COMPRESSED_CHUNK_SECTION_HEIGHT) { + indexY -= Level::COMPRESSED_CHUNK_SECTION_HEIGHT; + offset = Level::COMPRESSED_CHUNK_SECTION_TILES; + } + + unsigned char tileId = + tileIds[offset + + (((x - x0) << 11) | ((z - z0) << 7) | indexY)]; + if (tileId == 0 || tileId == 0xff) continue; + + Tile* tile = Tile::tiles[tileId]; + if (tile == nullptr) continue; + if (tile->getRenderLayer() != 0) continue; + if (tile->getRenderShape() != Tile::SHAPE_BLOCK) continue; + if (tile->isEntityTile()) continue; + if (!tile->isSolidRender()) continue; + if (Tile::transculent[tileId]) continue; + if (tileId == Tile::grass_Id) continue; + + tile->updateShape(level, x, y, z, -1, + std::shared_ptr()); + + double x0s = tile->getShapeX0(); + double x1s = tile->getShapeX1(); + double y0s = tile->getShapeY0(); + double y1s = tile->getShapeY1(); + double z0s = tile->getShapeZ0(); + double z1s = tile->getShapeZ1(); + + if (std::fabs(x0s) > greedyEps || + std::fabs(y0s) > greedyEps || + std::fabs(z0s) > greedyEps || + std::fabs(x1s - 1.0) > greedyEps || + std::fabs(y1s - 1.0) > greedyEps || + std::fabs(z1s - 1.0) > greedyEps) + continue; + + bool ok = true; + for (int face = 0; face < 6; face++) { + if (tile->getTexture(level, x, y, z, face) == nullptr) { + ok = false; + break; + } + } + if (!ok) continue; + + int lx = x - x0; + int ly = y - y0; + int lz = z - z0; + greedyEligible[greedyIndex(lx, ly, lz)] = 1; + greedyEligibleCount++; + } + } + } + } + tileRenderer->setWaterTopSkipMask(greedyLiquidTop.data()); +#else + tileRenderer->setWaterTopSkipMask(nullptr); +#endif + Tesselator::Bounds bounds; // 4J MGH - added { // this was the old default clip bounds for the chunk, set in @@ -443,8 +531,704 @@ void Chunk::rebuild() { for (int currentLayer = 0; currentLayer < 2; currentLayer++) { bool renderNextLayer = false; bool rendered = false; + // second part of the greedy mesh implementation + bool listStarted = false; + bool normalTessActive = false; + bool greedyTessActive = false; + int greedyMergedQuads = 0; + int greedyTilesEmitted = 0; + int greedyLiquidMergedQuads = 0; + int greedyLiquidTilesEmitted = 0; - bool started = false; + auto startListIfNeeded = [&]() { + if (listStarted) return; + listStarted = true; + + glNewList(lists + currentLayer, GL_COMPILE); + glDepthMask(true); + }; + + auto beginNormal = [&]() { + if (normalTessActive) return; + startListIfNeeded(); + + t->useCompactVertices(true); + t->useTileUV(false); + t->begin(); + t->offset((float)(-this->x), (float)(-this->y), (float)(-this->z)); + normalTessActive = true; + }; + + auto endNormal = [&]() { + if (!normalTessActive) return; + t->end(); + bounds.addBounds(t->bounds); + t->offset(0, 0, 0); + normalTessActive = false; + }; + + auto beginGreedy = [&]() { + if (greedyTessActive) return; + startListIfNeeded(); + + t->useCompactVertices(false); + t->useTileUV(true); + t->begin(); + t->offset((float)(-this->x), (float)(-this->y), (float)(-this->z)); + greedyTessActive = true; + }; + + auto endGreedy = [&]() { + if (!greedyTessActive) return; + t->end(); + bounds.addBounds(t->bounds); + t->useTileUV(false); + t->offset(0, 0, 0); + greedyTessActive = false; + }; + + auto greedyIndex = [](int lx, int ly, int lz) { + return (ly << 8) | (lz << 4) | lx; + }; + + auto getTileIdLocal = [&](int lx, int ly, int lz) -> unsigned char { + int worldY = y0 + ly; + int indexY = worldY; + int offset = 0; + if (indexY >= Level::COMPRESSED_CHUNK_SECTION_HEIGHT) { + indexY -= Level::COMPRESSED_CHUNK_SECTION_HEIGHT; + offset = Level::COMPRESSED_CHUNK_SECTION_TILES; + } + return tileIds[offset + + (((lx + 0) << 11) | ((lz + 0) << 7) | indexY)]; + }; + // bread taste better than key + auto computeFaceKey = [&](Tile* tile, int wx, int wy, int wz, int face, + uint32_t& colorKey, int& lightKey) { + int col = tile->getColor(level, wx, wy, wz); + float r = ((col >> 16) & 0xff) / 255.0f; + float g = ((col >> 8) & 0xff) / 255.0f; + float b = ((col) & 0xff) / 255.0f; + + + if (GameRenderer::anaglyph3d) { + // todo: this will DEF break anaglyph rendering + // do we still need it????????? + } + + float shade = 1.0f; + switch (face) { + case 0: + shade = 0.5f; + break; + case 1: + shade = 1.0f; + break; + case 2: + case 3: + shade = 0.8f; + break; + case 4: + case 5: + shade = 0.6f; + break; + default: + break; + } + + r *= shade; + g *= shade; + b *= shade; + + if (SharedConstants::TEXTURE_LIGHTING) { + int nx = wx, ny = wy, nz = wz; + if (face == 0) ny--; + if (face == 1) ny++; + if (face == 2) nz--; + if (face == 3) nz++; + if (face == 4) nx--; + if (face == 5) nx++; + lightKey = tileRenderer->getLightColor(tile, level, nx, ny, nz); + } else { + int nx = wx, ny = wy, nz = wz; + if (face == 0) ny--; + if (face == 1) ny++; + if (face == 2) nz--; + if (face == 3) nz++; + if (face == 4) nx--; + if (face == 5) nx++; + float br = tile->getBrightness(level, nx, ny, nz); + r *= br; + g *= br; + b *= br; + lightKey = 0; + } + + auto clampByte = [](float v) -> uint8_t { + int iv = (int)(v * 255.0f + 0.5f); + if (iv < 0) iv = 0; + if (iv > 255) iv = 255; + return (uint8_t)iv; + }; + + colorKey = + (clampByte(r) << 16) | (clampByte(g) << 8) | (clampByte(b)); + }; + + auto shadeAt = [&](Tile* tile, int ax, int ay, int az) { + return tileRenderer->getShadeBrightness(tile, level, ax, ay, az); + }; + + auto aoCorner = [&](float s1, float s2, float s3) { + if (s1 < 1.0f && s2 < 1.0f) return 0.2f; + return (s1 + s2 + s3) / 3.0f; + }; + + auto computeFaceAO = [&](Tile* tile, int wx, int wy, int wz, int face, + float outAo[4]) { + switch (face) { + case 0: { // Down (y-1) + float sW = shadeAt(tile, wx - 1, wy - 1, wz); + float sE = shadeAt(tile, wx + 1, wy - 1, wz); + float sN = shadeAt(tile, wx, wy - 1, wz - 1); + float sS = shadeAt(tile, wx, wy - 1, wz + 1); + float sWN = shadeAt(tile, wx - 1, wy - 1, wz - 1); + float sWS = shadeAt(tile, wx - 1, wy - 1, wz + 1); + float sEN = shadeAt(tile, wx + 1, wy - 1, wz - 1); + float sES = shadeAt(tile, wx + 1, wy - 1, wz + 1); + + outAo[0] = aoCorner(sW, sS, sWS); + outAo[1] = aoCorner(sW, sN, sWN); + outAo[2] = aoCorner(sE, sN, sEN); + outAo[3] = aoCorner(sE, sS, sES); + } break; + case 1: { // Up (y+1) + float sW = shadeAt(tile, wx - 1, wy + 1, wz); + float sE = shadeAt(tile, wx + 1, wy + 1, wz); + float sN = shadeAt(tile, wx, wy + 1, wz - 1); + float sS = shadeAt(tile, wx, wy + 1, wz + 1); + float sWN = shadeAt(tile, wx - 1, wy + 1, wz - 1); + float sWS = shadeAt(tile, wx - 1, wy + 1, wz + 1); + float sEN = shadeAt(tile, wx + 1, wy + 1, wz - 1); + float sES = shadeAt(tile, wx + 1, wy + 1, wz + 1); + + outAo[0] = aoCorner(sE, sS, sES); + outAo[1] = aoCorner(sE, sN, sEN); + outAo[2] = aoCorner(sW, sN, sWN); + outAo[3] = aoCorner(sW, sS, sWS); + } break; + case 2: { // North (z-1) + float sW = shadeAt(tile, wx - 1, wy, wz - 1); + float sE = shadeAt(tile, wx + 1, wy, wz - 1); + float sU = shadeAt(tile, wx, wy + 1, wz - 1); + float sD = shadeAt(tile, wx, wy - 1, wz - 1); + float sWU = shadeAt(tile, wx - 1, wy + 1, wz - 1); + float sWD = shadeAt(tile, wx - 1, wy - 1, wz - 1); + float sEU = shadeAt(tile, wx + 1, wy + 1, wz - 1); + float sED = shadeAt(tile, wx + 1, wy - 1, wz - 1); + + outAo[0] = aoCorner(sW, sU, sWU); + outAo[1] = aoCorner(sE, sU, sEU); + outAo[2] = aoCorner(sE, sD, sED); + outAo[3] = aoCorner(sW, sD, sWD); + } break; + case 3: { // South (z+1) + float sW = shadeAt(tile, wx - 1, wy, wz + 1); + float sE = shadeAt(tile, wx + 1, wy, wz + 1); + float sU = shadeAt(tile, wx, wy + 1, wz + 1); + float sD = shadeAt(tile, wx, wy - 1, wz + 1); + float sWU = shadeAt(tile, wx - 1, wy + 1, wz + 1); + float sWD = shadeAt(tile, wx - 1, wy - 1, wz + 1); + float sEU = shadeAt(tile, wx + 1, wy + 1, wz + 1); + float sED = shadeAt(tile, wx + 1, wy - 1, wz + 1); + + outAo[0] = aoCorner(sW, sU, sWU); + outAo[1] = aoCorner(sW, sD, sWD); + outAo[2] = aoCorner(sE, sD, sED); + outAo[3] = aoCorner(sE, sU, sEU); + } break; + case 4: { // West (x-1) + float sN = shadeAt(tile, wx - 1, wy, wz - 1); + float sS = shadeAt(tile, wx - 1, wy, wz + 1); + float sU = shadeAt(tile, wx - 1, wy + 1, wz); + float sD = shadeAt(tile, wx - 1, wy - 1, wz); + float sNU = shadeAt(tile, wx - 1, wy + 1, wz - 1); + float sND = shadeAt(tile, wx - 1, wy - 1, wz - 1); + float sSU = shadeAt(tile, wx - 1, wy + 1, wz + 1); + float sSD = shadeAt(tile, wx - 1, wy - 1, wz + 1); + + outAo[0] = aoCorner(sS, sU, sSU); + outAo[1] = aoCorner(sN, sU, sNU); + outAo[2] = aoCorner(sN, sD, sND); + outAo[3] = aoCorner(sS, sD, sSD); + } break; + case 5: { // East (x+1) + float sN = shadeAt(tile, wx + 1, wy, wz - 1); + float sS = shadeAt(tile, wx + 1, wy, wz + 1); + float sU = shadeAt(tile, wx + 1, wy + 1, wz); + float sD = shadeAt(tile, wx + 1, wy - 1, wz); + float sNU = shadeAt(tile, wx + 1, wy + 1, wz - 1); + float sND = shadeAt(tile, wx + 1, wy - 1, wz - 1); + float sSU = shadeAt(tile, wx + 1, wy + 1, wz + 1); + float sSD = shadeAt(tile, wx + 1, wy - 1, wz + 1); + + outAo[0] = aoCorner(sS, sD, sSD); + outAo[1] = aoCorner(sN, sD, sND); + outAo[2] = aoCorner(sN, sU, sNU); + outAo[3] = aoCorner(sS, sU, sSU); + } break; + default: + outAo[0] = outAo[1] = outAo[2] = outAo[3] = 1.0f; + break; + } + }; + + struct GreedyFaceKey { + Tile* tile; + Icon* tex; + uint32_t colorKey; + uint32_t aoKey; + int lightKey; + bool mipmap; + bool operator==(const GreedyFaceKey& other) const { + return tile == other.tile && tex == other.tex && + colorKey == other.colorKey && aoKey == other.aoKey && + lightKey == other.lightKey && mipmap == other.mipmap; + } + }; + + auto greedyMeshFace = [&](int face) { + const int U = 16; + const int V = 16; + std::vector valid(U * V, 0); + std::vector keys(U * V); + bool faceRendered = false; + + for (int slice = 0; slice < 16; slice++) { + memset(valid.data(), 0, valid.size()); + + for (int v = 0; v < V; v++) { + for (int u = 0; u < U; u++) { + int lx = 0, ly = 0, lz = 0; + switch (face) { + case 0: + case 1: + lx = u; + ly = slice; + lz = v; + break; + case 2: + case 3: + lx = u; + ly = v; + lz = slice; + break; + case 4: + case 5: + lx = slice; + ly = v; + lz = u; + break; + } + + int idx = greedyIndex(lx, ly, lz); + if (greedyEligible[idx] == 0) continue; + + int wx = x0 + lx; + int wy = y0 + ly; + int wz = z0 + lz; + + unsigned char tileId = getTileIdLocal(lx, ly, lz); + if (tileId == 0 || tileId == 0xff) continue; + + Tile* tile = Tile::tiles[tileId]; + if (tile == nullptr) continue; + if (tile->getRenderLayer() != currentLayer) continue; + + bool visible = false; + if (face == 0) + visible = tile->shouldRenderFace(level, wx, wy - 1, + wz, 0); + else if (face == 1) + visible = tile->shouldRenderFace(level, wx, wy + 1, + wz, 1); + else if (face == 2) + visible = tile->shouldRenderFace(level, wx, wy, + wz - 1, 2); + else if (face == 3) + visible = tile->shouldRenderFace(level, wx, wy, + wz + 1, 3); + else if (face == 4) + visible = tile->shouldRenderFace(level, wx - 1, wy, + wz, 4); + else if (face == 5) + visible = tile->shouldRenderFace(level, wx + 1, wy, + wz, 5); + + if (!visible) continue; + + Icon* tex = tile->getTexture(level, wx, wy, wz, face); + if (tex == nullptr) continue; + + GreedyFaceKey key{}; + key.tile = tile; + key.tex = tex; + key.mipmap = Tile::mipmapEnable[tileId]; + + uint32_t colorKey = 0; + int lightKey = 0; + computeFaceKey(tile, wx, wy, wz, face, colorKey, + lightKey); + key.colorKey = colorKey; + key.lightKey = lightKey; + + float ao[4]; + computeFaceAO(tile, wx, wy, wz, face, ao); + auto aoByte = [](float v) -> uint32_t { + int iv = (int)(v * 255.0f + 0.5f); + if (iv < 0) iv = 0; + if (iv > 255) iv = 255; + return (uint32_t)iv; + }; + key.aoKey = (aoByte(ao[0]) << 24) | + (aoByte(ao[1]) << 16) | + (aoByte(ao[2]) << 8) | aoByte(ao[3]); + + valid[u + v * U] = 1; + keys[u + v * U] = key; + } + } + + for (int v = 0; v < V; v++) { + for (int u = 0; u < U; u++) { + int idx = u + v * U; + if (!valid[idx]) continue; + + GreedyFaceKey key = keys[idx]; + + int w = 1; + while (u + w < U && valid[idx + w] && + keys[idx + w] == key) { + w++; + } + + int h = 1; + bool done = false; + while (v + h < V && !done) { + for (int k = 0; k < w; k++) { + int idx2 = (u + k) + (v + h) * U; + if (!valid[idx2] || !(keys[idx2] == key)) { + done = true; + break; + } + } + if (!done) h++; + } + + for (int dv = 0; dv < h; dv++) { + for (int du = 0; du < w; du++) { + valid[(u + du) + (v + dv) * U] = 0; + } + } + + greedyMergedQuads++; + greedyTilesEmitted += (w * h); + + beginGreedy(); + + t->setMipmapEnable(key.mipmap); + if (SharedConstants::TEXTURE_LIGHTING) { + t->tex2(key.lightKey); + } + + float rr = ((key.colorKey >> 16) & 0xff) / 255.0f; + float gg = ((key.colorKey >> 8) & 0xff) / 255.0f; + float bb = (key.colorKey & 0xff) / 255.0f; + + float ao1 = ((key.aoKey >> 24) & 0xff) / 255.0f; + float ao2 = ((key.aoKey >> 16) & 0xff) / 255.0f; + float ao3 = ((key.aoKey >> 8) & 0xff) / 255.0f; + float ao4 = (key.aoKey & 0xff) / 255.0f; + + tileRenderer->setGreedyAO( + rr, gg, bb, ao1, ao2, ao3, ao4, + SharedConstants::TEXTURE_LIGHTING ? key.lightKey + : 0); + + int baseX = 0, baseY = 0, baseZ = 0; + switch (face) { + case 0: + case 1: + baseX = x0 + u; + baseY = y0 + slice; + baseZ = z0 + v; + tileRenderer->setShape( + 0.0f, 0.0f, 0.0f, (float)w, 1.0f, (float)h); + if (face == 0) { + t->normal(0.0f, -1.0f, 0.0f); + tileRenderer->renderFaceDown( + key.tile, baseX, baseY, baseZ, key.tex); + } else { + t->normal(0.0f, 1.0f, 0.0f); + tileRenderer->renderFaceUp( + key.tile, baseX, baseY, baseZ, key.tex); + } + break; + case 2: + case 3: + baseX = x0 + u; + baseY = y0 + v; + baseZ = z0 + slice; + tileRenderer->setShape( + 0.0f, 0.0f, 0.0f, (float)w, (float)h, 1.0f); + if (face == 2) { + t->normal(0.0f, 0.0f, -1.0f); + tileRenderer->renderNorth( + key.tile, baseX, baseY, baseZ, key.tex); + } else { + t->normal(0.0f, 0.0f, 1.0f); + tileRenderer->renderSouth( + key.tile, baseX, baseY, baseZ, key.tex); + } + break; + case 4: + case 5: + baseX = x0 + slice; + baseY = y0 + v; + baseZ = z0 + u; + tileRenderer->setShape(0.0f, 0.0f, 0.0f, 1.0f, + (float)h, (float)w); + if (face == 4) { + t->normal(-1.0f, 0.0f, 0.0f); + tileRenderer->renderWest( + key.tile, baseX, baseY, baseZ, key.tex); + } else { + t->normal(1.0f, 0.0f, 0.0f); + tileRenderer->renderEast( + key.tile, baseX, baseY, baseZ, key.tex); + } + break; + } + // who the fuck at 4j named those functions. + tileRenderer->setApplyAmbienceOcclusion(false); + faceRendered = true; + } + } + } + + return faceRendered; + }; + + struct LiquidTopKey { + Tile* tile; + Icon* tex; + uint32_t colorKey; + int lightKey; + bool mipmap; + int heightQ; + float height; + bool operator==(const LiquidTopKey& other) const { + return tile == other.tile && tex == other.tex && + colorKey == other.colorKey && + lightKey == other.lightKey && mipmap == other.mipmap && + heightQ == other.heightQ; + } + }; + // liquids & oceans are usually flat, so they can definitively benefit from having this optimization. + auto greedyMeshLiquidTop = [&]() { + const int U = 16; + const int V = 16; + bool faceRendered = false; + const float hEps = 1e-4f; + + for (int ly = 0; ly < 16; ly++) { + std::vector valid(U * V, 0); + std::vector keys(U * V); + + for (int lz = 0; lz < V; lz++) { + for (int lx = 0; lx < U; lx++) { + unsigned char tileId = getTileIdLocal(lx, ly, lz); + if (tileId == 0 || tileId == 0xff) continue; + + Tile* tile = Tile::tiles[tileId]; + if (tile == nullptr) continue; + if (tile->getRenderShape() != Tile::SHAPE_WATER) + continue; + if (tile->getRenderLayer() != currentLayer) continue; + + int wx = x0 + lx; + int wy = y0 + ly; + int wz = z0 + lz; + + if (!tile->shouldRenderFace(level, wx, wy + 1, wz, 1)) + continue; + + if (LiquidTile::getSlopeAngle(level, wx, wy, wz, + tile->material) > -999) + continue; + + float h0 = tileRenderer->getWaterHeightAt( + wx, wy, wz, tile->material); + float h1 = tileRenderer->getWaterHeightAt( + wx, wy, wz + 1, tile->material); + float h2 = tileRenderer->getWaterHeightAt( + wx + 1, wy, wz + 1, tile->material); + float h3 = tileRenderer->getWaterHeightAt( + wx + 1, wy, wz, tile->material); + + float hmin = h0; + if (h1 < hmin) hmin = h1; + if (h2 < hmin) hmin = h2; + if (h3 < hmin) hmin = h3; + float hmax = h0; + if (h1 > hmax) hmax = h1; + if (h2 > hmax) hmax = h2; + if (h3 > hmax) hmax = h3; + + if (hmax - hmin > hEps) continue; + + float height = hmax - 0.001f; + if (height < 0.0f) height = hmax; + int heightQ = (int)(height * 4096.0f + 0.5f); + + int data = level->getData(wx, wy, wz); + Icon* tex = tileRenderer->getTexture(tile, 1, data); + if (tex == nullptr) continue; + + LiquidTopKey key{}; + key.tile = tile; + key.tex = tex; + key.mipmap = Tile::mipmapEnable[tileId]; + key.heightQ = heightQ; + key.height = height; + + int col = tile->getColor(level, wx, wy, wz); + float r = ((col >> 16) & 0xff) / 255.0f; + float g = ((col >> 8) & 0xff) / 255.0f; + float b = ((col) & 0xff) / 255.0f; + + if (GameRenderer::anaglyph3d) { + float cr = (r * 30 + g * 59 + b * 11) / 100; + float cg = (r * 30 + g * 70) / (100); + float cb = (r * 30 + b * 70) / (100); + r = cr; + g = cg; + b = cb; + } + + if (SharedConstants::TEXTURE_LIGHTING) { + key.lightKey = tileRenderer->getLightColor( + tile, level, wx, wy, wz); + } else { + float br = tile->getBrightness(level, wx, wy, wz); + r *= br; + g *= br; + b *= br; + key.lightKey = 0; + } + + auto clampByte = [](float v) -> uint8_t { + int iv = (int)(v * 255.0f + 0.5f); + if (iv < 0) iv = 0; + if (iv > 255) iv = 255; + return (uint8_t)iv; + }; + key.colorKey = (clampByte(r) << 16) | + (clampByte(g) << 8) | (clampByte(b)); + + valid[lx + lz * U] = 1; + keys[lx + lz * U] = key; + } + } + + for (int v = 0; v < V; v++) { + for (int u = 0; u < U; u++) { + int idx = u + v * U; + if (!valid[idx]) continue; + + LiquidTopKey key = keys[idx]; + + int w = 1; + while (u + w < U && valid[idx + w] && + keys[idx + w] == key) { + w++; + } + + int h = 1; + bool done = false; + while (v + h < V && !done) { + for (int k = 0; k < w; k++) { + int idx2 = (u + k) + (v + h) * U; + if (!valid[idx2] || !(keys[idx2] == key)) { + done = true; + break; + } + } + if (!done) h++; + } + + for (int dv = 0; dv < h; dv++) { + for (int du = 0; du < w; du++) { + valid[(u + du) + (v + dv) * U] = 0; + } + } + + greedyLiquidMergedQuads++; + greedyLiquidTilesEmitted += (w * h); + + beginGreedy(); + tileRenderer->setApplyAmbienceOcclusion(false); + + t->setMipmapEnable(key.mipmap); + if (SharedConstants::TEXTURE_LIGHTING) { + t->tex2(key.lightKey); + } + + float rr = ((key.colorKey >> 16) & 0xff) / 255.0f; + float gg = ((key.colorKey >> 8) & 0xff) / 255.0f; + float bb = (key.colorKey & 0xff) / 255.0f; + t->color(rr, gg, bb); + + int baseX = x0 + u; + int baseY = y0 + ly; + int baseZ = z0 + v; + tileRenderer->setShape(0.0f, 0.0f, 0.0f, (float)w, + key.height, (float)h); + t->normal(0.0f, 1.0f, 0.0f); + tileRenderer->renderFaceUp(key.tile, baseX, baseY, + baseZ, key.tex); + + for (int dv = 0; dv < h; dv++) { + for (int du = 0; du < w; du++) { + int lx = u + du; + int lz = v + dv; + int maskIdx = (ly << 8) | (lz << 4) | lx; + greedyLiquidTop[maskIdx] = 1; + } + } + + faceRendered = true; + } + } + } + + return faceRendered; + }; + +#if defined(ENABLE_GREEDY_MESHING) + rendered |= greedyMeshLiquidTop(); + + if (currentLayer == 0) { + tileRenderer->setApplyAmbienceOcclusion(false); + rendered |= greedyMeshFace(0); + rendered |= greedyMeshFace(1); + rendered |= greedyMeshFace(2); + rendered |= greedyMeshFace(3); + rendered |= greedyMeshFace(4); + rendered |= greedyMeshFace(5); + } + endGreedy(); +#endif // 4J - changed loop order here to leave y as the innermost loop for // better cache performance @@ -470,17 +1254,6 @@ void Chunk::rebuild() { // int tileId = // region->getTile(x,y,z); if (tileId > 0) { - if (!started) { - started = true; - - glNewList(lists + currentLayer, GL_COMPILE); - glDepthMask(true); // 4J added - t->useCompactVertices(true); // 4J added - t->begin(); - t->offset((float)(-this->x), (float)(-this->y), - (float)(-this->z)); - } - Tile* tile = Tile::tiles[tileId]; if (currentLayer == 0 && tile->isEntityTile()) { std::shared_ptr et = @@ -495,6 +1268,19 @@ void Chunk::rebuild() { if (renderLayer != currentLayer) { renderNextLayer = true; } else if (renderLayer == currentLayer) { +#if defined(ENABLE_GREEDY_MESHING) + if (currentLayer == 0) { + int lx = x - x0; + int ly = y - y0; + int lz = z - z0; + if (greedyEligible[greedyIndex(lx, ly, lz)] != + 0) { + continue; + } + } +#endif + + beginNormal(); rendered |= tileRenderer->tesselateInWorld(tile, x, y, z); } @@ -503,12 +1289,14 @@ void Chunk::rebuild() { } } - if (started) { - t->end(); - bounds.addBounds(t->bounds); // 4J MGH - added +#if defined(ENABLE_GREEDY_MESHING) + endGreedy(); +#endif + endNormal(); + + if (listStarted) { glEndList(); t->useCompactVertices(false); // 4J added - t->offset(0, 0, 0); } else { rendered = false; } diff --git a/targets/minecraft/client/renderer/Tesselator.cpp b/targets/minecraft/client/renderer/Tesselator.cpp index b10c8fd3f..4ebb201d3 100644 --- a/targets/minecraft/client/renderer/Tesselator.cpp +++ b/targets/minecraft/client/renderer/Tesselator.cpp @@ -65,6 +65,7 @@ Tesselator::Tesselator(int size) { useCompactFormat360 = false; // 4J added mipmapEnable = true; // 4J added useProjectedTexturePixelShader = false; // 4J added + useTileUVEnabled = false; this->size = size; @@ -110,7 +111,9 @@ void Tesselator::end() { _array->data(), useCompactFormat360 ? C4JRender::VERTEX_TYPE_COMPRESSED - : C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1, + : (useTileUVEnabled + ? C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TILEUV + : C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1), useProjectedTexturePixelShader ? C4JRender::PIXEL_SHADER_TYPE_PROJECTION : C4JRender::PIXEL_SHADER_TYPE_STANDARD); @@ -138,7 +141,9 @@ void Tesselator::end() { RenderManager.DrawVertices( (C4JRender::ePrimitiveType)mode, vertexCount, _array->data(), - C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1, + useTileUVEnabled + ? C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TILEUV + : C4JRender::VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1, C4JRender::PIXEL_SHADER_TYPE_STANDARD); } } @@ -177,6 +182,8 @@ void Tesselator::useCompactVertices(bool enable) { useCompactFormat360 = enable; } +void Tesselator::useTileUV(bool enable) { useTileUVEnabled = enable; } + bool Tesselator::getCompactVertices() { return useCompactFormat360; } bool Tesselator::setMipmapEnable(bool enable) { @@ -600,4 +607,4 @@ void Tesselator::addOffset(float x, float y, float z) { zo += z; } -bool Tesselator::hasMaxVertices() { return false; } \ No newline at end of file +bool Tesselator::hasMaxVertices() { return false; } diff --git a/targets/minecraft/client/renderer/Tesselator.h b/targets/minecraft/client/renderer/Tesselator.h index e419a1599..1ee7a86f6 100644 --- a/targets/minecraft/client/renderer/Tesselator.h +++ b/targets/minecraft/client/renderer/Tesselator.h @@ -33,6 +33,7 @@ private: int p; bool useCompactFormat360; // 4J - added bool useProjectedTexturePixelShader; // 4J - added + bool useTileUVEnabled; public: int count; @@ -125,6 +126,7 @@ public: void useCompactVertices(bool enable); // 4J added bool getCompactVertices(); // AP added void useProjectedTexture(bool enable); // 4J added + void useTileUV(bool enable); void tex(float u, float v); void tex2(int tex2); // 4J - change brought forward from 1.8.2 void color(float r, float g, float b); @@ -143,4 +145,4 @@ public: bool setMipmapEnable(bool enable); // 4J added bool hasMaxVertices(); // 4J Added -}; \ No newline at end of file +}; diff --git a/targets/minecraft/client/renderer/TileRenderer.cpp b/targets/minecraft/client/renderer/TileRenderer.cpp index cb4390a3a..9848cb32d 100644 --- a/targets/minecraft/client/renderer/TileRenderer.cpp +++ b/targets/minecraft/client/renderer/TileRenderer.cpp @@ -9,13 +9,12 @@ #include #include -#include "platform/sdl2/Render.h" #include "EntityTileRenderer.h" #include "GameRenderer.h" +#include "Tesselator.h" #include "app/common/App_enums.h" #include "app/common/src/Colours/ColourTable.h" #include "app/include/FrameProfiler.h" -#include "Tesselator.h" #include "minecraft/Direction.h" #include "minecraft/Facing.h" #include "minecraft/SharedConstants.h" @@ -65,6 +64,7 @@ #include "minecraft/world/level/tile/piston/PistonBaseTile.h" #include "minecraft/world/level/tile/piston/PistonExtensionTile.h" #include "minecraft/world/phys/Vec3.h" +#include "platform/sdl2/Render.h" bool TileRenderer::fancy = true; @@ -75,6 +75,7 @@ void TileRenderer::_init() { xFlipTexture = false; noCulling = false; applyAmbienceOcclusion = false; + waterTopSkipMask = nullptr; setColor = true; northFlip = FLIP_NONE; southFlip = FLIP_NONE; @@ -236,6 +237,40 @@ void TileRenderer::clearFixedTexture() { this->fixedTexture = nullptr; } bool TileRenderer::hasFixedTexture() { return fixedTexture != nullptr; } +void TileRenderer::setApplyAmbienceOcclusion(bool enabled) { + applyAmbienceOcclusion = enabled; +} + +void TileRenderer::setWaterTopSkipMask(const uint8_t* mask) { + waterTopSkipMask = mask; +} + +void TileRenderer::setGreedyAO(float baseR, float baseG, float baseB, float ao1, + float ao2, float ao3, float ao4, int tex2) { + applyAmbienceOcclusion = true; + + c1r = baseR * ao1; + c1g = baseG * ao1; + c1b = baseB * ao1; + + c2r = baseR * ao2; + c2g = baseG * ao2; + c2b = baseB * ao2; + + c3r = baseR * ao3; + c3g = baseG * ao3; + c3b = baseB * ao3; + + c4r = baseR * ao4; + c4g = baseG * ao4; + c4b = baseB * ao4; + + tc1 = tex2; + tc2 = tex2; + tc3 = tex2; + tc4 = tex2; +} + void TileRenderer::setShape(float x0, float y0, float z0, float x1, float y1, float z1) { if (!fixedShape) { @@ -4569,6 +4604,17 @@ bool TileRenderer::tesselateWaterInWorld(Tile* tt, int x, int y, int z) { float g = (col >> 8 & 0xff) / 255.0f; float b = (col & 0xff) / 255.0f; bool up = tt->shouldRenderFace(level, x, y + 1, z, 1); + if (waterTopSkipMask) { + int lx = x - xMin; + int ly = y - yMin; + int lz = z - zMin; + if ((unsigned)lx < 16 && (unsigned)ly < 16 && (unsigned)lz < 16) { + int idx = (ly << 8) | (lz << 4) | lx; + if (waterTopSkipMask[idx]) { + up = false; + } + } + } bool down = tt->shouldRenderFace(level, x, y - 1, z, 0); bool dirs[4]; dirs[0] = tt->shouldRenderFace(level, x, y, z - 1, 2); @@ -4776,6 +4822,10 @@ bool TileRenderer::tesselateWaterInWorld(Tile* tt, int x, int y, int z) { return changed; } +float TileRenderer::getWaterHeightAt(int x, int y, int z, Material* m) { + return getWaterHeight(x, y, z, m); +} + float TileRenderer::getWaterHeight(int x, int y, int z, Material* m) { int count = 0; float h = 0; diff --git a/targets/minecraft/client/renderer/TileRenderer.h b/targets/minecraft/client/renderer/TileRenderer.h index ab0d34771..b17fd9b26 100644 --- a/targets/minecraft/client/renderer/TileRenderer.h +++ b/targets/minecraft/client/renderer/TileRenderer.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -39,6 +40,7 @@ private: Icon* fixedTexture; bool xFlipTexture; bool noCulling; + const uint8_t* waterTopSkipMask; public: static bool fancy; @@ -81,6 +83,10 @@ public: void setFixedTexture(Icon* fixedTexture); void clearFixedTexture(); bool hasFixedTexture(); + void setApplyAmbienceOcclusion(bool enabled); + void setWaterTopSkipMask(const uint8_t* mask); + void setGreedyAO(float baseR, float baseG, float baseB, float ao1, + float ao2, float ao3, float ao4, int tex2); void setShape(float x0, float y0, float z0, float x1, float y1, float z1); void setShape(Tile* tt); void setFixedShape(float x0, float y0, float z0, float x1, float y1, @@ -188,6 +194,7 @@ public: void tesselateRowTexture(Tile* tt, int data, float x, float y, float z); bool tesselateWaterInWorld(Tile* tt, int x, int y, int z); + float getWaterHeightAt(int x, int y, int z, Material* m); private: float getWaterHeight(int x, int y, int z, Material* m); diff --git a/targets/platform/IPlatformRenderer.h b/targets/platform/IPlatformRenderer.h index 5570e87a2..fa3a95cc5 100644 --- a/targets/platform/IPlatformRenderer.h +++ b/targets/platform/IPlatformRenderer.h @@ -11,6 +11,7 @@ public: VERTEX_TYPE_COMPRESSED, VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_LIT, VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TEXGEN, + VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TILEUV, VERTEX_TYPE_COUNT }; diff --git a/targets/platform/sdl2/Render.cpp b/targets/platform/sdl2/Render.cpp index 0e570dc1d..ae5f4a945 100644 --- a/targets/platform/sdl2/Render.cpp +++ b/targets/platform/sdl2/Render.cpp @@ -196,8 +196,7 @@ struct ShaderUniforms { GLint uFogDensity = -1, uFogColor = -1, uFogEnable = -1; GLint uLMTransform = -1, uUseLightmap = -1, uAlphaRef = -1; GLint uTex0 = -1, uTex1 = -1, uGlobalLM = -1; - GLint uUseTexture = -1; - GLint uInvGamma = -1; + GLint uUseTexture = -1, uUseTileUV = -1, uInvGamma = -1; GLint uChunkOffset = -1; void build(const char* vs, const char* fs) { @@ -233,6 +232,7 @@ struct ShaderUniforms { L(uTex1); L(uGlobalLM); L(uUseTexture); + L(uUseTileUV); L(uInvGamma); L(uChunkOffset); #undef L @@ -380,8 +380,8 @@ static void glShadowSetDepthTest(bool e) { } static void glShadowSetBlendFunc(GLint s, GLint d) { - if (!(s_gl_shadow_mask & SHADOW_BLEND_FUNC) || - s_gl_state.blendSrc != s || s_gl_state.blendDst != d) { + if (!(s_gl_shadow_mask & SHADOW_BLEND_FUNC) || s_gl_state.blendSrc != s || + s_gl_state.blendDst != d) { ::glBlendFunc(s, d); s_gl_state.blendSrc = s; s_gl_state.blendDst = d; @@ -390,8 +390,7 @@ static void glShadowSetBlendFunc(GLint s, GLint d) { } static void glShadowSetDepthMask(GLboolean e) { - if (!(s_gl_shadow_mask & SHADOW_DEPTH_MASK) || - s_gl_state.depthMask != e) { + if (!(s_gl_shadow_mask & SHADOW_DEPTH_MASK) || s_gl_state.depthMask != e) { ::glDepthMask(e); s_gl_state.depthMask = e; s_gl_shadow_mask |= SHADOW_DEPTH_MASK; @@ -502,8 +501,7 @@ static void pushRenderState() { glUniform1f(s_shader.uFogStart, s_rs.fogStart); glUniform1f(s_shader.uFogEnd, s_rs.fogEnd); glUniform1f(s_shader.uFogDensity, s_rs.fogDensity); - glUniform4fv(s_shader.uFogColor, 1, - glm::value_ptr(s_rs.fogColor)); + glUniform4fv(s_shader.uFogColor, 1, glm::value_ptr(s_rs.fogColor)); glUniform1i(s_shader.uFogEnable, s_rs.fogEnable ? 1 : 0); } if (s_rs_dirty_mask & DIRTY_TEXTURE) { @@ -515,11 +513,9 @@ static void pushRenderState() { if (s_rs_dirty_mask & DIRTY_GAMMA) glUniform1f(s_shader.uInvGamma, 1.0f / s_rs.gamma); if (s_rs_dirty_mask & DIRTY_LMT) - glUniform4fv(s_shader.uLMTransform, 1, - glm::value_ptr(s_rs.lmt)); + glUniform4fv(s_shader.uLMTransform, 1, glm::value_ptr(s_rs.lmt)); if (s_rs_dirty_mask & DIRTY_GLOBAL_LM) - glUniform2fv(s_shader.uGlobalLM, 1, - glm::value_ptr(s_rs.globalLM)); + glUniform2fv(s_shader.uGlobalLM, 1, glm::value_ptr(s_rs.globalLM)); s_rs_dirty_mask = 0; } flushMatrices(); @@ -556,6 +552,7 @@ struct ChunkDrawCall { GLenum prim; GLint first; GLsizei count; + bool useTileUV; }; struct ChunkBuffer { @@ -893,13 +890,17 @@ void C4JRender::DrawVertices(ePrimitiveType ptype, int count, void* dataIn, int first = (int)(s_recVerts.size() / stride); s_recVerts.insert(s_recVerts.end(), (const uint8_t*)dataIn, (const uint8_t*)dataIn + bytes); - s_recDraws.push_back({glMode, first, (GLsizei)count}); + bool useTileUV = (vType == VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TILEUV); + s_recDraws.push_back({glMode, first, (GLsizei)count, useTileUV}); return; } pthread_mutex_lock(&s_glCallMtx); pushRenderState(); + glUniform1i(s_shader.uUseTileUV, + (vType == VERTEX_TYPE_PF3_TF2_CB4_NB4_XW1_TILEUV) ? 1 : 0); + glBindVertexArray(s_sVAO_std); glBindBuffer(GL_ARRAY_BUFFER, s_sVBO_std); @@ -1019,7 +1020,10 @@ bool C4JRender::CBuffCall(int index, bool) { pushRenderState(); glBindVertexArray(cb.vao); - for (const auto& dc : cb.draws) glDrawArrays(dc.prim, dc.first, dc.count); + for (const auto& dc : cb.draws) { + glUniform1i(s_shader.uUseTileUV, dc.useTileUV ? 1 : 0); + glDrawArrays(dc.prim, dc.first, dc.count); + } glBindVertexArray(0); pthread_mutex_unlock(&s_glCallMtx); @@ -1095,7 +1099,7 @@ void C4JRender::Set_matrixDirty() { s_boundProgram = 0; s_rs_dirty_mask = 0xFFFFFFFF; s_gl_shadow_mask = 0; - s_normalMatDirty = true; // normal matrix dirt after iggy reset + s_normalMatDirty = true; // normal matrix dirt after iggy reset s_matDirty = true; s_chunkOffsetValid = false; if (s_shader.prog) { @@ -1132,9 +1136,7 @@ void C4JRender::StateSetDepthMask(bool e) { glShadowSetDepthMask(e ? GL_TRUE : GL_FALSE); } void C4JRender::StateSetBlendEnable(bool e) { glShadowSetBlend(e); } -void C4JRender::StateSetBlendFunc(int s, int d) { - glShadowSetBlendFunc(s, d); -} +void C4JRender::StateSetBlendFunc(int s, int d) { glShadowSetBlendFunc(s, d); } void C4JRender::StateSetDepthFunc(int f) { ::glDepthFunc(f); } void C4JRender::StateSetFaceCull(bool e) { glShadowSetCull(e); } void C4JRender::StateSetFaceCullCW(bool e) { @@ -1181,7 +1183,10 @@ void C4JRender::StateSetFogEnable(bool e) { } } void C4JRender::StateSetFogMode(int mode) { - int v = (mode == GL_LINEAR) ? 1 : (mode == GL_EXP) ? 2 : (mode == 0x0801) ? 3 : 0; + int v = (mode == GL_LINEAR) ? 1 + : (mode == GL_EXP) ? 2 + : (mode == 0x0801) ? 3 + : 0; if (s_rs.fogMode != v) { s_rs.fogMode = v; markDirty(DIRTY_FOG); @@ -1303,9 +1308,9 @@ void C4JRender::TextureBindVertex(int idx, bool scaleLight) { s_rs.useLightmap = true; markDirty(DIRTY_TEXTURE); } - glm::vec4 newLmt = - scaleLight ? glm::vec4{1.f, 1.f, 8.f / 256.f, 8.f / 256.f} - : glm::vec4{1.f, 1.f, 0.f, 0.f}; + glm::vec4 newLmt = scaleLight + ? glm::vec4{1.f, 1.f, 8.f / 256.f, 8.f / 256.f} + : glm::vec4{1.f, 1.f, 0.f, 0.f}; if (s_rs.lmt != newLmt) { s_rs.lmt = newLmt; markDirty(DIRTY_LMT); diff --git a/targets/platform/sdl2/shaders/fragment.frag b/targets/platform/sdl2/shaders/fragment.frag index fc4640a4c..11efa3751 100644 --- a/targets/platform/sdl2/shaders/fragment.frag +++ b/targets/platform/sdl2/shaders/fragment.frag @@ -1,22 +1,113 @@ -R"GLSL( +R "GLSL( #version 330 core uniform sampler2D uTex0; uniform sampler2D uTex1; -uniform int uUseTexture; -uniform int uUseLightmap; +uniform int uUseTexture; +uniform int uUseTileUV; +uniform int uUseLightmap; uniform float uAlphaRef; -uniform vec4 uFogColor; -uniform int uFogEnable; +uniform vec4 uFogColor; +uniform int uFogEnable; uniform float uInvGamma; -in vec2 vUV0; -in vec2 vUV1; -in vec4 vColor; -in float vFogFactor; -out vec4 oColor; +in vec2 vUV0; +in vec2 vUV1; +in vec4 vColor; +in vec3 vWorldPos; +in float vFogFactor; +out vec4 oColor; void main() { - vec4 texColor = (uUseTexture != 0) ? texture(uTex0, vUV0) : vec4(1.0); + vec2 uv = vUV0; + vec2 dUVdx = vec2(0.0); + vec2 dUVdy = vec2(0.0); + bool useGrad = false; + bool disableMipmap = false; + + if (uUseTileUV != 0) { + vec2 baseUV = vUV0; + if (baseUV.x > 1.0) { + baseUV.x -= 1.0; + disableMipmap = true; + } + if (baseUV.y > 1.0) { + baseUV.y -= 1.0; + disableMipmap = true; + } + + vec2 atlasSize = vec2(textureSize(uTex0, 0)); + float tileSize = 16.0; + vec2 cell = floor(baseUV * atlasSize / tileSize); + vec2 uv0 = cell * tileSize / atlasSize; + vec2 cellSize = vec2(tileSize) / atlasSize; + + vec3 dpdx = dFdx(vWorldPos); + vec3 dpdy = dFdy(vWorldPos); + vec3 n = normalize(cross(dpdx, dpdy)); + + vec3 uAxis; + vec3 vAxis; + if (abs(n.y) > abs(n.x) && abs(n.y) > abs(n.z)) { + uAxis = vec3(1.0, 0.0, 0.0); + vAxis = vec3(0.0, 0.0, 1.0); + } else if (abs(n.z) > abs(n.x)) { + uAxis = vec3(1.0, 0.0, 0.0); + vAxis = vec3(0.0, 1.0, 0.0); + } else { + uAxis = vec3(0.0, 0.0, 1.0); + vAxis = vec3(0.0, 1.0, 0.0); + } + + float du = dot(vWorldPos, uAxis); + float dv = dot(vWorldPos, vAxis); + + vec2 gradU = vec2(dot(dpdx, uAxis), dot(dpdy, uAxis)); + vec2 gradV = vec2(dot(dpdx, vAxis), dot(dpdy, vAxis)); + vec2 gradUx = vec2(dFdx(baseUV.x), dFdy(baseUV.x)); + vec2 gradUy = vec2(dFdx(baseUV.y), dFdy(baseUV.y)); + + float corrUx = dot(gradUx, gradU); + float corrVx = dot(gradUx, gradV); + bool swap = abs(corrVx) > abs(corrUx); + + float signU = swap ? corrVx : corrUx; + float corrUy = dot(gradUy, gradU); + float corrVy = dot(gradUy, gradV); + float signV = swap ? corrUy : corrVy; + + float tu = swap ? dv : du; + float tv = swap ? du : dv; + + float dtu_dx = swap ? dot(dpdx, vAxis) : dot(dpdx, uAxis); + float dtu_dy = swap ? dot(dpdy, vAxis) : dot(dpdy, uAxis); + float dtv_dx = swap ? dot(dpdx, uAxis) : dot(dpdx, vAxis); + float dtv_dy = swap ? dot(dpdy, uAxis) : dot(dpdy, vAxis); + + if (signU < 0.0) { + tu = -tu; + dtu_dx = -dtu_dx; + dtu_dy = -dtu_dy; + } + if (signV < 0.0) { + tv = -tv; + dtv_dx = -dtv_dx; + dtv_dy = -dtv_dy; + } + + uv = uv0 + fract(vec2(tu, tv)) * cellSize; + dUVdx = vec2(dtu_dx, dtv_dx) * cellSize; + dUVdy = vec2(dtu_dy, dtv_dy) * cellSize; + useGrad = true; + } + + vec4 texColor = vec4(1.0); + if (uUseTexture != 0) { + if (useGrad) { + texColor = disableMipmap ? textureLod(uTex0, uv, 0.0) : textureGrad(uTex0, uv, dUVdx, dUVdy); + } else { + texColor = texture(uTex0, uv); + } + } vec4 c = texColor * vColor; if (c.a < uAlphaRef) discard; if (uUseLightmap != 0) c.rgb *= texture(uTex1, vUV1).rgb; @@ -26,4 +117,5 @@ void main() { oColor = c; } -)GLSL"; +) GLSL "; + diff --git a/targets/platform/sdl2/shaders/fragment_es.frag b/targets/platform/sdl2/shaders/fragment_es.frag index bd8b019fc..9f628e558 100644 --- a/targets/platform/sdl2/shaders/fragment_es.frag +++ b/targets/platform/sdl2/shaders/fragment_es.frag @@ -1,25 +1,116 @@ -R"GLSL( +R "GLSL( #version 300 es precision mediump float; precision mediump int; uniform sampler2D uTex0; uniform sampler2D uTex1; -uniform int uUseTexture; -uniform int uUseLightmap; +uniform int uUseTexture; +uniform int uUseTileUV; +uniform int uUseLightmap; uniform float uAlphaRef; -uniform vec4 uFogColor; -uniform int uFogEnable; +uniform vec4 uFogColor; +uniform int uFogEnable; uniform float uInvGamma; -in vec2 vUV0; -in vec2 vUV1; -in vec4 vColor; -in float vFogFactor; -out vec4 oColor; +in vec2 vUV0; +in vec2 vUV1; +in vec4 vColor; +in vec3 vWorldPos; +in float vFogFactor; +out vec4 oColor; void main() { - vec4 texColor = (uUseTexture != 0) ? texture(uTex0, vUV0) : vec4(1.0); + vec2 uv = vUV0; + vec2 dUVdx = vec2(0.0); + vec2 dUVdy = vec2(0.0); + bool useGrad = false; + bool disableMipmap = false; + + if (uUseTileUV != 0) { + vec2 baseUV = vUV0; + if (baseUV.x > 1.0) { + baseUV.x -= 1.0; + disableMipmap = true; + } + if (baseUV.y > 1.0) { + baseUV.y -= 1.0; + disableMipmap = true; + } + + vec2 atlasSize = vec2(textureSize(uTex0, 0)); + float tileSize = 16.0; + vec2 cell = floor(baseUV * atlasSize / tileSize); + vec2 uv0 = cell * tileSize / atlasSize; + vec2 cellSize = vec2(tileSize) / atlasSize; + + vec3 dpdx = dFdx(vWorldPos); + vec3 dpdy = dFdy(vWorldPos); + vec3 n = normalize(cross(dpdx, dpdy)); + + vec3 uAxis; + vec3 vAxis; + if (abs(n.y) > abs(n.x) && abs(n.y) > abs(n.z)) { + uAxis = vec3(1.0, 0.0, 0.0); + vAxis = vec3(0.0, 0.0, 1.0); + } else if (abs(n.z) > abs(n.x)) { + uAxis = vec3(1.0, 0.0, 0.0); + vAxis = vec3(0.0, 1.0, 0.0); + } else { + uAxis = vec3(0.0, 0.0, 1.0); + vAxis = vec3(0.0, 1.0, 0.0); + } + + float du = dot(vWorldPos, uAxis); + float dv = dot(vWorldPos, vAxis); + + vec2 gradU = vec2(dot(dpdx, uAxis), dot(dpdy, uAxis)); + vec2 gradV = vec2(dot(dpdx, vAxis), dot(dpdy, vAxis)); + vec2 gradUx = vec2(dFdx(baseUV.x), dFdy(baseUV.x)); + vec2 gradUy = vec2(dFdx(baseUV.y), dFdy(baseUV.y)); + + float corrUx = dot(gradUx, gradU); + float corrVx = dot(gradUx, gradV); + bool swap = abs(corrVx) > abs(corrUx); + + float signU = swap ? corrVx : corrUx; + float corrUy = dot(gradUy, gradU); + float corrVy = dot(gradUy, gradV); + float signV = swap ? corrUy : corrVy; + + float tu = swap ? dv : du; + float tv = swap ? du : dv; + + float dtu_dx = swap ? dot(dpdx, vAxis) : dot(dpdx, uAxis); + float dtu_dy = swap ? dot(dpdy, vAxis) : dot(dpdy, uAxis); + float dtv_dx = swap ? dot(dpdx, uAxis) : dot(dpdx, vAxis); + float dtv_dy = swap ? dot(dpdy, uAxis) : dot(dpdy, vAxis); + + if (signU < 0.0) { + tu = -tu; + dtu_dx = -dtu_dx; + dtu_dy = -dtu_dy; + } + if (signV < 0.0) { + tv = -tv; + dtv_dx = -dtv_dx; + dtv_dy = -dtv_dy; + } + + uv = uv0 + fract(vec2(tu, tv)) * cellSize; + dUVdx = vec2(dtu_dx, dtv_dx) * cellSize; + dUVdy = vec2(dtu_dy, dtv_dy) * cellSize; + useGrad = true; + } + + vec4 texColor = vec4(1.0); + if (uUseTexture != 0) { + if (useGrad) { + texColor = disableMipmap ? textureLod(uTex0, uv, 0.0) : textureGrad(uTex0, uv, dUVdx, dUVdy); + } else { + texColor = texture(uTex0, uv); + } + } vec4 c = texColor * vColor; if (c.a < uAlphaRef) discard; if (uUseLightmap != 0) c.rgb *= texture(uTex1, vUV1).rgb; @@ -29,4 +120,5 @@ void main() { oColor = c; } -)GLSL"; +) GLSL "; + diff --git a/targets/platform/sdl2/shaders/vertex.vert b/targets/platform/sdl2/shaders/vertex.vert index aa4978dd7..bd02e7eb8 100644 --- a/targets/platform/sdl2/shaders/vertex.vert +++ b/targets/platform/sdl2/shaders/vertex.vert @@ -1,40 +1,43 @@ -R"GLSL( +R "GLSL( #version 330 core -layout(location=0) in vec3 aPos; -layout(location=1) in vec2 aUV0; -layout(location=2) in vec4 aColor; -layout(location=3) in vec3 aNormal; -layout(location=4) in ivec2 aLMraw; +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec2 aUV0; +layout(location = 2) in vec4 aColor; +layout(location = 3) in vec3 aNormal; +layout(location = 4) in ivec2 aLMraw; -uniform mat4 uMVP; -uniform mat4 uMV; -uniform mat3 uNormalMatrix; +uniform mat4 uMVP; +uniform mat4 uMV; +uniform mat3 uNormalMatrix; uniform float uNormalSign; -uniform mat4 uTexMat0; -uniform vec4 uBaseColor; -uniform int uLighting; -uniform vec3 uLight0Dir; -uniform vec3 uLight1Dir; -uniform vec3 uLightDiffuse; -uniform vec3 uLightAmbient; -uniform vec3 uChunkOffset; -uniform int uFogMode; +uniform mat4 uTexMat0; +uniform vec4 uBaseColor; +uniform int uLighting; +uniform vec3 uLight0Dir; +uniform vec3 uLight1Dir; +uniform vec3 uLightDiffuse; +uniform vec3 uLightAmbient; +uniform vec3 uChunkOffset; +uniform int uFogMode; uniform float uFogStart; uniform float uFogEnd; uniform float uFogDensity; -uniform vec4 uLMTransform; -uniform vec2 uGlobalLM; +uniform vec4 uLMTransform; +uniform vec2 uGlobalLM; -out vec2 vUV0; -out vec2 vUV1; -out vec4 vColor; +out vec2 vUV0; +out vec2 vUV1; +out vec4 vColor; +out vec3 vWorldPos; out float vFogFactor; void main() { - vec4 aPos4 = vec4(aPos + uChunkOffset, 1.0); - vec4 eyePos = uMV * aPos4; - gl_Position = uMVP * aPos4; - vUV0 = (uTexMat0 * vec4(aUV0, 0.0, 1.0)).xy; + vec3 worldPos = aPos + uChunkOffset; + vec4 aPos4 = vec4(worldPos, 1.0); + vec4 eyePos = uMV * aPos4; + gl_Position = uMVP * aPos4; + vUV0 = (uTexMat0 * vec4(aUV0, 0.0, 1.0)).xy; + vWorldPos = worldPos; vec2 lm = (aLMraw.x <= -500) ? uGlobalLM : vec2(aLMraw); vUV1 = (lm / 256.0) * uLMTransform.xy + uLMTransform.zw; @@ -52,9 +55,13 @@ void main() { } float eDist = length(eyePos.xyz); - if (uFogMode == 1) vFogFactor = clamp((uFogEnd - eDist) / max(uFogEnd - uFogStart, 1e-4), 0.0, 1.0); + if (uFogMode == 1) vFogFactor = clamp((uFogEnd - eDist) / max(uFogEnd - uFogStart, 1e-4), 0.0, 1.0); else if (uFogMode == 2) vFogFactor = clamp(exp(-uFogDensity * eDist), 0.0, 1.0); - else if (uFogMode == 3) { float d = uFogDensity * eDist; vFogFactor = clamp(exp(-d*d), 0.0, 1.0); } - else vFogFactor = 1.0; + else if (uFogMode == 3) { + float d = uFogDensity * eDist; + vFogFactor = clamp(exp(-d * d), 0.0, 1.0); + } + else vFogFactor = 1.0; } -)GLSL"; \ No newline at end of file +) GLSL "; + diff --git a/targets/platform/sdl2/shaders/vertex_es.vert b/targets/platform/sdl2/shaders/vertex_es.vert index d2245c424..ec860e327 100644 --- a/targets/platform/sdl2/shaders/vertex_es.vert +++ b/targets/platform/sdl2/shaders/vertex_es.vert @@ -1,43 +1,46 @@ -R"GLSL( +R "GLSL( #version 300 es precision highp float; precision highp int; -layout(location=0) in vec3 aPos; -layout(location=1) in vec2 aUV0; -layout(location=2) in vec4 aColor; -layout(location=3) in vec3 aNormal; -layout(location=4) in ivec2 aLMraw; +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec2 aUV0; +layout(location = 2) in vec4 aColor; +layout(location = 3) in vec3 aNormal; +layout(location = 4) in ivec2 aLMraw; -uniform mat4 uMVP; -uniform mat4 uMV; -uniform mat3 uNormalMatrix; +uniform mat4 uMVP; +uniform mat4 uMV; +uniform mat3 uNormalMatrix; uniform float uNormalSign; -uniform mat4 uTexMat0; -uniform vec4 uBaseColor; -uniform int uLighting; -uniform vec3 uLight0Dir; -uniform vec3 uLight1Dir; -uniform vec3 uLightDiffuse; -uniform vec3 uLightAmbient; -uniform vec3 uChunkOffset; -uniform int uFogMode; +uniform mat4 uTexMat0; +uniform vec4 uBaseColor; +uniform int uLighting; +uniform vec3 uLight0Dir; +uniform vec3 uLight1Dir; +uniform vec3 uLightDiffuse; +uniform vec3 uLightAmbient; +uniform vec3 uChunkOffset; +uniform int uFogMode; uniform float uFogStart; uniform float uFogEnd; uniform float uFogDensity; -uniform vec4 uLMTransform; -uniform vec2 uGlobalLM; +uniform vec4 uLMTransform; +uniform vec2 uGlobalLM; -out vec2 vUV0; -out vec2 vUV1; -out vec4 vColor; +out vec2 vUV0; +out vec2 vUV1; +out vec4 vColor; +out vec3 vWorldPos; out float vFogFactor; void main() { - vec4 aPos4 = vec4(aPos + uChunkOffset, 1.0); - vec4 eyePos = uMV * aPos4; - gl_Position = uMVP * aPos4; - vUV0 = (uTexMat0 * vec4(aUV0, 0.0, 1.0)).xy; + vec3 worldPos = aPos + uChunkOffset; + vec4 aPos4 = vec4(worldPos, 1.0); + vec4 eyePos = uMV * aPos4; + gl_Position = uMVP * aPos4; + vUV0 = (uTexMat0 * vec4(aUV0, 0.0, 1.0)).xy; + vWorldPos = worldPos; vec2 lm = (aLMraw.x <= -500) ? uGlobalLM : vec2(aLMraw); vUV1 = (lm / 256.0) * uLMTransform.xy + uLMTransform.zw; @@ -55,9 +58,13 @@ void main() { } float eDist = length(eyePos.xyz); - if (uFogMode == 1) vFogFactor = clamp((uFogEnd - eDist) / max(uFogEnd - uFogStart, 1e-4), 0.0, 1.0); + if (uFogMode == 1) vFogFactor = clamp((uFogEnd - eDist) / max(uFogEnd - uFogStart, 1e-4), 0.0, 1.0); else if (uFogMode == 2) vFogFactor = clamp(exp(-uFogDensity * eDist), 0.0, 1.0); - else if (uFogMode == 3) { float d = uFogDensity * eDist; vFogFactor = clamp(exp(-d*d), 0.0, 1.0); } - else vFogFactor = 1.0; + else if (uFogMode == 3) { + float d = uFogDensity * eDist; + vFogFactor = clamp(exp(-d * d), 0.0, 1.0); + } + else vFogFactor = 1.0; } -)GLSL"; \ No newline at end of file +) GLSL "; +