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);
運行,終於正確了。