terrain: 16-bit heightmap import/export

This commit is contained in:
Turánszki János
2023-07-21 07:45:36 +02:00
parent 7e0d0b963a
commit c86d5c8ee8
4 changed files with 54 additions and 22 deletions
+25 -16
View File
@@ -197,13 +197,13 @@ HeightmapModifierWindow::HeightmapModifierWindow() : ModifierWindow("Heightmap")
AddWidget(&scaleSlider);
loadButton.Create("Load Heightmap...");
loadButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be placed in the world center.");
loadButton.SetTooltip("Load a heightmap texture, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be placed in the world center.\nIt is recommended to use a 16-bit PNG for heightmaps.");
loadButton.OnClick([=](wi::gui::EventArgs args) {
wi::helper::FileDialogParams params;
params.type = wi::helper::FileDialogParams::OPEN;
params.description = "Texture";
params.extensions = wi::resourcemanager::GetSupportedImageExtensions();
params.description = "*.png";
params.extensions = { "PNG" };
wi::helper::FileDialog(params, [=](std::string fileName) {
wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) {
@@ -212,21 +212,29 @@ HeightmapModifierWindow::HeightmapModifierWindow() : ModifierWindow("Heightmap")
heightmap_modifier->width = 0;
heightmap_modifier->height = 0;
int bpp = 0;
stbi_uc* rgba = stbi_load(fileName.c_str(), &heightmap_modifier->width, &heightmap_modifier->height, &bpp, 1);
if (rgba != nullptr)
if (stbi_is_16_bit(fileName.c_str()))
{
heightmap_modifier->data.resize(heightmap_modifier->width * heightmap_modifier->height);
for (int i = 0; i < heightmap_modifier->width * heightmap_modifier->height; ++i)
stbi_us* rgba = stbi_load_16(fileName.c_str(), &heightmap_modifier->width, &heightmap_modifier->height, &bpp, 1); if (rgba != nullptr)
{
heightmap_modifier->data[i] = rgba[i];
heightmap_modifier->data.resize(heightmap_modifier->width * heightmap_modifier->height * sizeof(uint16_t));
std::memcpy(heightmap_modifier->data.data(), rgba, heightmap_modifier->data.size());
stbi_image_free(rgba);
generation_callback(); // callback after heightmap load confirmation
}
}
else
{
stbi_uc* rgba = stbi_load(fileName.c_str(), &heightmap_modifier->width, &heightmap_modifier->height, &bpp, 1);
if (rgba != nullptr)
{
heightmap_modifier->data.resize(heightmap_modifier->width * heightmap_modifier->height * sizeof(uint8_t));
std::memcpy(heightmap_modifier->data.data(), rgba, heightmap_modifier->data.size());
generation_callback(); // callback after heightmap load confirmation
}
stbi_image_free(rgba);
generation_callback(); // callback after heightmap load confirmation
}
});
});
});
});
AddWidget(&loadButton);
SetSize(XMFLOAT2(200, 180));
@@ -666,7 +674,7 @@ void TerrainWindow::Create(EditorComponent* _editor)
AddWidget(&topLevelSlider);
saveHeightmapButton.Create("Save Heightmap...");
saveHeightmapButton.SetTooltip("Save a heightmap texture from the currently generated terrain, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe heightmap will be normalized into 8bit PNG format which can result in precision loss!");
saveHeightmapButton.SetTooltip("Save a heightmap texture from the currently generated terrain, where the red channel corresponds to terrain height and the resolution to dimensions.\nThe image will be saved as a single channel 16-bit PNG.");
saveHeightmapButton.SetSize(XMFLOAT2(wid, hei));
saveHeightmapButton.SetPos(XMFLOAT2(x, y += step));
AddWidget(&saveHeightmapButton);
@@ -729,8 +737,9 @@ void TerrainWindow::Create(EditorComponent* _editor)
wi::vector<uint8_t> data;
int width = int(aabb.getHalfWidth().x * 2 + 1);
int height = int(aabb.getHalfWidth().z * 2 + 1);
data.resize(width * height);
data.resize(width * height * sizeof(uint16_t));
std::fill(data.begin(), data.end(), 0u);
uint16_t* dest = (uint16_t*)data.data();
for (auto& chunk : terrain->chunks)
{
@@ -751,7 +760,7 @@ void TerrainWindow::Create(EditorComponent* _editor)
p.x -= aabb._min.x;
p.z -= aabb._min.z;
int coord = int(p.x) + int(p.z) * width;
data[coord] = uint8_t(wi::math::InverseLerp(aabb._min.y, aabb._max.y, p.y) * 255u);
dest[coord] = uint16_t(wi::math::InverseLerp(aabb._min.y, aabb._max.y, p.y) * 65535u);
}
}
}
@@ -767,7 +776,7 @@ void TerrainWindow::Create(EditorComponent* _editor)
wi::graphics::TextureDesc desc;
desc.width = uint32_t(width);
desc.height = uint32_t(height);
desc.format = wi::graphics::Format::R8_UNORM;
desc.format = wi::graphics::Format::R16_UNORM;
bool success = wi::helper::saveTextureToFile(data, desc, filename_replaced);
assert(success);
+15 -1
View File
@@ -7,6 +7,7 @@
#include "Utility/stb_image_write.h"
#include "Utility/basis_universal/encoder/basisu_comp.h"
#include "Utility/basis_universal/encoder/basisu_gpu_texture.h"
#include "Utility/basis_universal/encoder/lodepng.h"
extern basist::etc1_global_selector_codebook g_basis_global_codebook;
#include <thread>
@@ -219,6 +220,20 @@ namespace wi::helper
using namespace wi::graphics;
const uint32_t data_stride = GetFormatStride(desc.format);
std::string extension = wi::helper::toUpper(fileExtension);
if (!extension.compare("PNG") && desc.format == Format::R16_UNORM)
{
// Specialized handling for 16-bit single channel PNG:
uint16_t* dest = (uint16_t*)texturedata.data();
for (uint32_t i = 0; i < desc.width * desc.height; ++i)
{
uint16_t value = dest[i];
dest[i] = (value >> 8) | ((value & 0xFF) << 8); // little endian to big endian
}
lodepng::encode(filedata, texturedata, desc.width, desc.height, LCT_GREY, 16);
return !filedata.empty();
}
struct MipDesc
{
const uint8_t* address = nullptr;
@@ -246,7 +261,6 @@ namespace wi::helper
mip_depth = std::max(1u, mip_depth / 2);
}
std::string extension = wi::helper::toUpper(fileExtension);
bool basis = !extension.compare("BASIS");
bool ktx2 = !extension.compare("KTX2");
basisu::image basis_image;
+13 -4
View File
@@ -390,11 +390,20 @@ namespace wi::terrain
XMFLOAT2 p = world_pos;
p.x *= frequency;
p.y *= frequency;
XMFLOAT2 pixel = XMFLOAT2(p.x + width * 0.5f, p.y + this->height * 0.5f);
if (pixel.x >= 0 && pixel.x < width && pixel.y >= 0 && pixel.y < this->height)
XMFLOAT2 pixel = XMFLOAT2(p.x + this->width * 0.5f, p.y + this->height * 0.5f);
if (pixel.x >= 0 && pixel.x < this->width && pixel.y >= 0 && pixel.y < this->height)
{
const int idx = int(pixel.x) + int(pixel.y) * width;
Blend(height, ((float)data[idx] / 255.0f) * scale);
const int idx = int(pixel.x) + int(pixel.y) * this->width;
float value = 0;
if (data.size() == this->width * this->height * sizeof(uint8_t))
{
value = ((float)data[idx] / 255.0f);
}
else if (data.size() == this->width * this->height * sizeof(uint16_t))
{
value = ((float)((uint16_t*)data.data())[idx] / 65535.0f);
}
Blend(height, value * scale);
}
}
};
+1 -1
View File
@@ -9,7 +9,7 @@ namespace wi::version
// minor features, major updates, breaking compatibility changes
const int minor = 71;
// minor bug fixes, alterations, refactors, updates
const int revision = 253;
const int revision = 254;
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);