real world 3d terrain simulation

I am new to game development (full stack dev background) & now beginning to use unreal engine, my long term goal is to create a 3d simulation & planning software of predefined real world location. obviously for that we need to prepare beautiful, realistic, visually enhanced terrain of that area (more enhanced version of google earth)

i am thinking of 2 modules.

  1. the module where terrain will be prepared automatically from the dem data and satellite imagery upoaded of any area. options will be available to manually edit that terrain as well

  2. in the second module we will be able to choose the prepared terrains for any type of simulation, planning, discussion etc

I am totally new to this field (& missing a lot of information as of now). so kindly help me, from where to start?

for the very first stage i want to build 3d terrain for any custom area of about 4 sq. km (I have its dem.tif & satellite.tif). please explain me how can i do that correctly? currently i am struggling to convert .tif files to PNG16 format

thanks in advance.

Perhaps this tutorial I made some times ago can help you :

Is write it at a time I was working on a terrain importer project :

For geotiff to raw16 convertion I used GDAl library like this :

#include “Convert.h”

#include
#include
#include

#include <gdal_priv.h>

void ConvertGeoTiffToRaw16(ign::Download const & height_download, double no_data_value, ign::DVec2 const& position_m)
{
   std::cout << “Converting heightmap to raw” << std::endl;

std::string dataSetPath = height_download.compute_path();
std::string headerPath = height_download.folder_path + "/" + height_download.name + ".json";
std::string rawDataPath = height_download.folder_path + "/" + height_download.name + ".raw";

//Open dataset
GDALAllRegister();

auto poDataset = GDALDataset::FromHandle(GDALOpen(dataSetPath.c_str(), GA_ReadOnly));
if (poDataset == nullptr)
{
    std::cerr << "Can load dataset : " << dataSetPath << std::endl;
    return;
}

//Get infos
double adfGeoTransform[6];

//Get raster band
GDALRasterBand* poBand;
int             nBlockXSize, nBlockYSize;
int             bGotMin, bGotMax;
double          adfMinMax[2];
poBand = poDataset->GetRasterBand(1);
poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);


GDALSetRasterNoDataValue((GDALRasterBandH)poBand, -99999.0);

adfMinMax[0] = poBand->GetMinimum(&bGotMin);
adfMinMax[1] = poBand->GetMaximum(&bGotMax);
if (!(bGotMin && bGotMax))
    GDALComputeRasterMinMax((GDALRasterBandH)poBand, FALSE, adfMinMax);
//printf("Min=%.3fd, Max=%.3f\n", adfMinMax[0], adfMinMax[1]);

/*if (poBand->GetOverviewCount() > 0)
    printf("Band has %d overviews.\n", poBand->GetOverviewCount());
if (poBand->GetColorTable() != NULL)
    printf("Band has a color table with %d entries.\n",
        poBand->GetColorTable()->GetColorEntryCount());*/

//Reading
float* pafScanline;
int   nXSize = poBand->GetXSize();
int   nYSize = poBand->GetYSize();

pafScanline = (float*)CPLMalloc(sizeof(float) * nXSize);

//Compute distance
double lon0 = adfGeoTransform[0];
double lat0 = adfGeoTransform[3];
double lon1 = lon0 + nXSize * adfGeoTransform[1];
double lat1 = lat0 + nYSize * adfGeoTransform[5];
double midLat = (lat0 + lat1) / 2.0;
double midLon = (lon0 + lon1) / 2.0;

double minHeight_m = adfMinMax[0];
double height_m = adfMinMax[1] - minHeight_m;
double midHeight_m = minHeight_m + height_m / 2.0;

//Write header
ign::DVec2 tile_resolution_m_px = height_download.bbox.size_m / ign::DVec2(height_download.size_px);
ign::DVec2 tile_size_m = tile_resolution_m_px * (ign::DVec2(height_download.size_px) - ign::DVec2(1, 1));

std::ofstream header(headerPath);
header << std::setprecision(3) << std::fixed;
header << "{ " << std::endl;
header << "\t\"resolutionX_px\" : " << nXSize << ", " << std::endl;
header << "\t\"resolutionY_px\" : " << nYSize << ", " << std::endl;
header << "\t\"sizeX_m\" : " << tile_size_m.x << ", " << std::endl;
header << "\t\"sizeY_m\" : " << tile_size_m.y << ", " << std::endl;
header << "\t\"sizeZ_m\" : " << height_m << ", " << std::endl;
header << "\t\"minZ_m\" : " << minHeight_m << ", " << std::endl;
header << "\t\"maxZ_m\" : " << minHeight_m + height_m << ", " << std::endl;
header << "\t\"midZ_m\" : " << midHeight_m << ", " << std::endl;

header << "\t\"UEImport\" : { " << std::endl;
header << "\t\t\"LocationX\" : " << position_m.x * 100 << ", " << std::endl;
header << "\t\t\"LocationY\" : " << position_m.y * -100 << ", " << std::endl;
header << "\t\t\"LocationZ\" : " << midHeight_m * 100 << ", " << std::endl;
header << "\t\t\"ScaleX\" : " << 100.0 * tile_resolution_m_px.x << ", " << std::endl;
header << "\t\t\"ScaleY\" : " << 100.0 * tile_resolution_m_px.y << ", " << std::endl;
header << "\t\t\"ScaleZ\" : " << 100.0 * height_m / 511.992 << std::endl;
header << "}" << std::endl;

header << "}" << std::endl;
header.close();

//Write data
std::ofstream rawData(rawDataPath, std::ios::binary);

std::vector<uint16_t> rawLine = std::vector<uint16_t>(nXSize);

for (int y = 0; y < nYSize; ++y)
{
    poBand->RasterIO(GF_Read, 0, y, nXSize, 1,
        pafScanline, nXSize, 1, GDT_Float32,
        0, 0);

    for (int x = 0; x < nXSize; ++x)
    {
        rawLine[x] = (std::uint16_t)std::round(
            std::numeric_limits<uint16_t>::max() * (pafScanline[x] - minHeight_m) / height_m
        );
    }

    rawData.write((const char*)&rawLine.front(), nXSize * sizeof(std::uint16_t));
}

rawData.close();

//Close
CPLFree(pafScanline);
GDALClose(poDataset);
}