Hello
Im making a terrain system. I have a 1024x1024 PNG height map from which I sample height and apply it to my 1024x1024 wide terrain (on the CPU). The vertices are displaced and the computed normals seem good. However im running into an issue - the terrain mesh geometry isnt smooth as I hoped it would be. Effects similar to texture banding can be seen:
This seems to be a vertex position issue and not a normals issue as wireframe also displays this pattern:
The problem is even more visible when I increase the mesh resolution x4 :
Different combination of texture resolution and terrain resolution still run into the same thing.
Here is my heightmap texture:
It’s made in blender based on noise and baked from multires modifier on a plane. I load it using THREE.TextureLoader
and then use canvas2d to get it’s pixels and save the data into a 2D Float32Array.
Loading the height map texture:
textureLoader.load( `world_map_1k.png`, ( loadedTexture ) => {
// Extract pixel data from the loaded texture
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = loadedTexture.image.width;
canvas.height = loadedTexture.image.height;
context.drawImage( loadedTexture, 0, 0 );
const imageData = context.getImageData( 0, 0, canvas.width, canvas.height );
const pixels = imageData.data;
let index = 0;
// Convert RGB values to floats and store in the 2D array
for ( let i = 0; i < pixels.length; i += 4 ) {
heightMap.data[ index++ ] = ( ( pixels[ i ] / 255.0 ) - 0.5 ) * 2.0; // -1.0 : 1.0
} // Only save R channel since it's a black white texture anyway
});
From that heightMap
I later sample height using bilinear interpolation between 4 nearest data points:
Float2DArray.js sample method:
// class Float2DArray
sample( x, y ) {
if ( x < 0 || x > 1 || y < 0 || y > 1 ) return 1;
// Multiply input coordinate x by (width - 1) to ensure a coordinate 1.0 maps to highest valid index in the array (width - 1).
const fractionalX = x * ( this.#width - 1 );
const fractionalY = y * ( this.#height - 1 );
// Find the four surrounding integer indices that enclose the fractional indices.
const x1 = Math.floor( fractionalX );
const x2 = Math.ceil( fractionalX );
const y1 = Math.floor( fractionalY );
const y2 = Math.ceil( fractionalY );
// Get 2 interpolated X values
let valueX1, valueX2;
if ( x1 === x2 ) { // Both X are the same so we take values of [x][y1] and [x][y2]
valueX1 = this.get( x1, y1 );
valueX2 = this.get( x1, y2 );
} else {
const wX = x2 - fractionalX; // weight on X
valueX1 = this.get( x1, y1 ) * wX + this.get( x2, y1 ) * ( 1 - wX ); // lower X value
valueX2 = this.get( x1, y2 ) * wX + this.get( x2, y2 ) * ( 1 - wX ); // upper X value
}
const wY = y2 - fractionalY; // weight on Y
const interpolatedValue = valueX1 * wY + valueX2 * ( 1 - wY );
return interpolatedValue;
}
I also tested with noise textures not generated by me, but for example from the internet, and they also have this issue. I might presume this issue is not image specific?
Could it be that context.getImageData()
gets UInt8
values which dont have enough color precision, producing banding inside my application?
What could be the reason for this rude phenomenon? Does anybody know?
Thanks for reading and have a nice day!