WebGL three.js学习笔记 6种类型的纹理介绍及应用
本文所使用到的demo演示:
高光贴图Demo演示
反光效果Demo演示(因为是加载的模型,所以速度会慢)
(一)普通纹理
计算机图形学中的纹理既包括通常意义上物体表面的纹理即使物体表面呈现凹凸不平的沟纹,同时也包括在物体的光滑表面上的彩色图案,所谓的纹理映射就是在物体的表面上绘制彩色的图案。
在three.js中使用纹理可以实现很多不同的效果,但是最基本的就是为网格体的每个像素指定颜色。等同于将一张纹理图片应用在一个几何体的材质上。
使用的方式很简单,只需要设置
material.map = 需要设置的纹理对象
纹理对象的获得方式也很简单,只需要使用THREE.TextureLoader().load(url)函数就可以为url指定路径的纹理图片创建一个对象。具体的使用方式如下:
let texture = new THREE.TextureLoader().load("../../../Image/metal-rust.jpg");
let material = new THREE.MeshBasicMaterial();
material.map = texture;
let geometry = new THREE.BoxGeometry(10,10,10);
let cube = new THREE.Mesh(geometry,material);
scene.add(cube);
其中,"../../../Image/metal-rust.jpg"是我使用的纹理的路径,图片就是下面这一张
创建出来的带有上图纹理的cube就是这样的
除了THREE.TextureLoader()这个加载器以为,three.js还为我们提供了其他自定义的加载器,如dds格式,pvr格式,tga格式。
就拿tga格式举例,我们要加载tga格式的纹理,首先需要引用TGALoader.js这个文件,然后创建一个tga格式的加载器
let loader = new THREE.TGALoader();
我们就可以使用loader这个加载器,像上面一样的加载tga格式的纹理了。
具体代码如下:
let loader = new THREE.TGALoader();
let texture = loader.load("../../../Image/crate_color8.tga");
let material = new THREE.MeshBasicMaterial();
material.map = texture;
let geometry = new THREE.BoxGeometry(10,10,10);
let cube = new THREE.Mesh(geometry,material);
scene.add(cube);
下面是我使用的tga格式的纹理图片(只能上传截图,tga格式图片的这里上传不了)
运行出来是这个样子的
其他格式的加载也是和tga格式加载方法一样的,只需要引入相应的js文件就可以使用了。
(二)凹凸贴图
凹凸纹理用于为材质添加厚度与深度,如字面意思一样,可以让材质看上去是凹凸不平的。凹凸贴图只包含像素的相对高度,像素的密集程度定义凹凸的高度,所以想要让物体好看,首先还是应该设置一个普通的纹理,再在这个基础上添加一个凹凸纹理,就可以实现凹凸不平的物体效果。
凹凸贴图的创建方法很简单,和普通纹理类似,只是我们设置的不是map,而是bumpMap
material.bumpMap = 需要设置的纹理对象
特别需要注意的是,这里的材质只能使用MeshPhongMaterial,凹凸贴图才会有效果。
具体的设置方法如下:
let geom = new THREE.BoxGeometry(10, 10, 10);
//创建普通纹理材质
let texture = new THREE.TextureLoader().load("../../../Image/stone.jpg");
let material = new THREE.MeshPhongMaterial({
map:texture
});
cube = new THREE.Mesh(geom,material);
cube.position.set(-7,0,0);
scene.add(cube);
//创建凹凸纹理材质
let bumpTexture = new THREE.TextureLoader().load("../../../Image/stone-bump.jpg");
let bumpMaterial = new THREE.MeshPhongMaterial({
map:texture,
bumpMap:bumpTexture,
bumpScale:2
});
bumpCube = new THREE.Mesh(geom,bumpMaterial);
bumpCube.position.set(7,0,0);
scene.add(bumpCube);
其中material.bumpScale可以设置凹凸的高度,如果为负值,则表示的是深度。
运行程序截图如下:
左边材质的是普通的纹理贴图,右边的材质是带有凹凸纹理的,当前bumpScale设置的是2,两者看上去有比较明显的不同
使用的纹理图片如下:
凹凸纹理图片:
我们可以发现,凹凸图只包含了像素的相对高度,没有任何的倾斜的方向信息,所以使用凹凸纹理能表达的深度信息有限,如果想用实现更多的细节可以使用下面介绍的法向贴图。
(三)法向贴图
法向贴图保存的不是高度的信息,而是法向量的信息,我们使用法向贴图,只需要很少的顶点和面就可以实现很丰富的细节。
同样的,实现法向贴图和凹凸贴图也很类似,只需要设置
material.normalMap = 需要设置的纹理对象
同样也是在MeshPhongMaterial材质中才有效果,还要注意的一点是设置normalScale指定材质的凹凸程度时,normalScale需要接受的是一个THREE.Vector2类型
具体的代码如下:
let geom = new THREE.BoxGeometry(10, 10, 10);
//创建普通纹理材质
let texture = new THREE.TextureLoader().load("../../../Image/plaster.jpg");
let material = new THREE.MeshPhongMaterial({
map:texture
});
cube = new THREE.Mesh(geom,material);
cube.position.set(-7,0,0);
scene.add(cube);
//创建凹凸纹理材质
let normalTexture = new THREE.TextureLoader().load("../../../Image/plaster-normal.jpg");
let normalMaterial = new THREE.MeshPhongMaterial({
map:texture,
normalMap:normalTexture,
normalScale:new THREE.Vector2(1,1)
});
normalCube = new THREE.Mesh(geom,normalMaterial);
normalCube.position.set(7,0,0);
scene.add(normalCube);
场景如下图,右边的是带有法向纹理的物体,明显感觉出材质的细节多出来了很多。
用到的纹理图
法向纹理图:
虽然法向纹理能带给物体更逼真的效果,但是想要创建法向纹理图,本身就比较困难,需要ps或者blender这样的特殊工具。
(四)光照贴图
如果我们想在场景中添加阴影,three.js给我们提供了renderer.shadowMapEnabled = true这个办法,但是这对于资源的消耗是很大的。如果我们只是需要对静态的物体添加阴影效果,我们就有一种开销很小的办法,那就是光照贴图。
光照贴图是预先渲染好的阴影贴图,可以用来模拟真实的阴影。我们能使用这种技术创建出分辨率很高的阴影,并且不会损耗渲染的性能。因为是提前根据场景渲染好的,所以只对静态的场景有效。
比如下面这张光照贴图:
设置光照贴图的方式很简单,只需要设置
material.lightMap = 需要设置的纹理对象
和前面两个没什么太大的区别。当纹理设置好以后,我们还需要把我们的物体摆放在正确的位置,这样阴影效果才会真实的显现出来。
let lightMap = new THREE.TextureLoader().load("../../../Image/lm-1.png");
let map = new THREE.TextureLoader().load("../../../Image/floor-wood.jpg");
//创建地板
let planeGeo = new THREE.PlaneGeometry(95,95,1,1);
planeGeo.faceVertexUvs[1] = planeGeo.faceVertexUvs[0];
let planeMat = new THREE.MeshBasicMaterial({
color:0x999999,
lightMap:lightMap,//在地板的材质上添加光照贴图
map:map//地板的普通纹理材质
});
let plane = new THREE.Mesh(planeGeo,planeMat);
plane.rotation.x = -Math.PI / 2;
plane.position.y = 0;
scene.add(plane);
//创建大的cube
var boxGeo = new THREE.BoxGeometry(12,12,12);
var material = new THREE.MeshBasicMaterial();
material.map = new THREE.TextureLoader().load("../../../Image/stone.jpg");
var box = new THREE.Mesh(boxGeo,material);
box.position.set(0.9,6,-12);
scene.add(box);
//创建小的cube
var boxGeo = new THREE.BoxGeometry(6, 6, 6);
var material = new THREE.MeshBasicMaterial();
material.map = new THREE.TextureLoader().load("../../../Image/stone.jpg");
var box = new THREE.Mesh(boxGeo,material);
box.position.set(-13.2, 3, -6);
scene.add(box);
其中,planeGeo.faceVertexUvs[1] = planeGeo.faceVertexUvs[0] 这句话是我们需要明确的指定光照贴图的uv映射(将纹理的哪一部分应用在物体表面)这样才能将光照贴图的使用和其他的纹理分别开来。
planeGeo.faceVertexUvs保存的就是几何体面的uv映射信息,我们将faceVertexUvs[0]层的信息保存到faceVertexUvs[1]层
faceVertexUvs的官方文档解释:
.faceVertexUvs : Array
Array of face UV layers, used for mapping
textures onto the geometry. Each UV layer is an array of UVs matching
the order and number of vertices in faces.
运行结果如图:
(五)高光贴图
高光是光源照射到物体然后反射到人的眼睛里时,物体上最亮的那个点就是高光,高光不是光,而是物体上最亮的部分。
而高光贴图就是高光贴图是反应光线照射在物体表面的高光区域时所产生的环境反射,它的作用是反映物体高光区域效果。
通过高光贴图,我们可以为材质创建一个闪亮的、色彩明快的贴图。高光贴图的黑色部分会暗淡,而白色的部分会比较的亮。
创建高光贴图的方法也和前面差不多
material.specularMap= 需要设置的纹理对象
具体的代码如下:
let map = new THREE.TextureLoader().load("../../../Image/Earth.png");
let specularMap = new THREE.TextureLoader().load("../../../Image/EarthSpec.png");
let normalMap = new THREE.TextureLoader().load("../../../Image/EarthNormal.png");
let sphereMaterial = new THREE.MeshPhongMaterial({
map:map,
specularMap:specularMap,
normalMap:normalMap,
normalScale:THREE.Vector2(2,2),
specular:0x0000ff,
shininess:2
});
let sphereGeometry = new THREE.SphereGeometry(30,30,30);
let sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);
scene.add(sphere);
这段代码创建了一个球体,并为球体的材质贴上了普通纹理,法向纹理和高光纹理,其中specular属性可以决定反光的颜色,shininess可以决定发光的亮度。
运行出来的样子如下:
可以看到,海洋的地方比较亮,而大陆的的颜色相对较暗。
高光贴图Demo演示
用到的几张纹理图:
高光纹理:
法向纹理:
(六)环境贴图
如果我们想要在场景中创建反光的物体,通常会使用光线追踪的算法,但是这对cpu的消耗是巨大的,但是环境贴图就给我们创造了更容易的方法,我们只需要使用给物体的材质贴上环境贴图,就可以模拟反光的效果。
首先我们的场景需要有一个环境,这个环境我们可以使用CubeTextureLoader()来创建。在前面的文章里曾经介绍过如何创建360度全景的环境,这个CubeTextureLoader()和那里面用到的其实是一样的,只是版本的更替,现在更多使用这个函数。
具体用法是:
let cubeMap = new THREE.CubeTextureLoader().setPath(
"../../../Image/MapCube/Bridge2/").load(
[
'posx.jpg',
'negx.jpg',
'posy.jpg',
'negy.jpg',
'posz.jpg',
'negz.jpg'
]);
scene = new THREE.Scene();
scene.background = cubeMap;
在前面的文章已经介绍过,这里就不再赘述。
创建cubeMap所用到的图片在http://www.humus.name/index.php?page=Textures可以直接下载。
我们有了一个可以反射的环境以后,就可以开始为我们的物体创建材质贴图了。
创建材质贴图的方式和前面还是差不多
material.envMap = scene.background;
scene.background就是我们刚刚所创建的场景的背景,这样材质的环境贴图就相当于贴上了周围环境,从摄像机去看物体的话,看上去就是对环境有一个反射的效果了。
创建的代码如下:
function initObject()
{
let material = new THREE.MeshPhongMaterial();
material.envMap = scene.background;
let boxGeometry = new THREE.BoxGeometry(5,50,50);
let box = new THREE.Mesh(boxGeometry,material);
box.position.set(-70,0,-10);
box.rotation.y-=Math.PI/2;
scene.add(box);
let sphereGeometry = new THREE.SphereGeometry(30,30,30);
let sphere = new THREE.Mesh(sphereGeometry,material);
sphere.position.set(70,0,-10);
scene.add(sphere);
}
和前面的代码没有太大的区别,这里主要是创建了两个物体,都使用的相同环境贴图的材质。
运行的结果:
可以看到,这两个物体都对环境有反射的效果。
值得注意的是,我们使用环境贴图创建的材质仅仅静态的环境贴图。我们只能看到物体上面有周围环境的反射,看不到物体对其他物体的反射。
如果我们要看到物体对其他物体的反射,我们可以使用一个新的对象——cubeCamera
创建cubeCamera的方法很简单.
let cubeCamera = new THREE.CubeCamera(0.1, 2000, 2048);
scene.add(cubeCamera);
其中:
第一个参数0.1是相机的近裁剪距离
第二个参数2000是相机远裁剪距离
第三个参数2048是相机分辨率
使用THREE.CubeCamera可以为场景中所要渲染的物体创建快照,并使用这些快照创建CubeMap对象。但是需要确保摄像机被放置在THREE.Mesh网格上你所想显示反射的位置上。例如,我们想在球体的中心显示反射,由于球体所处的位置是(0, 0, 0),所以我们没有显示的指定THREE.CubeCamera的位置。我们只是将动态反射应用于球体上,所以把它的envMap设置为cubeCamera.renderTarget
即material.envMap = cubeCamera.renderTarget;
简单来说,就是把我们所要显示反射的“镜子”的material.envMap设置为cubeCamera.renderTarget,同时还要把cubeCamera的位置设置到镜子的位置,cubeCamera.position.copy(镜子.position);
代码如下:
let loader = new THREE.STLLoader();
loader.load("../../../asset/LibertStatue.obj.stl",function (bufferGeometry)
{
let material = new THREE.MeshBasicMaterial();
material.envMap=scene.background;
obj = new THREE.Mesh(bufferGeometry,material);
obj.scale.set(50,50,50);
scene.add(obj);
});//加载stl模型
let cubeMaterial = new THREE.MeshPhongMaterial();
cubeMaterial.envMap = cubeCamera.renderTarget;
let boxGeometry = new THREE.BoxGeometry(3, 400, 400);
let box = new THREE.Mesh(boxGeometry, cubeMaterial);
box.position.set(0, 0, -300);
box.rotation.y -= Math.PI / 2;
scene.add(box);
cubeCamera.position.copy(box.position);
这段代码中,我们从外部加载了一个stl格式的模型,也可以就使用简单的几何体来演示。下面的一部分代码就创建了可以反射的镜子。
最后,我们还需要在render()中添加cubeCamera.update(renderer, scene)用cubeCamera进行渲染
function render()
{
if(obj) obj.rotation.y+=0.02;
cubeCamera.update(renderer, scene);
stats.update();
renderer.clear();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
运行后的情况如下:
我们可以看到我们加载的stl模型在我们创建的镜子中反射出来了,并且会根据模型的移动,镜子的反射也会自动变化。
反光效果Demo演示
以上就是介绍的全部类型的纹理。
反光效果demo的完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Environment Map</title>
<script src="../../../import/three.js"></script>
<script src="../../../import/stats.js"></script>
<script src="../../../import/Setting.js"></script>
<script src="../../../import/OrbitControls.js"></script>
<script src="../../../import/STLLoader.js"></script>
<style type="text/css">
body {
border: none;
cursor: pointer;
width: 100%;
height: 1000px;
/*全屏显示的设置*/
margin: 0;
overflow: hidden; /*消除浏览器的滚动条*/
}
/*加载动画*/
#loading {
width: 100%;
height: 850px;
background-color: #333333;
}
#spinner {
width: 100px;
height: 100px;
position: fixed;
top: 50%;
left: 50%;
}
.double-bounce1, .double-bounce2 {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #67CF22;
opacity: 0.6;
position: absolute;
top: 0;
left: 0;
-webkit-animation: bounce 2.0s infinite ease-in-out;
animation: bounce 2.0s infinite ease-in-out;
}
.double-bounce2 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
@-webkit-keyframes bounce {
0%, 100% {
-webkit-transform: scale(0.0)
}
50% {
-webkit-transform: scale(1.0)
}
}
@keyframes bounce {
0%, 100% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
}
50% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
}
</style>
</head>
<body onload="Start()">
<!--加载动画的div-->
<div id="loading">
<div id="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</div>
</div>
<script>
let camera, renderer, scene, cubeCamera, light;
let controller;
function initThree()
{
//渲染器初始化
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x333333);
document.body.appendChild(renderer.domElement);//将渲染添加到body中
//初始化摄像机,这里使用透视投影摄像机
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000);
camera.position.set(20, 15, 200);
camera.up.x = 0;//设置摄像机的上方向为哪个方向,这里定义摄像的上方为Y轴正方向
camera.up.y = 1;
camera.up.z = 0;
camera.lookAt(0, 0, 0);
let cubeMap = new THREE.CubeTextureLoader().setPath("../../../Image/MapCube/Bridge2/").load(
[
'posx.jpg',
'negx.jpg',
'posy.jpg',
'negy.jpg',
'posz.jpg',
'negz.jpg'
]);
scene = new THREE.Scene();
scene.background = cubeMap;
cubeCamera = new THREE.CubeCamera(0.1, 1000, 2048);
scene.add(cubeCamera);
//相机的移动
controller = new THREE.OrbitControls(camera, renderer.domElement);
controller.target = new THREE.Vector3(0, 0, 0);
light = new THREE.AmbientLight(0xffffff);
light.position.set(-50, -50, -50);
scene.add(light);
}
let obj;
function initObject()
{
let loader = new THREE.STLLoader();
loader.load("../../../asset/LibertStatue.obj.stl",function (bufferGeometry)
{
let material = new THREE.MeshBasicMaterial();
material.envMap=scene.background;
obj = new THREE.Mesh(bufferGeometry,material);
obj.scale.set(50,50,50);
scene.add(obj);
console.log(obj);
});
let cubeMaterial = new THREE.MeshPhongMaterial();
cubeMaterial.envMap = cubeCamera.renderTarget;
let boxGeometry = new THREE.BoxGeometry(3, 400, 400);
let box = new THREE.Mesh(boxGeometry, cubeMaterial);
box.position.set(0, 0, -300);
box.rotation.y -= Math.PI / 2;
scene.add(box);
cubeCamera.position.copy(box.position);
document.getElementById('loading').style.display = 'none';
}
//渲染函数
function render()
{
if(obj) obj.rotation.y+=0.02;
cubeCamera.update(renderer, scene);
stats.update();
renderer.clear();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
//功能函数
function setting()
{
loadFullScreen();
loadAutoScreen(camera, renderer);
loadStats();
}
//运行主函数
function Start()
{
initThree();
initObject();
setting();
render();
}
</script>
</body>
</html>