From 4bb12c872f324d192127b31fdb18cd6efb9867bc Mon Sep 17 00:00:00 2001 From: Pedro Gimeno Date: Sat, 22 Feb 2020 20:51:16 +0100 Subject: [PATCH] Precision improvements (Part 1) Improve rendering precision by subtracting the camera position from the models' position, then temporarily setting the camera to (0, 0, 0) for rendering. This keeps GL coordinates in a low range. --- client/include/world/ClientPlayer.hpp | 2 +- client/include/world/ClientWorld.hpp | 4 +++ client/source/hud/BlockCursor.cpp | 4 ++- client/source/states/GameState.cpp | 1 + client/source/world/ClientWorld.cpp | 44 ++++++++++++++++++++------- common/include/core/EngineConfig.hpp | 2 ++ 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/client/include/world/ClientPlayer.hpp b/client/include/world/ClientPlayer.hpp index 613bd910d..7e2855688 100644 --- a/client/include/world/ClientPlayer.hpp +++ b/client/include/world/ClientPlayer.hpp @@ -68,7 +68,7 @@ class ClientPlayer : public Player { void setPosition(float x, float y, float z); - const gk::Camera &camera() { return m_camera; } + gk::Camera &camera() { return m_camera; } private: void testPoint(const ClientWorld &world, float x, float y, float z, glm::vec3 &speed); diff --git a/client/include/world/ClientWorld.hpp b/client/include/world/ClientWorld.hpp index 89861c2b3..0cf8bf2f3 100644 --- a/client/include/world/ClientWorld.hpp +++ b/client/include/world/ClientWorld.hpp @@ -25,6 +25,8 @@ #include +#include + #include "ClientChunk.hpp" #include "Network.hpp" #include "World.hpp" @@ -48,6 +50,7 @@ class ClientWorld : public World, public gk::Drawable { Chunk *getChunk(int cx, int cy, int cz) const override; void setClient(ClientCommandHandler &client) { m_client = &client; } + void setCamera(gk::Camera &camera) { m_camera = &camera; } std::size_t loadedChunkCount() const { return m_chunks.size(); } @@ -61,6 +64,7 @@ class ClientWorld : public World, public gk::Drawable { TextureAtlas &m_textureAtlas; ClientCommandHandler *m_client = nullptr; + gk::Camera *m_camera = nullptr; mutable float m_ud = 1000; mutable s32 m_ux; diff --git a/client/source/hud/BlockCursor.cpp b/client/source/hud/BlockCursor.cpp index 2038e4622..d9e8d9ed3 100644 --- a/client/source/hud/BlockCursor.cpp +++ b/client/source/hud/BlockCursor.cpp @@ -241,7 +241,9 @@ void BlockCursor::draw(gk::RenderTarget &target, gk::RenderStates states) const glCheck(glDisable(GL_POLYGON_OFFSET_FILL)); glCheck(glDisable(GL_CULL_FACE)); - states.transform.translate(m_selectedBlock.x, m_selectedBlock.y, m_selectedBlock.z); + // Subtract the camera position - see comment in ClientWorld::draw() + gk::Vector3d cameraPosition = m_player.camera().getPosition(); + states.transform.translate(m_selectedBlock.x - cameraPosition.x, m_selectedBlock.y - cameraPosition.y, m_selectedBlock.z - cameraPosition.z); target.draw(m_vbo, GL_LINES, 0, 24, states); diff --git a/client/source/states/GameState.cpp b/client/source/states/GameState.cpp index 7e0453d55..8e8643cdc 100644 --- a/client/source/states/GameState.cpp +++ b/client/source/states/GameState.cpp @@ -64,6 +64,7 @@ GameState::GameState(const std::string &host, int port) { m_world.setClient(m_clientCommandHandler); m_player.setClientID(m_client.id()); + m_world.setCamera(m_player.camera()); } void GameState::onEvent(const SDL_Event &event) { diff --git a/client/source/world/ClientWorld.cpp b/client/source/world/ClientWorld.cpp index 0348d39be..f1e5be7d2 100644 --- a/client/source/world/ClientWorld.cpp +++ b/client/source/world/ClientWorld.cpp @@ -21,6 +21,7 @@ * ===================================================================================== */ #include +#include #include #include @@ -62,7 +63,7 @@ void ClientWorld::update() { void ClientWorld::sendChunkRequests() { // If we have a chunk marked for initialization - if (m_ud < 1000) { + if (m_ud < 1000000.0) { ClientChunk *chunk = (ClientChunk *)getChunk(m_ux, m_uy, m_uz); if(chunk && !chunk->hasBeenRequested()) { // Send a chunk request to the server @@ -188,7 +189,7 @@ void ClientWorld::createChunkNeighbours(ClientChunk *chunk) { } void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const { - if (!target.getView()) { + if (!target.getView() || !m_camera) { DEBUG("ERROR: Trying to draw world without a camera"); return; } @@ -197,21 +198,40 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const states.shader->setUniform("u_renderDistance", Config::renderDistance * CHUNK_WIDTH); gk::Shader::bind(nullptr); - m_ud = 1000.0; + m_ud = 1000000.0; m_ux = 0; m_uy = 0; m_uz = 0; + // Changing the values sent to the GPU to double precision is suicidal, + // performance wise, if possible at all. Therefore we want to keep the + // GL rendering numbers in single precision format. But that introduces + // an issue at larger coordinates, because the precision of floats + // quickly degrades as the numbers grow, with a random wobbling being + // very noticeable at e.g. coordinates >= 65536 or so, and the waving + // leaves effect being very jerky in conparison with the effect near the + // origin. + // + // To gain rendering precision, we subtract the camera position from the + // coordinates of the models to be rendered, to make them all small in + // relation to the camera, prior to converting them to floats. Then the + // camera itself is moved to (0, 0, 0) for rendering purposes. Now the + // vertex coordinates passed to the renderer are all small, and single + // precision floats suffice for the drawing. + + gk::Vector3d cameraPos(m_camera->getPosition()); + m_camera->setPosition(0, 0, 0); // Temporarily move the camera to the origin + std::vector> chunks; for(auto &it : m_chunks) { - states.transform = glm::translate(glm::mat4(1.0f), - glm::vec3(it.second->x() * CHUNK_WIDTH, - it.second->y() * CHUNK_DEPTH, - it.second->z() * CHUNK_HEIGHT)); + gk::Transform tf = glm::translate(glm::mat4(1.0f), + glm::vec3(it.second->x() * CHUNK_WIDTH - cameraPos.x, + it.second->y() * CHUNK_DEPTH - cameraPos.y, + it.second->z() * CHUNK_HEIGHT - cameraPos.z)); // Is the chunk close enough? glm::vec4 center = target.getView()->getViewTransform().getMatrix() - * states.transform.getMatrix() + * tf.getMatrix() * glm::vec4(CHUNK_WIDTH / 2, CHUNK_DEPTH / 2, CHUNK_HEIGHT / 2, 1); // Nope, too far, don't render it @@ -223,7 +243,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const it.second->setTooFar(false); // Is this chunk's centre on the screen? - float d = glm::length(center); + float d = glm::length2(center); center.x /= center.w; center.y /= center.w; @@ -231,7 +251,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const // Our screen coordinates are +X right, +Y up, and for a right-handed // coordinate system, depth must be negative Z, so anything with a // positive Z is behind the camera. - if(center.z > CHUNK_HEIGHT / 2) { + if (center.z > CHUNK_MAXSIZE / 2) { continue; } @@ -255,7 +275,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const continue; } - chunks.emplace_back(it.second.get(), states.transform); + chunks.emplace_back(it.second.get(), tf); } for (u8 i = 0 ; i < ChunkBuilder::layers ; ++i) { @@ -264,5 +284,7 @@ void ClientWorld::draw(gk::RenderTarget &target, gk::RenderStates states) const it.first->drawLayer(target, states, i); } } + + m_camera->setPosition(cameraPos); // Restore the camera to its original position } diff --git a/common/include/core/EngineConfig.hpp b/common/include/core/EngineConfig.hpp index 450ea0e11..b15a67e0a 100644 --- a/common/include/core/EngineConfig.hpp +++ b/common/include/core/EngineConfig.hpp @@ -34,6 +34,8 @@ namespace { constexpr int CHUNK_DEPTH = 16; constexpr int CHUNK_HEIGHT = 32; + constexpr int CHUNK_MAXSIZE = CHUNK_WIDTH > CHUNK_DEPTH ? (CHUNK_HEIGHT > CHUNK_WIDTH ? CHUNK_HEIGHT : CHUNK_WIDTH) : (CHUNK_HEIGHT > CHUNK_DEPTH ? CHUNK_HEIGHT : CHUNK_DEPTH); + // Several parts of the code use & (CHUNK_xxx - 1) assuming they are powers of 2 static_assert((CHUNK_WIDTH & (CHUNK_WIDTH - 1)) == 0, "CHUNK_WIDTH is not a power of 2"); static_assert((CHUNK_DEPTH & (CHUNK_DEPTH - 1)) == 0, "CHUNK_DEPTH is not a power of 2");