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);
运行,终于正确了。