1. 程式人生 > >three.js開發我的世界minecraft(一)地形的生成

three.js開發我的世界minecraft(一)地形的生成

我的世界minecraft地形生成

官網提供了一個minecraft案例:examples/webgl_geometry_minecraft.html

生成的效果,中間是一個直徑10的小球,我作為參考用的,這裡面的立方體在three.js中的單位是100。

案例中的地形生成,主要是採用Perlin Noise隨機生成。也可以採用灰度圖來生成。難點是理解Perlin Noise演算法。

Perlin Noise (柏林噪聲)

柏林噪聲是由Ken Perlin於1983年提出的一種梯度噪聲(Gradient Noise,通常由計算機模擬得到的一組噪聲,相較於傳統的離散數值噪聲value noise要更加連續平滑)他在1985年的SIGGRAPH會議上,做了一場以“An Image Synthesizer”為題的學術報告,正式提出他的這一發現。

      柏林噪聲的應用非常廣泛: 合成地形高度圖、生成物體表面的複雜紋理、火焰煙霧特效、波動效果的模擬等等。
 Ken Perlin的個人主頁:http://mrl.nyu.edu/~perlin/(紐約大學-媒體研究實驗室 nyu Media Research Lab)

看了一上午,這個perlin noise 的坑有點大,還是直接跳過,不跳進去了,免得出不來。淚奔啊,這幾天每天上午寫bug,下午找bug,一天時間就浪費了。

基本單位立方體

我的世界,是由“畫素塊”組成的,以一個立方體為單位。我想以立方體為基本模型單位來生成整個地形。但是研究官方的程式碼之後,發現人家更高明。一個立方體是有5個面組成的,底部的面直接省略掉,牛。

案例中修改了uv的座標,這樣的好處是使用一張圖就可以貼出不同的效果。下面是未修改uv的貼圖情況。關於attributes.uv.array在文件中沒有找到說明。自己的理解就是 平面有四個頂點,對於的uv座標是8個值,存在陣列中,順序是上左,上右,下左,下右,先x後y。這樣就可以生成側面和頂面的紋理了。

 原貼圖檔案很小,16X32畫素。放大後呈現方塊化效果,正符合我的世界的風格。主要是設定了magFilter屬性,指定了紋理的放大方式,nearestFilter最鄰近過濾。

// 右邊
var pxGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
pxGeometry.attributes.uv.array[ 1 ] = 0.5;
pxGeometry.attributes.uv.array[ 3 ] = 0.5;
pxGeometry.rotateY( Math.PI / 2 );
pxGeometry.translate( 50, 0, 0 );
//左邊
var nxGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
nxGeometry.attributes.uv.array[ 1 ] = 0.5;
nxGeometry.attributes.uv.array[ 3 ] = 0.5;
nxGeometry.rotateY( - Math.PI / 2 );
nxGeometry.translate( - 50, 0, 0 );
//上面
var pyGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
pyGeometry.attributes.uv.array[ 5 ] = 0.5;
pyGeometry.attributes.uv.array[ 7 ] = 0.5;
pyGeometry.rotateX( - Math.PI / 2 );
pyGeometry.translate( 0, 50, 0 );
//前面
var pzGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
pzGeometry.attributes.uv.array[ 1 ] = 0.5;
pzGeometry.attributes.uv.array[ 3 ] = 0.5;
pzGeometry.translate( 0, 0, 50 );
//後面
var nzGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
nzGeometry.attributes.uv.array[ 1 ] = 0.5;
nzGeometry.attributes.uv.array[ 3 ] = 0.5;
nzGeometry.rotateY( Math.PI );
nzGeometry.translate( 0, 0, -50 );

地形生成

部分程式碼,先定義了地圖的長寬。然後是生成的noise陣列,放在data中。

var worldWidth = 128, worldDepth = 128,
    worldHalfWidth = worldWidth / 2, worldHalfDepth = worldDepth / 2,
    data = generateHeight( worldWidth, worldDepth );
function generateHeight( width, height ) {
    var data = [], perlin = new ImprovedNoise(),
        size = width * height, quality = 2, z = Math.random() * 100;
    for ( var j = 0; j < 4; j ++ ) {
        if ( j === 0 ) for ( var i = 0; i < size; i ++ ) data[ i ] = 0;
        for ( var i = 0; i < size; i ++ ) {
            var x = i % width, y = ( i / width ) | 0;
            data[ i ] += perlin.noise( x / quality, y / quality, z ) * quality;
        }
        quality *= 4;
    }
    return data;
}
function getY( x, z ) {

    return ( data[ x + z * worldWidth ] * 0.2 ) | 0;

}

var matrix = new THREE.Matrix4();
var geometries = [];
for ( var z = 0; z < worldDepth; z ++ ) {
    for ( var x = 0; x < worldWidth; x ++ ) {
        var h = getY( x, z );
        matrix.makeTranslation(
            x * 100 - worldHalfWidth * 100,
            h * 100,
            z * 100 - worldHalfDepth * 100
        );
        var px = getY( x + 1, z );
        var nx = getY( x - 1, z );
        var pz = getY( x, z + 1 );
        var nz = getY( x, z - 1 );
        geometries.push( pyGeometry.clone().applyMatrix( matrix ) );
        if ( ( px !== h && px !== h + 1 ) || x === 0 ) {
            geometries.push( pxGeometry.clone().applyMatrix( matrix ) );
        }
        if ( ( nx !== h && nx !== h + 1 ) || x === worldWidth - 1 ) {
            geometries.push( nxGeometry.clone().applyMatrix( matrix ) );
        }
        if ( ( pz !== h && pz !== h + 1 ) || z === worldDepth - 1 ) {
            geometries.push( pzGeometry.clone().applyMatrix( matrix ) );
        }
        if ( ( nz !== h && nz !== h + 1 ) || z === 0 ) {
            geometries.push( nzGeometry.clone().applyMatrix( matrix ) );
        }
    }
}

var geometry = THREE.BufferGeometryUtils.mergeBufferGeometries( geometries );
geometry.computeBoundingSphere();
var texture = new THREE.TextureLoader().load( 'minecraft/atlas.png' );
texture.magFilter = THREE.NearestFilter;

var mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { map: texture, side: THREE.DoubleSide } ) );
scene.add( mesh );