三、【Three.js】three.js之几何体与网格
three.js中的几何体基本上可以看成三维空间中的点集和将这些点连起来的面。如立方体是由八个顶点及其连接这些顶点的六个面组成。
一、定义一个几何体
通过定义顶点和面来自定义创建一个几何体:
// 定义点
let verties = [
new THREE.Vector3(1,4,1),
new THREE.Vector3(1,4,-1),
new THREE.Vector3(1,0,1),
new THREE.Vector3(1,0,-1),
new THREE.Vector3(-1,4,-1),
new THREE.Vector3(-1,4,1),
new THREE.Vector3(-1,0,-1),
new THREE.Vector3(-1,0,1)
];
// 连接顶点,定义面
let faces = [
new THREE.Face3(0,2,1),
new THREE.Face3(2,3,1),
new THREE.Face3(4,6,5),
new THREE.Face3(6,7,5),
new THREE.Face3(4,5,1),
new THREE.Face3(5,0,1),
new THREE.Face3(7,6,2),
new THREE.Face3(6,3,2),
new THREE.Face3(5,7,0),
new THREE.Face3(7,2,0),
new THREE.Face3(1,3,4),
new THREE.Face3(3,6,4)
];
let geometry = new THREE.Geometry();
geometry.vertices = verties;
geometry.faces = faces;
geometry.computeFaceNormals();
// 创建材质
let materials = [
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}),
new THREE.MeshLambertMaterial({opacity: 0.6, color: 0x44ff44, transparent: true})
];
// 根据几何体与材质形成要显示的对象
let mesh = createMultiMaterialObject(geometry, materials);
mesh.castShadow = true;
mesh.children.forEach(item => item.castShadow = true);
scene.add(mesh);
下面是一个可以更改顶点坐标的动态几何体
import '../../stylus/index.styl'
import * as THREE from 'three';
import * as dat from 'dat.gui';
import {initStats,initTrackballControls, createMultiMaterialObject} from '../../util/util';
function init() {
let stats = initStats();
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setClearColor(0x000000);
renderer.shadowMap.enabled = true;
scene.add(camera);
let axes = new THREE.AxesHelper(30);
scene.add(axes);
let planeGeometry = new THREE.PlaneGeometry(60, 40,1,1);
let planeMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff
});
let plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.receiveShadow = true;
scene.add(plane);
let ambientLight = new THREE.AmbientLight(0x494949);
scene.add(ambientLight);
let verties = [
new THREE.Vector3(1,4,1),
new THREE.Vector3(1,4,-1),
new THREE.Vector3(1,0,1),
new THREE.Vector3(1,0,-1),
new THREE.Vector3(-1,4,-1),
new THREE.Vector3(-1,4,1),
new THREE.Vector3(-1,0,-1),
new THREE.Vector3(-1,0,1)
];
let faces = [
new THREE.Face3(0,2,1),
new THREE.Face3(2,3,1),
new THREE.Face3(4,6,5),
new THREE.Face3(6,7,5),
new THREE.Face3(4,5,1),
new THREE.Face3(5,0,1),
new THREE.Face3(7,6,2),
new THREE.Face3(6,3,2),
new THREE.Face3(5,7,0),
new THREE.Face3(7,2,0),
new THREE.Face3(1,3,4),
new THREE.Face3(3,6,4)
];
let geometry = new THREE.Geometry();
geometry.vertices = verties;
geometry.faces = faces;
geometry.computeFaceNormals();
let materials = [
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}),
new THREE.MeshLambertMaterial({opacity: 0.6, color: 0x44ff44, transparent: true})
];
let mesh = createMultiMaterialObject(geometry, materials);
mesh.castShadow = true;
mesh.children.forEach(item => item.castShadow = true);
scene.add(mesh);
let spotLight = new THREE.SpotLight(0xffffff,1,150,120);
// 阴影贴图设置,值越大,阴影越清晰,占用资源也越大
spotLight.shadow.mapSize.height = 2048;
spotLight.shadow.mapSize.width = 2048;
spotLight.position.set(-40, 30, 30);
spotLight.castShadow = true;
spotLight.lookAt(mesh);
scene.add(spotLight);
camera.position.x = -20;
camera.position.y = 25;
camera.position.z = 20;
camera.lookAt(new THREE.Vector3(5, 0, 0));
document.body.appendChild(renderer.domElement);
let trackballControls = initTrackballControls(camera, renderer);
let controlPoints = [];
controlPoints.push(addControl(3, 5, 3));
controlPoints.push(addControl(3, 5, 0));
controlPoints.push(addControl(3, 0, 3));
controlPoints.push(addControl(3, 0, 0));
controlPoints.push(addControl(0, 5, 0));
controlPoints.push(addControl(0, 5, 3));
controlPoints.push(addControl(0, 0, 0));
controlPoints.push(addControl(0, 0, 3));
function render () {
stats.update();
trackballControls.update();
let verties = [];
controlPoints.forEach(point => {
verties.push(new THREE.Vector3(point.x, point.y, point.z));
});
mesh.children.forEach(function (e) {
e.geometry.vertices = verties;
e.geometry.verticesNeedUpdate = true;
e.geometry.computeFaceNormals();
delete e.geometry.__directGeometry
});
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
function addControl(x,y,z){
return new function(){
this.x = x;
this.y = y;
this.z = z;
}
}
let gui = new dat.GUI();
gui.add(new function(){
this.clone = function(){
let clonedGeometry = mesh.children[0].geometry.clone();
let materials = [
new THREE.MeshLambertMaterial({opacity: 0.8, color: 0xff44ff, transparent: true}),
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})
];
let mesh2 = createMultiMaterialObject(clonedGeometry, materials);
mesh2.children.forEach(function (e) {
e.castShadow = true
});
mesh2.translateX(5);
mesh2.translateZ(5);
mesh2.name = "clone";
scene.remove(scene.getChildByName("clone"));
scene.add(mesh2);
}
}, 'clone');
for(let i = 0; i < 8; i++) {
let folder = gui.addFolder(`顶点${i+1}`);
folder.add(controlPoints[i], 'x', -10, 10);
folder.add(controlPoints[i], 'y', -10, 10);
folder.add(controlPoints[i], 'z', -10, 10);
}
}
window.onload = init();
完整示例:https://github.com/MAXLZ1/threejs_demo
二、网格对象
一个网格由一个几何体,以及一个或多个材质组成。例如上面创建的mesh。
网格对象的属性及方法:
属性(方法) | 说明 |
---|---|
position | 该属性决定该对象相对于父对象的位置,通常父对象是THREE.Scene对象或者是THREE.Object3D对象 |
rotation | 通过该属性设置绕每个轴的旋转角度 |
scale | 通过该属性设置在x/y/z轴方向的缩放 |
translateX(amount) | 沿x轴平移amount距离 |
translateY(amount) | 沿y轴平移amount距离 |
translateZ(amount) | 沿z轴平移amount距离 |
visible | 设置是否可见,false不会被渲染到场景 |
2.1 属性OR方法示例
import '../../stylus/index.styl'
import * as THREE from 'three';
import * as dat from 'dat.gui';
import {initTrackballControls, initStats} from '../../util/util'
function init(){
let stats = initStats();
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 100);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
scene.add(camera);
let axes = new THREE.AxesHelper(30);
scene.add(axes);
let planeGeometry = new THREE.PlaneGeometry(60, 40,1,1);
let planeMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff
});
let plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5*Math.PI;
plane.position.x = 0;
plane.position.y = 0;
plane.position.z = 0;
plane.receiveShadow = true;
scene.add(plane);
let cubeGeometry = new THREE.BoxGeometry(5,10,5);
let cubeMaterial = new THREE.MeshLambertMaterial({
color: 0x3896fe,
opacity: .6
});
let cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = 2.5;
cube.position.y = 5;
cube.position.z = 2.5;
cube.castShadow = true;
scene.add(cube);
let spotLight = new THREE.SpotLight(0xffffff,1,150,120);
spotLight.shadow.mapSize = new THREE.Vector2(2048,2048);
spotLight.position.set(-40, 40, 30);
spotLight.castShadow = true;
scene.add(spotLight);
let ambientLight = new THREE.AmbientLight(0x494949);
scene.add(ambientLight);
camera.position.x = -30;
camera.position.y = 20;
camera.position.z = 30;
camera.lookAt(scene.position);
document.body.appendChild(renderer.domElement);
// 添加控制变量
let controls = new function(){
this.positionX = 2.5;
this.positionY = 5;
this.positionZ = 2.5;
this.rotationX = 0;
this.rotationY = 0;
this.rotationZ = 0;
this.scaleX = 1;
this.scaleY = 1;
this.scaleZ = 1;
this.translateX = 0;
this.translateY = 0;
this.translateZ = 0;
this.visible = true;
this.translate = function() {
cube.translateX(controls.translateX);
cube.translateY(controls.translateY);
cube.translateZ(controls.translateZ);
// 平移之后位置发生变化,重新给位置赋值
controls.positionX = cube.position.x;
controls.positionY = cube.position.y;
controls.positionZ = cube.position.z;
};
}
let gui = new dat.GUI();
let guiPosition = gui.addFolder('position');
let posX = guiPosition.add(controls, 'positionX', -10, 10).listen();
let posY = guiPosition.add(controls, 'positionY', 5, 10).listen();
let posZ = guiPosition.add(controls, 'positionZ', -10, 10).listen();
posX.onChange(value => cube.position.x = value);
posY.onChange(value => cube.position.y = value);
posZ.onChange(value => cube.position.z = value);
let guiRotation = gui.addFolder('rotation');
guiRotation.add(controls, 'rotationX', -4, 4);
guiRotation.add(controls, 'rotationY', -4, 4);
guiRotation.add(controls, 'rotationZ', -4, 4);
let guiScale = gui.addFolder('scale');
guiScale.add(controls, 'scaleX', 0, 3);
guiScale.add(controls, 'scaleY', 0, 3);
guiScale.add(controls, 'scaleZ', 0, 3);
let guiTranslate = gui.addFolder('translate');
guiTranslate.add(controls, 'translateX', -10, 10);
guiTranslate.add(controls, 'translateY', 5, 10);
guiTranslate.add(controls, 'translateZ', -10, 10);
guiTranslate.add(controls, 'translate');
gui.add(controls, 'visible');
let trackballControls = initTrackballControls(camera, renderer);
function render(){
stats.update();
trackballControls.update();
cube.rotation.x = controls.rotationX;
cube.rotation.y = controls.rotationY;
cube.rotation.z = controls.rotationZ;
cube.scale.set(controls.scaleX, controls.scaleY, controls.scaleZ);
cube.visible = controls.visible;
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
}
window.onload = init();