WebGL 学习 —-Three.js 学习笔记(5)
点击查看 demo 演示
Demo 地址:https://nsytsqdtn.github.io/d…
简单网格材质 MeshNormalMaterial
MeshNormalMaterial 是一种不受渲染时使用的颜色影响的材质,它只与自己每一个面从内到外的法向量有关。法向量在 webgl 中用处十分广泛,光的反射,以及三维图形的纹理映射都与这个有关。
从图中可以看到,网格的每一面渲染的颜色都是不一样的,如果我们想要在物体表面添加法向量,我们可以使用的 THREE.ArrowHelper 去表示每一个法向量,它的参数为
THREE.ArrowHelper(dir, origin, length, color, headLength, headWidth)
** 其中参数的意义为:
dir: 方向, 默认是法向量
origin: 开始的坐标位置
length:辅助线的长度
color:辅助线的颜色
headLength:头部的长度
headWidth:头部的宽度 **
对于一个球体,要描述它每一个面的法向量,首先需要对它的每一个面进行遍历,取出这个面上的三个顶点(因为 webgl 的面都是三角形,所以是三个顶点),通过 divideScalar(3)这个函数计算它的中心位置,我们就可以在这个中心位置点上,从内向外引出一个 ArrowHelper,来模拟法向量。
for(let i=0;i<sphereGeometry.faces.length;i++){// 在每一个面上面循环
let face = sphereGeometry.faces[i];// 得到每个面的对象
let centroid = new THREE.Vector3();
// 先创建一个 vector3 对象,要使用这个对象找到每个面的中心
centroid.add(sphereGeometry.vertices[face.a]);
// 将这该面的三个顶点的索引传给 sphereGeometry.vertices 找到其顶点的坐标
// 再添加进 centroid
centroid.add(sphereGeometry.vertices[face.b]);
centroid.add(sphereGeometry.vertices[face.c]);
centroid.divideScalar(3);// 三角形的中心点坐标
let arrow = new THREE.ArrowHelper(
face.normal,//face 这个面的法向量
centroid,
2,
0xffcc55,
0.5,
0.5);// 箭头辅助线,相当于把法向量用箭头表示出来
sphere.add(arrow);
}
其中,centroid.add(sphereGeometry.vertices[face.a])这段代码中的 sphereGeometry.vertices 存有几何体的所有顶点信息,通过 [] 索引可以取得其中的某一个顶点。face.a 还有下面的 face.b 和 c 都是该面的顶点索引号,表示这个面是由顶点编号为 face.a,face.b,face.c 的三个顶点所构成的一个三角形(webgl 的面都是三角形),然后我们再计算这三个顶点的中心点。
菜单面板的设置
在菜单面板中设置一些 MeshNormalmaterial 的一些属性,便于去测试这种材质的一些特质
其中:
**this.visible = meshMaterial.visible;// 是否可见
this.wireframe = meshMaterial.wireframe;// 是否以线框的方式渲染物体
this.wireframeWidth = meshMaterial.wireframeLinewidth;// 线框的宽度
this.transparent = meshMaterial.transparent;// 是否透明
this.opacity = meshMaterial.opacity;// 透明度,需要 transparent 为 true 才有效果
this.side = "front";// 边的渲染方式,有三种,前面,后面,还有双面
this.selectMesh = "sphere";// 当前选择的几何体
this.shading = "smooth";// 着色方式,有平面着色和平滑着色,对一个面很平的几何体几乎看不出区别,如正方体 **
function initDatGUI() {
// 设置菜单中需要的参数
controls = new function () {
this.rotationSpeed = 0.02;
this.visible = meshMaterial.visible;// 是否可见
this.wireframe = meshMaterial.wireframe;// 是否以线框的方式渲染物体
this.wireframeWidth = meshMaterial.wireframeLinewidth;// 线框的宽度
this.transparent = meshMaterial.transparent;// 是否透明
this.opacity = meshMaterial.opacity;// 透明度,需要 transparent 为 true 才有效果
this.side = "front";// 边的渲染方式,有三种,前面,后面,还有双面
this.selectMesh = "sphere";// 当前选择的几何体
this.shading = "smooth";// 着色方式,有平面着色和平滑着色,对一个面很平的几何体几乎看不出区别,如正方体
};
let gui = new dat.GUI();
// 将刚刚设置的参数添加到菜单中
let F1 = gui.addFolder("Mesh");
F1.add(controls, "rotationSpeed", 0, 0.1);
F1.add(controls, "visible").onChange(function (e) {meshMaterial.visible = e;});
F1.add(controls, "wireframe").onChange(function (e) {meshMaterial.wireframe = e;});
F1.add(controls, "wireframeWidth",0,10).onChange(function (e) {meshMaterial.wireframeWidth = e;});
F1.add(controls, "transparent").onChange(function (e) {meshMaterial.transparent = e;});
F1.add(controls, "opacity",0,1).onChange(function (e) {meshMaterial.opacity = e;});
F1.add(controls, "side",["front","back","double"]).onChange(function (e) {switch (e) {
case "front":
meshMaterial.side = THREE.FrontSide;
break;
case "back":
meshMaterial.side = THREE.BackSide;
break;
case "double":
meshMaterial.side = THREE.DoubleSide;
break;
}
meshMaterial.needsUpdate = true;// 要在程序中让材质更新需要添加这一句话
});
F1.add(controls, "selectMesh",["sphere","cube","plane"]).onChange(function (e) {
// 先把场景的物体清除,再来添加
scene.remove(cube);
scene.remove(sphere);
scene.remove(plane);
switch (e) {
case "sphere":
scene.add(sphere);
break;
case "cube":
scene.add(cube);
break;
case "plane":
scene.add(plane);
break;
}
});
F1.add(controls, "shading",["flat","smooth"]).onChange(function (e) {switch (e) {
case "flat":
meshMaterial.shading = THREE.FlatShading;
break;
case "smooth":
meshMaterial.shading = THREE.SmoothShading;
break;
}
meshMaterial.needsUpdate = true;// 要在程序中让材质更新需要添加这一句话
});
}
** 注意在程序运行过程中想要改变材质的属性,需要在改完以后,添加一句
meshMaterial.needsUpdate = true,这样才能更新成功。**
360 度全景背景
360 度全景背景能够让人有身临其境的感觉,所有这里的背景使用了全景背景
如果想要使用全景的背景,就需要 6 张 6 个方向的图片来合成一个完整的背景(也可以使用 1 张 6 方向的图片),然后把这些贴图赋值给 scene.background
let urls =[
'image/posx.jpg',
'image/negx.jpg',
'image/posy.jpg',
'image/negy.jpg',
'image/posz.jpg',
'image/negz.jpg'
];// 引入 6 个方向的贴图
let cubeMap = THREE.ImageUtils.loadTextureCube(urls);
scene = new THREE.Scene();
scene.background = cubeMap;
这些图片的需要按照顺序摆放,右左上下后前,否则背景会错乱。
这里给一个全景图片的网站,里面有很多的 360 度风景图,都是 6 张类型的,下载下来解压后就可以直接引入
http://www.humus.name/index.p…
本例子的完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Depth Material Test</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/dat.gui.min.js"></script>
<script src="../../import/SceneUtils.js"></script>
<style type="text/css">
div#WebGL-output {
border: none;
cursor: pointer;
width: 100%;
height: 850px;
background-color: #333333;
}
</style>
</head>
<body onload="Start()">
<div id="WebGL-output"></div>
<script>
let camera, renderer, scene, light;
let controller;
let controls;
let cube, sphere, plane, meshMaterial;
function initThree() {
// 渲染器初始化
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x333333);
document.getElementById("WebGL-output").appendChild(renderer.domElement);// 将渲染添加到 div 中
// 初始化摄像机,这里使用透视投影摄像机
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 10, 100);
camera.position.set(0, 40, 60);
camera.up.x = 0;// 设置摄像机的上方向为哪个方向,这里定义摄像的上方为 Y 轴正方向
camera.up.y = 1;
camera.up.z = 0;
camera.lookAt(0, 0, 0);
// 初始化场景
let urls =[
'image/posx.jpg',
'image/negx.jpg',
'image/posy.jpg',
'image/negy.jpg',
'image/posz.jpg',
'image/negz.jpg'
];// 引入 6 个方向的贴图
let cubeMap = THREE.ImageUtils.loadTextureCube(urls);
scene = new THREE.Scene();
scene.background = cubeMap;
// 相机的移动
controller = new THREE.OrbitControls(camera, renderer.domElement);
controller.target = new THREE.Vector3(0, 0, 0);
light = new THREE.AmbientLight(0x0c0c0c);
scene.add(light);
// add spotlight for the shadows
light = new THREE.SpotLight(0xffffff);
light.position.set(0, 30, 30);
scene.add(light);
}
// 初始化菜单面板
function initDatGUI() {
// 设置菜单中需要的参数
controls = new function () {
this.rotationSpeed = 0.02;
this.visible = meshMaterial.visible;// 是否可见
this.wireframe = meshMaterial.wireframe;// 是否以线框的方式渲染物体
this.wireframeWidth = meshMaterial.wireframeLinewidth;// 线框的宽度
this.transparent = meshMaterial.transparent;// 是否透明
this.opacity = meshMaterial.opacity;// 透明度,需要 transparent 为 true 才有效果
this.side = "front";// 边的渲染方式,有三种,前面,后面,还有双面
this.selectMesh = "sphere";// 当前选择的几何体
this.shading = "smooth";// 着色方式,有平面着色和平滑着色,对一个面很平的几何体几乎看不出区别,如正方体
};
let gui = new dat.GUI();
// 将刚刚设置的参数添加到菜单中
let F1 = gui.addFolder("Mesh");
F1.add(controls, "rotationSpeed", 0, 0.1);
F1.add(controls, "visible").onChange(function (e) {meshMaterial.visible = e;});
F1.add(controls, "wireframe").onChange(function (e) {meshMaterial.wireframe = e;});
F1.add(controls, "wireframeWidth",0,10).onChange(function (e) {meshMaterial.wireframeWidth = e;});
F1.add(controls, "transparent").onChange(function (e) {meshMaterial.transparent = e;});
F1.add(controls, "opacity",0,1).onChange(function (e) {meshMaterial.opacity = e;});
F1.add(controls, "side",["front","back","double"]).onChange(function (e) {switch (e) {
case "front":
meshMaterial.side = THREE.FrontSide;
break;
case "back":
meshMaterial.side = THREE.BackSide;
break;
case "double":
meshMaterial.side = THREE.DoubleSide;
break;
}
meshMaterial.needsUpdate = true;// 要在程序中让材质更新需要添加这一句话
});
F1.add(controls, "selectMesh",["sphere","cube","plane"]).onChange(function (e) {
// 先把场景的物体清除,再来添加
scene.remove(cube);
scene.remove(sphere);
scene.remove(plane);
switch (e) {
case "sphere":
scene.add(sphere);
break;
case "cube":
scene.add(cube);
break;
case "plane":
scene.add(plane);
break;
}
});
F1.add(controls, "shading",["flat","smooth"]).onChange(function (e) {switch (e) {
case "flat":
meshMaterial.shading = THREE.FlatShading;
break;
case "smooth":
meshMaterial.shading = THREE.SmoothShading;
break;
}
meshMaterial.needsUpdate = true;// 要在程序中让材质更新需要添加这一句话
});
}
function initObject() {
// 创建正方体,球和地面的几何体
let cubeGeometry = new THREE.BoxGeometry(10, 10, 10);
let sphereGeometry = new THREE.SphereGeometry(10, 20, 20);
let planeGeometry = new THREE.PlaneGeometry(10, 10, 1, 1);
// 创建一个法向量材质
meshMaterial = new THREE.MeshNormalMaterial();
cube = new THREE.Mesh(cubeGeometry, meshMaterial);
sphere = new THREE.Mesh(sphereGeometry, meshMaterial);
plane = new THREE.Mesh(planeGeometry, meshMaterial);
// 把三者的位置统一
cube.position.set(0,0,0);
sphere.position = cube.position;
plane.position = cube.position;
// 在球的每一个面上显示一个法向量,方便观测这种法向量材质的渲染方式
for(let i=0;i<sphereGeometry.faces.length;i++){// 在每一个面上面循环
let face = sphereGeometry.faces[i];// 得到每个面的对象
let centroid = new THREE.Vector3();// 先创建一个 vector3 对象,要使用这个对象找到每个面的中心,centroid.add(sphereGeometry.vertices[face.a]);
// 将这该面的三个顶点的索引传给 sphereGeom.vertices 找到其顶点的坐标,再添加进 centroid
centroid.add(sphereGeometry.vertices[face.b]);
centroid.add(sphereGeometry.vertices[face.c]);
centroid.divideScalar(3);// 三角形的中心点坐标
let arrow = new THREE.ArrowHelper(
face.normal,
centroid,
2,
0xffcc55,
0.5,
0.5);// 箭头辅助线,相当于把法向量用箭头表示出来
sphere.add(arrow);
}
scene.add(sphere);
}
function rotation() {scene.traverse(function (e) {if (e instanceof THREE.Mesh) {e.rotation.y += controls.rotationSpeed;}
})
}
// 渲染函数
function render() {rotation();
stats.update();
renderer.clear();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
// 功能函数
function setting() {loadFullScreen();
loadAutoScreen(camera, renderer);
loadStats();}
// 运行主函数,敲代码的时候老是敲错,所以改了一个名字,叫 Start 更方便
function Start() {initThree();
initObject();
initDatGUI();
setting();
render();}
</script>
</body>
</html>