Creating polished landscapes using elevation map in Three.js

I'm currently working on generating smooth terrain using the PlaneBufferGeometry feature of three.js, utilizing a height map sourced from Google Images:

Unfortunately, I'm encountering some choppiness in the result..

(Apologies, this happens to be my first question and it seems that I need 10 reputation points to share images, if not for that limitation. Nonetheless, here's something even better: a live demo! You can left click and drag to rotate and scroll to zoom)

My goal is to achieve a smooth terrain, so am I missing something or is this outcome expected, requiring further smoothing afterwards?

Below is the code snippet I have been working with:

const IMAGE_SRC = 'terrain2.png';
const SIZE_AMPLIFIER = 5;
const HEIGHT_AMPLIFIER = 10;

var WIDTH;
var HEIGHT;


var container = jQuery('#wrapper');
var scene, camera, renderer, controls;
var data, plane;

image();
// init();

function image() {
    var image = new Image();
    image.src = IMAGE_SRC;
    image.onload = function() {
        WIDTH = image.width;
        HEIGHT = image.height;

        var canvas = document.createElement('canvas');
        canvas.width = WIDTH;
        canvas.height = HEIGHT;
        var context = canvas.getContext('2d');


        console.log('image loaded');
        context.drawImage(image, 0, 0);
        data = context.getImageData(0, 0, WIDTH, HEIGHT).data;

        console.log(data);

        init();
    }
}

function init() {

    // initialize camera
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, .1, 100000);
    camera.position.set(0, 1000, 0);

    // initialize scene
    scene = new THREE.Scene();

    // initialize directional light (sun)
    var sun = new THREE.DirectionalLight(0xFFFFFF, 1.0);
    sun.position.set(300, 400, 300);
    sun.distance = 1000;
    scene.add(sun);

    var frame = new THREE.SpotLightHelper(sun);
    scene.add(frame);

    // initialize renderer
    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(0x000000);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.append(renderer.domElement);

    // initialize controls
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = .05;
    controls.rotateSpeed = .1;

    // initialize plane
    plane = new THREE.PlaneBufferGeometry(WIDTH * SIZE_AMPLIFIER, HEIGHT * SIZE_AMPLIFIER, WIDTH - 1, HEIGHT - 1);
    plane.castShadow = true;
    plane.receiveShadow = true;

    var vertices = plane.attributes.position.array;
    // apply height map to vertices of plane
    for(i=0, j=2; i < data.length; i += 4, j += 3) {
        vertices[j] = data[i] * HEIGHT_AMPLIFIER;
    }

    var material = new THREE.MeshPhongMaterial({color: 0xFFFFFF, side: THREE.DoubleSide, shading: THREE.FlatShading});

    var mesh = new THREE.Mesh(plane, material);
    mesh.rotation.x = - Math.PI / 2;
    mesh.matrixAutoUpdate  = false;
    mesh.updateMatrix();

    plane.computeFaceNormals();
    plane.computeVertexNormals();

    scene.add(mesh);

    animate();
}

function animate() {
    requestAnimationFrame(animate);

    renderer.render(scene, camera);
    controls.update();
}

Answer №1

The reason for the uneven appearance is due to the limited color depth in the height map. To provide a visual example, I colored a section of the height map using Photoshop's Paint bucket tool with 0 tolerance and non-continuous setting. This coloring reveals how large areas on the map share the same height value, resulting in jagged terrain features.

These uniformly colored sections form plateaus in the terrain, explaining the presence of both plateaus and sharp transitions in the landscape.

https://example.com/image.png

To address this issue, consider either smoothing out the Z values of the geometry or using a height map that incorporates higher bit depths such as 16 bits or even 32 bits for more precise height information. The current height map utilizes only 8 bits, equivalent to 256 values.

Answer №2

If you want to improve the smoothness of your heightmap, consider sampling more than just one pixel. Currently, the vertex indices are directly linked to pixels in the data array, and the z-value is updated from the image.

for(i=0, j=2; i < data.length; i += 4, j += 3) {
  vertices[j] = data[i] * HEIGHT_AMPLIFIER;
}

Alternatively, you could:

  • Take multiple samples with various offsets along the x/y axes
  • Calculate an average value from these samples (possibly weighted)

This approach would help in smoothing out transitions between areas of the same height.

Another option is to implement a blur-kernel technique (considering efficiency, a fast box-blur might be suitable compared to a gaussian blur).

Given the limitation in resolution by using a single byte, it's advisable to convert the image to float32 first:

const highResData = new Float32Array(data.length / 4);
for (let i = 0; i < highResData.length; i++) {
  highResData[i] = data[4 * i] / 255;
}

With higher numeric resolution after the conversion, you can now apply smoothing techniques. You have options like modifying existing libraries for float32 usage, utilizing ndarrays with ndarray-gaussian-filter, or creating a simple custom solution. The goal is to find average values for uniformly colored plateaus.

Hopefully, these suggestions prove helpful. Best of luck :)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The controller is unable to retrieve the posted value

Whenever I try to retrieve the post value from my controller, it always returns null. Even though I can see that there is a post value present when I check, for some reason, I am not able to access that value in my controller. Does anyone know what the p ...

Using Typescript with Vue to create a reactive exported variable

Scenerio Setup Currently, I have developed a Vue3 + Firebase project which includes an object called userInfo in the main.ts file. The export declaration is as follows: export var userInfo :any = {} It is important to note that the :any declaration is ...

Implement the color codes specified in the AngularJS file into the SASS file

Once the user logs in, I retrieve a color code that is stored in localstorage and use it throughout the application. However, the challenge lies in replacing this color code in the SASS file. $Primary-color:#491e6a; $Secondary-color:#e5673a; $button-color ...

leveraging angular service with ionic framework

(function () { 'use strict'; angular .module('app') .factory('UserService', UserService); UserService.$inject = ['$http']; function UserService($http) { var service = {}; ...

Creating a customized tooltip without the need for calling $(document).foundation() and using modernizr

I'm facing an issue with initializing the foundation tool-tip without having to initialize everything (such as $(document).foundation()). It seems like a simple task, but I am struggling. I have two questions in mind: (1) How can I utilize new Founda ...

Troubleshooting issue: Chrome bug causing JavaScript full height intro section to have incorrect padding and display issues

Trying to create a full-height intro section using basic JavaScript markup has been quite the challenge. Here's a more detailed explanation: I have a parent DIV element that is supposed to adjust to the full height of the viewport. Unfortunately, t ...

Verify a document located on another server

My objective is to prompt the user to download a file generated by my script and upload it onto their server (this functionality has been successfully implemented). The purpose of this task is to confirm that the user can indeed upload files to the website ...

Incorporating dynamic data binding using AngularJS in a span tag

In my view, I am using this Angular expression: <span ng-if="{{list.StoreList ? (list.StoreList.length ' Products)' : '(0 Products)'}}"> </span> The purpose is to display the count of items in StoreList if there are any, ...

Error loading 'protobuf.js' while retrieving Firestore document

When trying to run a basic node file with Firebase and Firestore, the following code was used: const firebase = require("firebase"); const http = require('http') require("firebase/firestore"); firebase.initializeApp({ apiKey: '...' ...

The art of handling jQuery promises and interconnected AJAX requests

Is there a better way to handle dependent AJAX calls using promises/deferred in jQuery? Currently, my code looks like this: $.when($.ajax('https://somedomain.com/getuserinfo/results.jsonp', { dataType : 'jsonp', jsonp ...

Make a tab the active tab within the Material-UI tab component

For the current project, I have decided to utilize Material UI as the primary library. One of the pages in the project requires four tabs, which I am implementing using the tab component from the Material UI library. By default, when rendering the page wi ...

An error was encountered in the index.js file within the app directory when running the webpack

Recently, I was told to learn react.js even though my knowledge of javascript is limited. Nevertheless, I decided to dive in and start with a simple "Hello World" project. Initially, when I used the index.js file below and ran webpack -p, everything worke ...

Increase the row horizontally: Enlarge the row by several columns and dynamically load various components based on the selected column

I am currently working on implementing a feature called "expand row by column" using the example below. /* eslint max-len: 0 */ import React from 'react'; import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table'; cons ...

Develop a custom JavaScript code block in Selenium WebDriver using Java

Recently, I came across a JavaScript code snippet that I executed in the Chrome console to calculate the sum of values in a specific column of a web table: var iRow = document.getElementById("DataTable").rows.length var sum = 0 var column = 5 for (i=1; i& ...

Trouble with the back button functionality following logout

Despite hours of research and following various links, I am still experiencing a problem where my page continues to load after logging out and clicking the back button. Below is the code I have tried for my log out page: protected void Page_Load(object se ...

The event listener for 'ended' is triggering multiple times

I am in the process of developing a music app, and my goal is to have the next song automatically play when the current one ends. However, I am facing an issue where the EventListener seems to be triggered multiple times in succession - first once, then tw ...

disable the form submission function in dropzone when buttons are clicked

Dropzone is being utilized on my page in the following manner alongside other input types such as text and select dropdowns: <form name="somename" method="post" action="/gotoURL" form="role"> // Other form elements here <div id="dropare ...

When Select2 doesn't find a suitable option, the text "other" will be displayed

Is it possible to show a default option in select2 dropdown if the user's typed input does not match any available options? $("something").select2({ formatNoMatches: function(term) { //return a predefined search choice } }); I have searched ...

How to Make Bootstrap 3 Collapse Panels Stand Out with an Active Class

First of all, here is the fiddle link: http://jsfiddle.net/krish7878/h38jn324 I have a simple question. When a panel is clicked and it expands to show its contents, a class 'active' should be added to 'panel-heading'. I came across a ...

Using jQuery Ajax in ASP.NET MVC to send arguments from JavaScript via POST requests

I have a controller action in ASP.NET MVC that has the following signature in VB.NET: <HttpPost()> Public Function ClosestCities (ByVal position As MapCoordinate, ByVal citiesCount As UInteger) As JsonResult The MapCordinate class is defined as ...