携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
官方案例链接: three.js examples (threejs.org)
效果预览
步骤拆分
- 定义变量,定义场景,相机,渲染器,操作平面,射线选取,预放置材质,已放置材质的变量
- 初始化场相机,定义相机类型为透视相机,视野范围为45度,宽高比为屏幕宽高比,设置近视距离为1,最远观测距离为1000
- 初始化场景,设置场景的颜色为0xf0f0f0
- 初始化与预放置材质,初始化该材质为基础网格材质,这种材质不会反射光线,并且在传递参数的时候设置这个材质为透明材质,当满足某个条件的时候他才不透明。
- 初始化放置材质,设置长宽高为50,设置他为一种非光泽化的镜面材质,MeshLambertMaterial,这种材质可以很好的模拟一些表面,但是不能模拟具有镜面高光的光泽表面。
- 初始化网格辅助器,设置网格辅助器的宽度,和每一行分成了多少个格子,然后加入到场景当中去。
- 初始化镭射选取器和鼠标指针Pointer。
- 初始化一个实际的可以被访问到的平面,然后将该平面覆盖网格辅助器
- 初始化光照,设置场景反射光为0x606060,设置直射光,直射光设置为白光。
- 初始化渲染器,渲染器为WebGL渲染器,将渲染器的domElement添加到页面中去。
- 初始化监听器,鼠标移动监听器,鼠标点击监听器,键盘点击监听器,键盘抬起监听器。
- 鼠标移动: 设置pointer的x,y与鼠标移动到的位置相对应,然后通过摄像机和鼠标的位置更新镭射器的位置。获取镭射器光线照射到了哪个场景中的材质,如果说实际选取到了某个材质的话,将他调整位置到gridHelper的格子上显示。
- 鼠标点击:大体步骤和鼠标移动一致。区别在于我们会判断是否鼠标点击的时候按下了键盘上的shift按键,如果按下了的话,那么我们就走删除逻辑,否则我们就走新增逻辑
- 键盘点击: 判断是否按下shift,isShift这个变量设置为true
- 键盘抬起:判断是否按下shift,isShift这个变量设置为false
构建代码模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three'
let camera,scene,renderer
let plane
let pointer,raycaster,isShiftDown=false;
let cubeGeo,cubeMaterial
const objects=[]
init()
render()
function init(){
}
function render(){
}
</script>
</body>
</html>
初始化相机
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(500, 800, 1300);
camera.lookAt(0, 0, 0);
初始化场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
初始化浮动预选方块
const rollOverGeo = new THREE.BoxGeometry(50, 50, 50);
rollOverMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, opacity: 0.5, transparent: true });
rollOverMesh = new THREE.Mesh(rollOverGeo, rollOverMaterial);
scene.add(rollOverMesh);
初始化实际放置方块
cubeGeo = new THREE.BoxGeometry(50, 50, 50);
cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xfeb74c, map: new THREE.TextureLoader().load('./textures/square-outline-textured.png') });
初始化网格辅助器
const gridHelper = new THREE.GridHelper(1000, 20);
scene.add(gridHelper);
初始化镭射选点&鼠标指针
raycaster = new THREE.Raycaster();
pointer = new THREE.Vector2();
初始化不可见的平面对象
const geometry = new THREE.PlaneGeometry(1000, 1000);
geometry.rotateX(- Math.PI / 2);
plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ visible: false }));
scene.add(plane);
objects.push(plane);
初始化场景灯光
const ambientLight = new THREE.AmbientLight(0x606060);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 0.75, 0.5).normalize();
scene.add(directionalLight);
初始化渲染器,场景渲染到页面
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
增加事件监听器
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerdown', onPointerDown);
document.addEventListener('keydown', onDocumentKeyDown);
document.addEventListener('keyup', onDocumentKeyUp);
鼠标指针预选取事件
function onPointerMove(event) {
pointer.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(objects, false);
if (intersects.length > 0) {
const intersect = intersects[0];
console.log(intersect)
rollOverMesh.position.copy(intersect.point).add(intersect.face.normal);
rollOverMesh.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);
render();
}
}
鼠标点击放置事件
function onPointerDown(event) {
pointer.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(objects, false);
if (intersects.length > 0) {
const intersect = intersects[0];
// delete cube
if (isShiftDown) {
if (intersect.object !== plane) {
scene.remove(intersect.object);
objects.splice(objects.indexOf(intersect.object), 1);
}
// create cube
} else {
const voxel = new THREE.Mesh(cubeGeo, cubeMaterial);
voxel.position.copy(intersect.point).add(intersect.face.normal);
voxel.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);
scene.add(voxel);
objects.push(voxel);
}
render();
}
}
键盘按键按下事件
function onDocumentKeyDown(event) {
switch (event.keyCode) {
case 16: isShiftDown = true; break;
}
}
键盘按键松开事件
function onDocumentKeyUp(event) {
switch (event.keyCode) {
case 16: isShiftDown = false; break;
}
}
思考这个场景中使用到的ThreeJS API
初始化灯光
- 直射光:
directionalLight.position.set(1, 0.75, 0.5).normalize();- normalize: 将该向量转换为单位向量(unit vector), 也就是说,将该向量的方向设置为和原向量相同,但是其长度
镭射选取器
- 射线与鼠标同步: raycaster.setFromCamera(pointer, camera);
- setFromCamera: 将pointer也就是鼠标指针的位置往场景中照射一道射线,射线穿过物体,然后进行选取。
- 获取射线穿过的物体: raycaster.intersectObjects
- intersectObjects: 射线穿过的物品
向量设置
- 设置预选取方块的位置
rollOverMesh.position.copy(intersect.point).add(intersect.face.normal);- copy: 相当于将intersect的vector向量赋值给rollOverMesh的position
- add:将intersect的face.normal这个点的x|y|z都依次加到rollOverMesh的x|y|z上面去
rollOverMesh.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25);- divideScalar:除法,x|y|z都除以50
- floor: 向下取整
- multiplyScalar: 乘法,x|y|z都乘50
- addScalar:加法,x|y|z都加25
在场景中删除/新增操作
-
判断是否按下Shift
- 如果说按下Shift再进行点击,那么就是删除
- 没有按下Shift进行点击,那么就是新增。
-
删除: 先从场景中移除元素,然后再在Objects数组中移除元素
- 从场景中移除元素:
scene.remove(intersect.object); - 从保存的元素数组中移除元素:
objects.splice(objects.indexOf(intersect.object), 1);
- 从场景中移除元素:
-
新增: 将预选取的代码复制一份然后直接添加到场景中去就好了。
const voxel = new THREE.Mesh(cubeGeo, cubeMaterial); voxel.position.copy(intersect.point).add(intersect.face.normal); voxel.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25); scene.add(voxel); objects.push(voxel);
【ThreeJS掘金最通俗入门指南-像素绘画案例】 到此结束,如果您觉得对您有帮助的话,可以点赞收藏一下哦。您的支持就是对我最大的鼓励,能让我得到更多的推送流量,帮助到更多的人,最后感谢您花费宝贵的时间认真的阅读这篇文章。如果说您有什么需要我改进的建议欢迎到评论区一起和我讨论❤❤❤