Skip to content

Commit

Permalink
Precision improvements (Part 1)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Pedro Gimeno committed Feb 24, 2020
1 parent af1a641 commit 4bb12c8
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 13 deletions.
2 changes: 1 addition & 1 deletion client/include/world/ClientPlayer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions client/include/world/ClientWorld.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

#include <unordered_map>

#include <gk/gl/Camera.hpp>

#include "ClientChunk.hpp"
#include "Network.hpp"
#include "World.hpp"
Expand All @@ -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(); }

Expand All @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion client/source/hud/BlockCursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 1 addition & 0 deletions client/source/states/GameState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
44 changes: 33 additions & 11 deletions client/source/world/ClientWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* =====================================================================================
*/
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/norm.hpp>

#include <gk/gl/Shader.hpp>
#include <gk/resource/ResourceHandler.hpp>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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<std::pair<ClientChunk*, gk::Transform>> 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
Expand All @@ -223,15 +243,15 @@ 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;

// If it is behind the camera, don't bother drawing it.
// 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;
}

Expand All @@ -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) {
Expand All @@ -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
}

2 changes: 2 additions & 0 deletions common/include/core/EngineConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit 4bb12c8

Please sign in to comment.