Hello World(2) - - 加載紋理

0. 本文可以做什麼

在前一節的基礎上,添加了有紋理的地面 、輔助座標。

1. 添加地面

init()中加入

// floor
{
    var mat = new THREE.MeshBasicMaterial({color:0xffffff});
    var geom = new THREE.PlaneGeometry(1000, 1000, 1, 1);

    var floor = new THREE.Mesh(geom, mat);
    scene.add(floor);
}

和前面創建cube一樣,這裏創建地面。
不過用的是PlaneGeometry創建一個平面;參數分別是寬、長、寬分段、長分段。

接着運行,你會看到一個白色的平面在方塊後面。
讓我們再改變一下它的角度,在scene.add()前加入

floor.rotation.x = Math.PI * -0.5;

OpenGL使用的是右手座標系,旋轉方向也是右手定義的。
你可以將右手伸出做個GOOD的手勢,大拇指沿X軸正方向,那麼你的其餘四指就是旋轉的正方向。
於是——上述代碼將floor繞X軸順時針旋轉90度。

然後運行,你會看到方塊被白色的地面埋了一半。
我們不打算改變地面高度,於是你得修改cube的座標。
在創建cube的代碼中加入

cube.position.set(0, 50, 0);

運行,你會看到方塊冒出來了。

2. 添加輔助座標軸

你可能很需要畫一個座標軸,特別是在複雜的場景中確定空間位置的時候。

init()中加入

// axes
{
    var axes = new THREE.AxesHelper(300);
    scene.add(axes);
}

這裏用了THREE.AxesHelper()來創建輔助座標軸,參數是軸的長度。

運行,你會看到一個座標軸從方塊中心冒出來 。
你可能發現地面上的軸不是那麼清晰,因爲軸線和地面重合了。我們將它往上挪一點點,在add前加入

axes.position.y = 1;

接着運行,你會發現軸線清晰了。
座標軸

在OpenGL中,面重合往往會帶來一些麻煩,造成面閃爍;因爲浮點數精度是有限的,如果你用OpenGL畫陰影貼圖,你肯定會遇到這個問題。

座標軸的顏色

XYZ軸上面並沒有標上字母啊,我怎麼知道哪根軸是X軸?

我們表達顏色時用的是RGB,而表達座標時用的是XYZ,很自然地會用R對應X這樣,於是紅色就是X軸,綠Y軸,藍Z軸。
而且在數據結構方面,RGB和XYZ都用的是同一個數據結構是THREE.Vector3,爲一個三維向量。

3. 給地面添加紋理

材質可以表達物體的光澤屬性,讓我們分辨物體是金屬還是塑料;紋理則表達物體表面紋路。
通常二者結合起來就可以渲染出很不錯的畫面了。

我們在floor中創建mat材質前,加入

var tex = new THREE.TextureLoader().load('src/img/colors.png');

這張圖片的地址是three.js/examples/textures/colors.png
上述代碼用紋理加載器加載了一個紋理。

然後刪掉原來的mat,替換成下面的

var mat = new THREE.MeshBasicMaterial({map: tex});

map:tex 設置我們的紋理映射是tex,我們用這個紋理就不再需要color了。

運行,你會看到圖片被貼到地面上了
有紋理的地面

調整紋理映射

拉近了看,是不是紋理有點粗糙?
因爲紋理圖片像素是有限的,放大了自然會這樣。

既然這是一個地面,我們可以接受重複,於是在創建tex後加入

tex.repeat.set(10, 10);

上述代碼設置紋理映射在水平和垂直方向重複10次。

運行,你會發現
錯誤的紋理

這是怎麼了?

設置紋理包裹

英文原文是Texture Wrapping 可能別的書籍上用的不是這個詞“紋理包裹”。

紋理座標可以用ST或者UV表示,是一個意思。
原點(0,0)在紋理的左上角,S/U是右,而T/V是下。右下角是(1,1)。

於是我們可以推斷紋理圖片的座標範圍不會超過1。
我們這裏重複了10次,紋理座標是10。而超出1這個範圍的紋理該怎麼取座標呢?

可以設置紋理的包裹模式,three.js中有以下3種:
1. THREE.ClampToEdgeWrapping
2. THREE.RepeatWrapping
3. THREE.MirroredRepeatWrapping

默認值爲第1種,即超出的部分取紋理邊緣的顏色。
第2種是超出後就重複紋理,等於丟棄座標中整數的部分。
第3種是鏡像,一般地面會用這個模式。當紋理的整數部分是奇數時是第2種,是偶數時則會對應翻轉。

如果我們知道了爲什麼紋理會變成這樣,我們接着添加一句

tex.wrapS = tex.wrapT = THREE.RepeatWrapping;

上述代碼分別設置2個方向上的重複模式,由此可見不同方向上可以用不同的模式。

我們這裏沒有用第3種模式,因爲我們的貼圖有點像瓷磚,重複起來會感覺很和諧。
如果貼圖是一張草地的話,會用第3種模式。因爲全是一樣的草看來很假,不需要重複感。

運行,你會看到好得多的紋理渲染。

背面的紋理

如果你把鏡頭拉到下方,你又可以看到奇怪的現象
地面不見了

這是因爲三維世界中,物體是有正反面區分的。
而爲了運行效率,OpenGL默認會只繪製正面(如果你在前面把地面給轉反了,那麼你只會在下方纔看到地面)。

我們需要設置紋理也貼到背面去。
mat創建時的代碼改爲這樣

var mat = new THREE.MeshBasicMaterial({map: tex, side:THREE.DoubleSide});

這樣我們就貼在正反面都應用了同一個紋理。

運行,你可以發現地面的反面也會渲染了,但是cube那裏有些閃爍。
前面已經提到了面重合帶來的問題,我們修改cube那裏的代碼,將cube向上移一點點

cube.position.set(0, 51, 0);

運行,終於正確了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章