Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Terrain Shading #28

Merged
merged 10 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,19 @@ git clone --recursive https://github.com/gue-ni/TerrainRenderer.git
cmake -DCMAKE_BUILD_TYPE=Release -B build
cmake --build build --config Release --parallel
```

## Digital Elevation Model

There are countless providers of satellite image tiles, but for the digital elevation model we have to provide the
data ourself. Download [ogd-10m-at.zip](https://gis.ktn.gv.at/OGD/Geographie_Planung/ogd-10m-at.zip) from
[data.gv.at](https://www.data.gv.at/katalog/dataset/b5de6975-417b-4320-afdb-eb2a9e2a1db).

Unzip the files and process the data with [gdal_translate](https://gdal.org/programs/gdal_translate.html) and [gdal2tiles.py](https://gdal.org/programs/gdal2tiles.html):

```bash
# convert from float32 to uint8 and scale values to [0, 255]
gdal_translate -ot Byte -scale 0 3795 0 255 dhm_at_lamb_10m_2018.tif grayscale-byte-fullsize.tif

# create tiles
gdal2tiles.py --resume --processes=2 --zoom=6-16 --tilesize=128 grayscale-byte-fullsize.tif tiles
```
21 changes: 19 additions & 2 deletions app/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,29 @@ void App::render_ui()
ImGui::Checkbox("Ray Intersect", &m_terrain.intersect_terrain);
ImGui::Checkbox("Debug View", &m_terrain.debug_view);
ImGui::Checkbox("Frustum Culling", &m_terrain.frustum_culling);
ImGui::Checkbox("Enable Shading", &m_terrain.shading);

ImGui::SliderFloat("Camera Speed", &m_speed, 10.0f, 5000.0f);
ImGui::SliderFloat("Fog Far", &m_terrain.fog_far, 100.0f, 100000.0f);
ImGui::SliderFloat("Fog Density", &m_terrain.fog_density, 0.0f, 10.0f);

ImGui::SliderFloat("Horizon", &m_terrain.max_horizon, 0.0f, 5000.0f);

ImGui::SliderFloat("Sun Azimuth", &m_terrain.sun_azimuth, 0.0f, 360.0f);
ImGui::SliderFloat("Sun Elevation", &m_terrain.sun_elevation, 0.0f, 90.0f);

ImGui::Checkbox("Manual Zoom", &m_terrain.manual_zoom);
if (m_terrain.manual_zoom) {
ImGui::SliderInt("Min Zoom", &m_terrain.min_zoom, zoom, 16);
ImGui::SliderInt("Max Zoom", &m_terrain.max_zoom, zoom, 16);
}

if (ImGui::CollapsingHeader("Atmosphere")) {
ImGui::SliderFloat("Fog Far", &m_terrain.fog_far, 100.0f, 100000.0f);
ImGui::SliderFloat("Fog Density", &m_terrain.fog_density, 0.0f, 10.0f);
}

if (ImGui::Button("Reload Shaders")) {
m_terrain.reload_shaders();
}
ImGui::End();

ImGui::Render();
Expand Down Expand Up @@ -128,6 +142,9 @@ void App::read_input(float dt)
case SDLK_p:
m_paused = !m_paused;
break;
case SDLK_r:
m_terrain.reload_shaders();
break;
}
break;

Expand Down
6 changes: 3 additions & 3 deletions app/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class FirstPersonCamera : public Camera
void update()
{
glm::vec3 world_up = {0.0f, 1.0f, 0.0f};
glm::vec3 forward = vector_from_spherical(glm::radians(pitch), glm::radians(yaw));
glm::vec3 right = glm::normalize(glm::cross(forward, world_up));
glm::vec3 up = glm::normalize(glm::cross(right, forward));
glm::vec3 forward = direction_from_spherical(glm::radians(pitch), glm::radians(yaw));
glm::vec3 right = glm::cross(forward, world_up);
glm::vec3 up = glm::cross(right, forward);
glm::vec3 position = local_position();

set_local_transform(glm::inverse(glm::lookAt(position, position + forward, up)));
Expand Down
12 changes: 11 additions & 1 deletion app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,24 @@ add_executable(app
)

target_link_libraries(app PRIVATE
terrain
terrain
)

target_include_directories(app PRIVATE
"${glm_SOURCE_DIR}"
"${SDL2_INCLUDE_DIRS}"
)

if(ENABLE_ADDRESS_SANITIZER)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
endif()

if(ENABLE_THREAD_SANITIZER)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=thread")
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=thread")
endif()

if(WIN32)
add_custom_command(TARGET app POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:app> $<TARGET_FILE_DIR:app>
Expand Down
2 changes: 1 addition & 1 deletion gfx
Submodule gfx updated 2 files
+30 −0 gl.cpp
+6 −0 gl.h
2 changes: 1 addition & 1 deletion terrain/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ inline T clamp_range(const T& value, const Bounds<T>& range)
return max(range.min, min(value, range.max));
}

inline glm::vec3 vector_from_spherical(float pitch, float yaw)
inline glm::vec3 direction_from_spherical(float pitch, float yaw)
{
return {std::cos(yaw) * std::cos(pitch), std::sin(pitch), std::sin(yaw) * std::cos(pitch)};
}
Expand Down
112 changes: 73 additions & 39 deletions terrain/TerrainRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ const char* skybox_frag =
;
/* clang-format on */

AABB aabb_from_node(const Node* node)
static AABB aabb_from_node(const Node* node)
{
float height = 100.0f; // TODO: do something smarter
return AABB({node->min.x, 0.0f, node->min.y}, {node->max.x, height, node->max.y});
}

Bounds<glm::vec2> rescale_uv(const TileId& parent_tile_id, const TileId& tile_id)
static Bounds<glm::vec2> rescale_uv(const TileId& parent_tile_id, const TileId& tile_id)
{
// what is the problem here?
unsigned zoom_delta = tile_id.zoom - parent_tile_id.zoom;
Expand All @@ -59,8 +59,18 @@ Bounds<glm::vec2> rescale_uv(const TileId& parent_tile_id, const TileId& tile_id

TerrainRenderer::TerrainRenderer(const TileId& root_tile, unsigned max_zoom_level_range,
const Bounds<glm::vec2>& bounds)
: m_shader(std::make_unique<ShaderProgram>(shader_vert, shader_frag)),
:
#if NDEBUG
m_terrain_shader(std::make_unique<ShaderProgram>(shader_vert, shader_frag)),
m_sky_shader(std::make_unique<ShaderProgram>(skybox_vert, skybox_frag)),
#else
m_terrain_shader(ShaderProgram::create_from_files(
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/terrain.vert",
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/terrain.frag")),
m_sky_shader(ShaderProgram::create_from_files(
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/sky.vert",
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/sky.frag")),
#endif
m_root_tile(root_tile),
m_chunk(32, 1.0f),
m_bounds(bounds),
Expand All @@ -71,7 +81,7 @@ TerrainRenderer::TerrainRenderer(const TileId& root_tile, unsigned max_zoom_leve
{
// the rendered terrain does not necessarily match with it's size in meters
float width = m_bounds.size().x;
float tile_width = wms::tile_width(wms::tiley2lat(m_root_tile.y, m_root_tile.zoom), m_root_tile.zoom);
float tile_width = m_root_tile.width_in_meters();
m_terrain_scaling_factor = width / tile_width;

// for decoding the height map
Expand All @@ -82,7 +92,7 @@ TerrainRenderer::TerrainRenderer(const TileId& root_tile, unsigned max_zoom_leve
#endif
m_height_scaling_factor = (max_elevation - min_elevation);

#if 0
#if 1
(void)m_tile_cache.tile_texture_sync(m_root_tile, TileType::ORTHO);
(void)m_tile_cache.tile_texture_sync(m_root_tile, TileType::HEIGHT);

Expand All @@ -94,6 +104,19 @@ TerrainRenderer::TerrainRenderer(const TileId& root_tile, unsigned max_zoom_leve
#endif
}

void TerrainRenderer::reload_shaders()
{
#if !NDEBUG
ShaderProgram::reload_from_files(std::move(m_terrain_shader),
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/terrain.vert",
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/terrain.frag");

ShaderProgram::reload_from_files(std::move(m_sky_shader),
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/sky.vert",
"C:/Users/jakob/Documents/Projects/TerrainRenderer/terrain/shaders/sky.frag");
#endif
}

float TerrainRenderer::terrain_elevation(const glm::vec2& point)
{
Coordinate coord = point_to_coordinate(point);
Expand Down Expand Up @@ -175,15 +198,17 @@ glm::vec2 TerrainRenderer::calculate_lod_center(const Camera& camera)
}
}

Texture* TerrainRenderer::find_cached_lower_zoom_parent(Node* node, Bounds<glm::vec2>& uv, const TileType& type)
Texture* TerrainRenderer::find_cached_lower_zoom_parent(Node* node, Bounds<glm::vec2>& uv, const TileType& type,
TileId& used)
{
Texture* parent_texture = nullptr;
TileId tile_id = node->id;
used = tile_id;

unsigned zoom = m_root_tile.zoom + node->depth;

Node* parent = node->parent;
TileId parent_tile_id;
TileId parent_tile_id = tile_id;

#if 1
while (parent != nullptr) {
Expand All @@ -204,6 +229,8 @@ Texture* TerrainRenderer::find_cached_lower_zoom_parent(Node* node, Bounds<glm::

uv = rescale_uv(parent_tile_id, tile_id);

used = parent_tile_id;

return parent_texture;
}

Expand All @@ -229,27 +256,31 @@ void TerrainRenderer::render(const Camera& camera)

if (wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

m_shader->bind();
m_shader->set_uniform("u_view", camera.view_matrix());
m_shader->set_uniform("u_proj", camera.projection_matrix());
m_shader->set_uniform("u_camera_position", camera.world_position());
m_shader->set_uniform("u_height_scaling_factor", m_height_scaling_factor * m_terrain_scaling_factor);
m_shader->set_uniform("u_debug_view", debug_view);
m_terrain_shader->bind();
m_terrain_shader->set_uniform("u_view", camera.view_matrix());
m_terrain_shader->set_uniform("u_proj", camera.projection_matrix());
m_terrain_shader->set_uniform("u_camera_position", camera.world_position());
m_terrain_shader->set_uniform("u_height_scaling_factor", m_height_scaling_factor);
m_terrain_shader->set_uniform("u_terrain_scaling_factor", m_terrain_scaling_factor);

m_terrain_shader->set_uniform("u_debug_view", debug_view);
m_terrain_shader->set_uniform("u_shading", shading);

const glm::vec3 sky_color_1 = gfx::rgb(0xB8DEFD);
const glm::vec3 sky_color_2 = gfx::rgb(0x6F93F2);
const glm::vec3 sun_color = glm::vec3(1.0, 0.9, 0.7);

const glm::vec3 sun_direction = direction_from_spherical(glm::radians(sun_elevation), glm::radians(sun_azimuth));

m_terrain_shader->set_uniform("u_sun_dir", sun_direction);
m_terrain_shader->set_uniform("u_sun_color", sun_color);

#if ENABLE_FOG
m_shader->set_uniform("u_fog_color", sky_color_1);
m_shader->set_uniform("u_fog_near", 0.0f);
m_shader->set_uniform("u_fog_far", fog_far);
m_shader->set_uniform("u_fog_density", fog_density);

float sun_elevation = 25.61f, sun_azimuth = 179.85f;
glm::vec3 sun_direction = vector_from_spherical(glm::radians(sun_elevation), glm::radians(sun_azimuth));
m_shader->set_uniform("u_sun_dir", sun_direction);
m_terrain_shader->set_uniform("u_fog_color", sky_color_1);
m_terrain_shader->set_uniform("u_fog_near", 0.0f);
m_terrain_shader->set_uniform("u_fog_far", fog_far);
m_terrain_shader->set_uniform("u_fog_density", fog_density);
#else
m_shader->set_uniform("u_fog_color", glm::vec3(0.0f));
m_terrain_shader->set_uniform("u_fog_color", glm::vec3(0.0f));
#endif

// This render function is executed on all quadtree nodes.
Expand All @@ -267,35 +298,37 @@ void TerrainRenderer::render(const Camera& camera)
Bounds<glm::vec2> albedo_uv(glm::vec2(0.0f), glm::vec2(1.0f));
Bounds<glm::vec2> height_uv(glm::vec2(0.0f), glm::vec2(1.0f));

TileId albedo_tile_id, height_tile_id;

#if ENABLE_FALLBACK
if (!albedo) {
albedo = find_cached_lower_zoom_parent(node, albedo_uv, TileType::ORTHO);
albedo = find_cached_lower_zoom_parent(node, albedo_uv, TileType::ORTHO, albedo_tile_id);
}

if (!heightmap) {
heightmap = find_cached_lower_zoom_parent(node, height_uv, TileType::HEIGHT);
heightmap = find_cached_lower_zoom_parent(node, height_uv, TileType::HEIGHT, height_tile_id);
}
#endif

if (albedo && heightmap) {
m_shader->set_uniform("u_zoom", node->depth);
m_terrain_shader->set_uniform("u_zoom", node->depth);

const float pixel_per_tile = 256;
const float root_tile_width = 0;
float tile_width = wms::tile_width(wms::tiley2lat(tile_id.y, tile_id.zoom), tile_id.zoom);
m_shader->set_uniform("u_pixel_resolution", tile_width / pixel_per_tile);
const float pixel_per_tile = 128;
float tile_width = tile_id.width_in_meters();
float pixel_resolution = tile_width / pixel_per_tile;
m_terrain_shader->set_uniform("u_pixel_resolution", pixel_resolution);

albedo->bind(0);
m_shader->set_uniform("u_albedo_texture", 0);
m_shader->set_uniform("u_albedo_uv_min", albedo_uv.min);
m_shader->set_uniform("u_albedo_uv_max", albedo_uv.max);
m_terrain_shader->set_uniform("u_albedo_texture", 0);
m_terrain_shader->set_uniform("u_albedo_uv_min", albedo_uv.min);
m_terrain_shader->set_uniform("u_albedo_uv_max", albedo_uv.max);

heightmap->bind(1);
m_shader->set_uniform("u_height_texture", 1);
m_shader->set_uniform("u_height_uv_min", height_uv.min);
m_shader->set_uniform("u_height_uv_max", height_uv.max);
m_terrain_shader->set_uniform("u_height_texture", 1);
m_terrain_shader->set_uniform("u_height_uv_min", height_uv.min);
m_terrain_shader->set_uniform("u_height_uv_max", height_uv.max);

m_chunk.draw(m_shader.get(), node->min, node->max);
m_chunk.draw(m_terrain_shader.get(), node->min, node->max);
}
};

Expand Down Expand Up @@ -338,8 +371,9 @@ void TerrainRenderer::render(const Camera& camera)
m_sky_shader->bind();
m_sky_shader->set_uniform("u_view", glm::mat4(glm::mat3(camera.view_matrix())));
m_sky_shader->set_uniform("u_proj", camera.projection_matrix());
m_sky_shader->set_uniform("u_sky_color_1", sky_color_1);
m_sky_shader->set_uniform("u_sky_color_2", sky_color_2);
m_sky_shader->set_uniform("u_sky_color", sky_color_1);
m_sky_shader->set_uniform("u_sun_dir", sun_direction);
m_sky_shader->set_uniform("u_sun_color", sun_color);
m_sky_box.draw(m_sky_shader.get(), glm::vec3(0.0f, 0.0f, 0.0f), 1.0f);

glDepthFunc(GL_LESS);
Expand Down
11 changes: 7 additions & 4 deletions terrain/TerrainRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
using namespace gfx;
using namespace gfx::gl;

Bounds<glm::vec2> rescale_uv(const TileId& parent_tile_id, const TileId& tile_id);

class TerrainRenderer
{
public:
TerrainRenderer(const TileId& root_tile, unsigned max_zoom_level_range, const Bounds<glm::vec2>& bounds);

void render(const Camera& camera);

void reload_shaders();

// get terrain elevation in meters
float terrain_elevation(const glm::vec2&);

Expand All @@ -43,14 +43,17 @@ class TerrainRenderer
bool debug_view{false};
bool debug_view_2{false};
bool manual_zoom{false};
bool shading{true};
bool frustum_culling{true};
float fog_far{2000.0f};
float fog_density{0.66f};
float max_horizon{500.0f};
int min_zoom, max_zoom;
float sun_elevation = 15.0f;
float sun_azimuth = 0.0f;

private:
const std::unique_ptr<ShaderProgram> m_shader, m_sky_shader;
std::unique_ptr<ShaderProgram> m_terrain_shader, m_sky_shader;
const TileId m_root_tile;
const Chunk m_chunk;
const Cube m_sky_box;
Expand All @@ -65,5 +68,5 @@ class TerrainRenderer

glm::vec2 calculate_lod_center(const Camera& camera);

Texture* find_cached_lower_zoom_parent(Node* node, Bounds<glm::vec2>& uv, const TileType&);
Texture* find_cached_lower_zoom_parent(Node* node, Bounds<glm::vec2>& uv, const TileType&, TileId& used);
};
2 changes: 2 additions & 0 deletions terrain/TileUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ struct TileId {
return {min, max};
}

inline float width_in_meters() const { return wms::tile_width(wms::tiley2lat(y, zoom), zoom); }

inline TileId parent() const { return TileId(zoom - 1U, x / 2U, y / 2U); }

// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Subtiles
Expand Down
Loading
Loading