第十章 加载和使用纹理
在材质中使用纹理
function createMesh(geom,imageFile){
var texture = THREE.ImageUtils.loadTexture('../assets/textures/general/' + imageFile);
var mat = new THREE.MeshPhongMaterial();
mat.map = texture;
var mesh = new THREE.Mesh(geom,mat);
return mesh;
}
这里的纹理图片可以是:png、gif、jpeg
纹理加载是异步的。这是没有问题的,因为我们有一个render循环,每秒大概渲染场景60次,所以一旦纹理加载完毕就会立即在场景中显示。
如果你想纹理加载前一致等待。
texture = THREE.ImageUtils.loadTexture('texture.png',{},function(){
renderer.render(scene);
});
几乎所有的图片都可以作为纹理。
为了达到最好的效果,最好使用正方形的图片,其长宽都是2的次方。
由于纹理需要放大和缩小,所以通常纹理上的像素(纹理单元:texel)不会一对一地映射成面上的像素。因此WebGL和Three.js提供了几种选择。
magFilter属性:指定纹理如何放大。默认值:THREE.LinearFilter
minFilter属性:指定纹理如何缩小。默认值:THREE.LinearMipMapLinearFilter
属性值:
THREE.NearestFilter 最邻近过滤
THREE.LinearFilter 线性过滤
mipmap:一个mipmap是一组纹理图片,每个图片的尺寸都是前一张图片的一半。这些图片实在加载纹理时创建的,可以生成较平滑的过滤效果。
mipmap的属性值:
THREE.NearestMipMapNearestFilter:选择最贴近目标解析度的mipmap,然后使用最邻近过滤原则。
THREE.NearestMipMapLinarFilter:选择层次最近的两个mipmap,然后在这两层之间使用最邻近过滤原则获取两个中间值,然后这两个中间值传递给线性过滤器,以获得最终效果。
THREE.LinearMipMapNearestFilter:选择最贴近目标解析度的mipmap,然后使用线性过滤原则。
THREE.LinearMipMapLinearFilter:选择层次最近的两个mipmap,然后在这两层之间使用线性过滤原则获得两个中间值,然后这两个中间值传递给线性过滤器,以获得最终结果。
凹凸纹理贴图创建皱纹
凹凸纹理的目的是为材质增加厚度。
function createMesh(geom,imageFile,bump){
var texture = THREE.ImageUtils.loadTexture('../assets/textures/general/'+imageFile);
var mat = new THREE.MeshPhongMaterial();
mat.map = texture;
var bump = THREE.ImageUtils.loadTexture('../assets/textures/general/'+bump);
mat.bumpMap = bump;
mat.bumpScale = 0.2;//凹凸的高度,负数表示凹下去的深度。
var mesh = new THREE.Mesh(geom,mat);
return mesh;
}
凹凸贴图中只有像素的相对高度,没有任何坡度的方向性信息。所以凹凸贴图所能达到的厚度和细节程度是有限的。更多的细节可以使用法向贴图。
使用法向贴图创建更加细致的凹凸和皱纹
法向贴图中保存的不是每个像素的高度,而是像素的法向向量。使用法向贴图,只需使用很少的顶点和面,就可以创建出细节非常丰富的模型。
function createMesh(geom,imageFile,normal){
var t = THREE.ImageUtils.loadTexture('../assets/textures/general/'+imageFile);
var m = THREE.ImageUtils.loadTexture('../assets/textures/general/'+normal);
var mat2 = new THREE.MeshPhongMaterial({
mat:t,
normalMap:m
});
var mesh = new THREE.Mesh(geom,mat2);
return mesh;
}
我们还可以指定凹凸的程度,方法是设置:mat.normalScale.set(1,1);通过这个属性,可以沿着x轴和y轴缩放。不过最好将他们设置成一样的。
法向贴图的问题是不容易创建。需要使用特殊的工具,例如:Blender和Photoshop,它们可以将高解析度的渲染结果或图片作为输入,从中创建法向贴图。
使用光照贴图创建假阴影
光照贴图是预先渲染好的阴影,可以用它来模拟真实的阴影。
只对静态场景起效果。
var lm = THREE.ImageUtils.loadTexture('../assets/textures/lightmap/lm-1.png');
var wood = THREE.ImageUtils.loadTexture('../assets/textures/general/floor-wood.jpg');
var groundMaterial = new THREE.MeshBasicMaterial({
lightMap:lm,
map:wood
});
groundGeom.faceVertexUvs[1] = groundGeom.faceVertexUvs[0];
要想光照贴图显示出来,我们需要为光照贴图明确指定UV映射(将纹理的哪一部分应用到表面)。只有这样,我们才能将光照贴图和其他的纹理独立开来。
用环境贴图创建虚假的反光效果
计算反光非常消耗CPU,而且通常会使用法线追踪算法。
所以,我们可以创建一个对象所处环境的纹理来伪装反光,并将它应用到特定的对象上。
步骤:
1、创建一个CubeMap对象
2、创建一个带有这个CubeMap对象的方块(天空盒)
3、将CubeMap作为纹理
天空盒照片:
http://www.humus.name/index.php?page=Textures
function createCubeMap(){
var path = "../assets/textures/cubemap/parliament/";
var format = '.jpg';
var urls= [
path + 'posx' + format , path + 'negx' + format,
path + 'posy' + format , path + 'negy' + format,
path + 'posz' + format , path + 'negz' + format,
];
var textureCube = THREE.ImageUtils.loadTextureCube(urls);
return textureCube;
}
创建一个方块:
var textureCube = createCubeMap();
var shader = THREE.ShaderLib['cube'];
shader.uniforms['tCube'].value = textureCube;
var material = new THREE.ShaderMaterial({
fragmentShader:shader.fragmentShader,
vertexShader:shader.vertexShader,
uniforms:shader.uniforms,
depthWrite:false,
side:THREE.BackSide
});
cubeMesh = new THREE.Mesh(new THREE.CubeGeometry(100,100,100),material);
同一个Cube对象可以应用到某个网格上,用来创建虚假的反光。
var sphere1 = createMesh(new THREE.SphereGeometry(10,15,15),'plaster.jpg');
sphere1.material.envMap = textureCube;
sphere1.rotation.y = -0.5;
sphere1.position.x = 12;
sphere1.position.y = 5;
scene.add(sphere1);
var sphere2 = createMesh(new THREE.CubeGeometry(10,15,15),'plaste.jpg','plaster-normal.jpg');
sphere2.material.envMap = textureCube;
sphere2.rotation.y = 0.5;
sphere2.position.x = -12;
sphere2.position.y = 5;
scene.add(sphere2);
折射:
var textureCube = THREE.ImageUtils.loadTextureCube(urls,new THREE.CubeRefractionMapping);
通过材质的refraction属性可以控制折射率。
在这个示例中,我们使用的是静态环境贴图。即我们只能看到环境的反光,而无法看到其他网格。
高光贴图
可以为材质指定一个闪亮的、色彩明快的贴图。
一般高光贴图会同specular属性一起使用。
var specularTexture = THREE.ImageUtils.loadTexture('../assets/textures/planets/EarthSpec.png');
var normalTexture = THREE.ImageUtils.loadTexture('../assets/textures/planets/EarthNormal.png');
var planetMaterial = new THREE.MeshPhongMaterial();
planetMaterial.specularMap = specularTexture;
planetMaterial.specular = new THREE.Color(0xff0000);
planetMaterial.shininess = 1;
planetMaterial.normalMap = normalTexture;
最好的效果往往是使用低光亮度实现的,但高光贴图还会受到光照的影响。
纹理的高级用途
一、定制UV映射
通过UV映射,可以指定纹理的哪一部分显示在物体表面。当你在Three.js中创建几何体时,根据几何体的类型,这些映射也一并自动创建。
UV定制一般是在诸如Blender这样的软件中完成的,特别是在模型变得复杂的时候。
UV映射有两个维度,U和V,分别取值范围是0-1.
需要为构成面的每一个顶点指定u和v。
重复映射
cube.material.map.wrapS = THREE.RepeatWrapping;//沿x方向的行为
cube.material.map.wrapT = THREE.RepeatWrapping;//沿y方向的行为
THREE.RepeatWrapping 允许纹理重复自己
THREE.ClampToEdgeWrapping 默认设置,纹理边缘的像素会被拉伸,以填满剩下的空间。
如果使用了THREE.RepeatWrapping,我们可以使用下面的代码代替repeat属性:
cube.material.map.repeat.set(repeatX,repeatY);
参数:
repeatX:指定在x轴方向多久重复一次。
repeatY:指定在y轴方向多久重复一次。
如果设置为1,都不会重复。
如果设置<1,纹理就会被放大。
如果设置为负数,就会产生纹理镜像。
当修改repeat属性时,Three.js会自动更新纹理,并用新的设置进行渲染。
当然,你从 THREE.RepeatWrapping 切换到 THREE.ClampToEdgeWrapping 时,你就要明确更新纹理:
cube.material.map.needsUpdate = true;
在画布上绘制图案并作为纹理
交互式画布:literally库
http://literallycanvas.com
"fs-container">
"canvas-output" style='float:left'>
...
var canvas = document.createElement('canvas');
$('#canvas-output')[0].appendChild(canvas);
//创建绘图工具
$('#canvas-output').literallycanvas(
{imageURLPrefix:'../libs/literally/img'}
);
//将在画布上的绘图结果作为输入创建一个纹理
function createMesh(geom){
var canvasMap = new THREE.Texture(canvas);
var mat = new THREE.MeshPhongMaterial();
mat.map = canvasMap;
var mesh = new THREE.Mesh(geom,mat);
return mesh;
}
更新材质:
function render(){
stats.update();
cube.rotation.y += 0.01;
cube.rotation.x += 0.01;
cube.material.map.needsUpdate = true;
requeatAnimationFrame(render);
webGLRenderer.render(scene,camera);
}
用画布作凹凸纹理
凹凸图只是简单的黑白图片。
贴图中的像素的密集程度越高,贴图看上去越皱。
在画布上随机产生一副灰度图。
Perlin噪声,可以产生看上去非常自然的随机纹理。
var ctx = canvas.getContext('2d');
function fillWithPerlin(perlin,ctx){
for(var x = 0 ;x < 512; x++){
for(var y = 0 ;y <512;y++){
var base = new THREE.Color(0xffffff);
var value = perlin.noise(x/10,y/10,0);//在画布x坐标和y坐标的基础上生成一个0到1之间的值。该值可以在画布上画一个像素点。
base.multiplyScalar(value);
ctx.fillStyle = '#' + base.getHexString();
ctx.fillRect(x,y,1,1);
}
}
}
function createMesh(geom){
var bumpMap = new THREE.Texture(canvas);
var mat = new THREE.MeshPhongMaterial();
mat.color = new THREE.Color(0x77ff77);
mat.bumpMap = bumpMap;
bumpMap.needsUpdate = true;
var mesh = new THREE.Mesh(geom,mat);
return mesh;
}
用视频输出作为纹理
webgl已经直接支持HTML视频元素。
var video = document.getElementById('video');
texture = new THREE.Texture(video);
texture.minFilter = Three.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = false;
注意:
1.由于我们的视频不是正方形,所以要保证材质不会生成mipmap。
2.由于材质变化得很频繁,所以我们需要设置简单高效的过滤器。
var materialArray =[];
materialArray.push(new THREE.MeshBasicMaterial(color:0x0051ba));
materialArray.push(new THREE.MeshBasicMaterial(color:0x0051ba));
materialArray.push(new THREE.MeshBasicMaterial(color:0x0051ba));
materialArray.push(new THREE.MeshBasicMaterial(color:0x0051ba));
materialArray.push(new THREE.MeshBasicMaterial(color:0x0051ba));
materialArray.push(new THREE.MeshBasicMaterial(color:0x0051ba));
var faceMaterial = new THREE.MeshFaceMaterial(materialArray);
var mesh = new THREE.Mesh(geom,faceMaterial);
在帧循环中更新材质
if(video.readyState === video.HAVE_ENOUGH_DATA){
if(texture)
texture.needsUpdate = true;
}
小结:
使用纹理时的注意事项:
1.纹理图片的类型可以是png、jpg或gif格式的。图片加载是异步的,所以要么使用渲染循环,要么在加载纹理时提供一个回调函数。
2.使用正方形纹理。这样就可以使用mipmap来达到更好的效果。
3.标准情况下,Three.js并不支持反光,可以使用环境贴图创建出虚假的反光。
4.要想直接控制物体表面的光亮贴图,可以使用高光贴图。,
5.纹理的repeat属性,可以让纹理自我复制。还要记住将材质的包裹属性从 ClampToEdgeWrapping 改成 RepeatWrapping
6.Thee.js中可以用HTML5画布元素或者视频元素创建动态纹理。