自定義頂點建立幾何體與克隆 Three.js本身已經有很多的網格模型,基本已經夠我們的使用,但是如果我們還是想自己根據頂點坐標來建立幾何模型的話,Three.js也是可以的。 基本效果如圖: "點擊查看demo演示" demo演示:https://nsytsqdtn.github.io/demo/v ...
自定義頂點建立幾何體與克隆
Three.js本身已經有很多的網格模型,基本已經夠我們的使用,但是如果我們還是想自己根據頂點坐標來建立幾何模型的話,Three.js也是可以的。
基本效果如圖:
點擊查看demo演示
demo演示:https://nsytsqdtn.github.io/demo/vertices/vertices
實際上出於性能的考慮,three.js是認為我們的幾何體在整個生命周期中是不會改變的,但是我們還是想使用dat.gui.js去實時更新我們自定義幾何體的頂點信息。
當頂點信息發生變化時,我們就需要使用
geometry.verticesNeedUpdate = true;
但是在每一幀渲染完後這個值又會變為false,所以我們需要每次渲染中都更新這個值。
完整代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Three.js</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#canvas-frame {
border: none;
cursor: pointer;
width: 100%;
height: 850px;
background-color: #333333;
}
</style>
</head>
<body onload="threeStart()">
<div id="canvas-frame"></div>
<script>
let renderer, camera, scene;
let controller;
let controls;
let vertices;
let faces;
let controlPoints = [];
let geom;
let mesh;
//初始化渲染器
function initThree() {
renderer = new THREE.WebGLRenderer({
antialias: true
});//定義渲染器
renderer.setSize(window.innerWidth, window.innerHeight);//設置渲染的寬度和高度
document.getElementById("canvas-frame").appendChild(renderer.domElement);//將渲染器加在html中的div裡面
renderer.setClearColor(0x333333, 1.0);//渲染的顏色設置
renderer.shadowMapEnabled = true;//開啟陰影,預設是關閉的,太影響性能
renderer.shadowMapType = THREE.PCFSoftShadowMap;//陰影的一個類型
camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight, 1, 10000);//perspective是透視攝像機,這種攝像機看上去畫面有3D效果
//攝像機的位置
camera.position.x = 10;
camera.position.y = 15;
camera.position.z = 15;
camera.up.x = 0;
camera.up.y = 1;//攝像機的上方向是Y軸
camera.up.z = 0;
camera.lookAt(0, 0, 0);//攝像機對焦的位置
//這三個參數共同作用才能決定畫面
scene = new THREE.Scene();
let light = new THREE.SpotLight(0xffffff, 1.0, 0);//點光源
light.position.set(-40, 60, -10);
light.castShadow = true;//開啟陰影
light.shadowMapWidth = 8192;//陰影的解析度,可以不設置對比看效果
light.shadowMapHeight = 8192;
scene.add(light);
light = new THREE.AmbientLight(0xcccccc, 0.2);//環境光,如果不加,點光源照不到的地方就完全是黑色的
scene.add(light);
cameraControl();
vertices = [
new THREE.Vector3(1, 3, 1),
new THREE.Vector3(1, 3, -1),
new THREE.Vector3(1, -1, 1),
new THREE.Vector3(1, -1, -1),
new THREE.Vector3(-1, 3, -1),
new THREE.Vector3(-1, 3, 1),
new THREE.Vector3(-1, -1, -1),
new THREE.Vector3(-1, -1, 1)
];//頂點坐標,一共8個頂點
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),
];//頂點索引,每一個面都會根據頂點索引的順序去繪製線條
geom = new THREE.Geometry();
geom.vertices = vertices;
geom.faces = faces;
geom.computeFaceNormals();//計演算法向量,會對光照產生影響
//兩個材質放在一起使用
let materials = [
new THREE.MeshLambertMaterial({opacity: 0.6, color: 0x44ff44, transparent: true}),//透明度更改
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})//線條材質,讓觀察更直觀一點
];
//創建多材質對象,要引入SceneUtils.js文件,如果只有一個材質就不需要這個函數
mesh = THREE.SceneUtils.createMultiMaterialObject(geom, materials);
mesh.children.forEach(function (e) {
e.castShadow = true
});
scene.add(mesh);
initDat();
}
//可視化面板
function initDat() {
function addControl(x, y, z) {
controls = new function () {
this.x = x;
this.y = y;
this.z = z;
};
return controls;
}
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));
//克隆一個幾何體
let addClone = new function () {
this.clone = function () {
let clonedGeometry = mesh.children[0].geometry.clone();
let materials = [
new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}),
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})
];
let mesh2 = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);
mesh2.children.forEach(function (e) {
e.castShadow = true
});
mesh2.translateX(Math.random()*4+3);
mesh2.translateZ(Math.random()*4+3);
mesh2.name = "clone";
//刪掉場景中已經存在的克隆體,再重新創建一個
scene.remove(scene.getChildByName("clone"));
scene.add(mesh2);
}
};
let gui = new dat.GUI();
gui.add(addClone, 'clone');
for (let i = 0; i < 8; i++) {
let f1 = gui.addFolder('Vertices ' + (i + 1));//把每個頂點的三個坐標都收攏在一個Folder裡面,更加美觀方便
f1.add(controlPoints[i], 'x', -10, 10);
f1.add(controlPoints[i], 'y', -10, 10);
f1.add(controlPoints[i], 'z', -10, 10);
}
}
// 攝像機的控制,可以採用滑鼠拖動來控制視野
function cameraControl() {
controller = new THREE.OrbitControls(camera, renderer.domElement);
controller.target = new THREE.Vector3(0, 0, 0);
}
let plane;
//初始化物體
function initObject() {
//定義了一個地面
let planeGeometry = new THREE.PlaneGeometry(100, 100, 1, 1);
let planeMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff,
});
plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15;
plane.receiveShadow = true;//開啟地面的接收陰影
scene.add(plane);//添加到場景中
// initCustomObj();
}
//定義的一個功能文件
function initSetting() {
loadAutoScreen(camera, renderer);
loadFullScreen();
loadStats();
}
//動畫
function render() {
stats.update();
//單材質幾何體要更新頂點的話使用這一段語句
// for (let i = 0; i < 8; i++) {
// console.log(mesh);
// mesh.geometry.vertices[i].set(controlPoints[i].x, controlPoints[i].y, controlPoints[i].z);
// mesh.geometry.verticesNeedUpdate = true;
// mesh.geometry.computeFaceNormals();
// }
let vertices = [];
for (let i = 0; i < 8; i++) {
vertices.push(new THREE.Vector3(controlPoints[i].x, controlPoints[i].y, controlPoints[i].z));
}
mesh.children.forEach(function (e) {
e.geometry.vertices = vertices;
e.geometry.verticesNeedUpdate = true;//通知頂點更新
e.geometry.elementsNeedUpdate = true;//特別重要,通知線條連接方式更新
e.geometry.computeFaceNormals();
});
requestAnimationFrame(render);
renderer.render(scene, camera);
}
//主函數
function threeStart() {
initThree();
initObject();
initSetting();
render();
}
</script>
</body>
</html>
特別要註意的是
在頂點發生變化時,如果是多材質對象的話,需要使用遍歷每一個子對象來進行更新頂點數據。並且不僅要更新頂點,還要更新線條的連接方式geometry.elementsNeedUpdate = true,否則是沒有效果的。(甚至嘗試了一下不更新頂點,只更新線條也是可以達到實時更新的效果)
let vertices = [];
for (let i = 0; i < 8; i++) {
vertices.push(new THREE.Vector3(controlPoints[i].x, controlPoints[i].y, controlPoints[i].z));
}
mesh.children.forEach(function (e) {
e.geometry.vertices = vertices;
e.geometry.verticesNeedUpdate = true;//通知頂點更新
e.geometry.elementsNeedUpdate = true;//特別重要,通知線條連接方式更新
e.geometry.computeFaceNormals();
});
如果是單一的材質幾何體,就不需要去遍歷每一個子物體,直接把幾何體的每一個頂點值更改,然後在通知頂點更新,就可以了。
//單材質幾何體要更新頂點的話使用這一段語句
for (let i = 0; i < 8; i++) {
console.log(mesh);
mesh.geometry.vertices[i].set(controlPoints[i].x, controlPoints[i].y, controlPoints[i].z);
mesh.geometry.verticesNeedUpdate = true;
mesh.geometry.computeFaceNormals();
}
註:
老版本的three.js,SceneUtils是沒有單獨拿出來作為一個js文件的,是直接寫在three.js里。
而且使用69版本的three.js時,不需要更新線條的連接方式也可以實現實時更新。但是103版本試了很多次,都不行。
另外,使用的OrbitControls.js和dat.gui.min.js最好都是和自己用的Three.js版本要一致,否則可能會報錯。有一些教程的示常式序版本可能就比較舊了,如果直接拿來用可能會出問題,註意分辨一下。