上节实现了根据全局 store 里的 json 渲染 3d 场景的 mesh:


这节继续来完善。
首先实现点击菜单,添加 Mesh 和 Light 的功能

各个组件都可以从全局 Store 里访问和修改 json:

所以我们只需要在 Menu 组件里点击添加的时候,修改 json 添加对应的 mesh
然后在 Main 组件里同步通知 Three.js 去更新场景就好了。

const { addMesh } = useThreeStore();
function handleClick(e) {
addMesh(e.key);
}
拿到 store 里的 addMesh 方法。
点击菜单项的时候,调用 addMesh 来添加一个 mesh。
然后我们在 Properties 组件里展示下 json:
import { useThreeStore } from "../../store";
function Properties() {
const { data } = useThreeStore();
return <div className="Properties">
<pre>
{JSON.stringify(data, null, 2)}
</pre>
</div>
}
export default Properties;
试一下:

点击添加立方体是有可以的,别的我们还没实现。
我们实现下这 2 种 Mesh 和 2 种 Light 的添加:
改下 store/index.js 里的 addMesh 方法:
addMesh(type) {
function addItem(creator) {
set(state => {
return {
data: {
...state.data,
meshArr: [
...state.data.meshArr,
creator()
]
}
}
})
}
if(type === 'Box') {
addItem(createBox);
} else if(type === 'Cylinder') {
addItem(createCylinder);
}
}
和前面差不多,只不过多了一个 Cylinder 类型。
然后创建 createCylinder 方法:

function createCylinder() {
const newId = Math.random().toString().slice(2, 8);
return {
id: newId,
type: 'Cylinder',
name: 'Cylinder' + newId,
props: {
radiusTop: 200,
radiusBottom: 200,
height: 300,
material: {
color: 'orange',
},
position: {
x: 0,
y: 0,
z: 0
}
}
}
}
store 里添加 mesh 搞定了,我们还需要把它渲染出来:
把之前渲染的逻辑注释掉:

我们直接在组件里写:

const sceneRef = useRef();
sceneRef.current = scene;
首先用 useRef 来保存 scene。
然后在 data 变化的时候去更新 scene:

import { MeshTypes, useThreeStore } from "../../store";
import * as THREE from 'three';
useEffect(() => {
const scene = sceneRef.current;
data.meshArr.forEach(item => {
if(item.type === MeshTypes.Box) {
const { width, height, depth, material: { color }, position} = item.props;
let mesh = scene.getObjectByName(item.name);
if(!mesh) {
const geometry = new THREE.BoxGeometry(width, height, depth);
const material = new THREE.MeshPhongMaterial({
color
});
mesh = new THREE.Mesh(geometry, material);
}
mesh.name = item.name;
mesh.position.copy(position)
scene.add(mesh);
} else if(item.type === MeshTypes.Cylinder) {
const { radiusTop, radiusBottom, height, material: { color }, position} = item.props;
let mesh = scene.getObjectByName(item.name);
if(!mesh) {
const geometry = new THREE.CylinderGeometry(radiusTop, radiusBottom, height);
const material = new THREE.MeshPhongMaterial({
color
});
mesh = new THREE.Mesh(geometry, material);
}
mesh.name = item.name;
mesh.position.copy(position)
scene.add(mesh);
}
})
}, [data]);
data 变化的时候,遍历 data.meshArr 来新增或者更新 mesh。
根据 name 来查找 mesh,如果找到了就直接更新属性,否则就创建一个新的。
(这里写的时候 scene 没类型提示,因为为了简化,我们没用 ts。你可以在 init.js 里写完之后复制过来,或者你可以用 typescript 来写)
把 meshArr 清空:

试下效果:

没啥问题。
然后来做一下选中的功能。
实现点击需要用到 RayCaster,这个我们写过很多次了。

renderer.domElement.addEventListener('click', (e) => {
const y = -((e.offsetY / height) * 2 - 1);
const x = (e.offsetX / width) * 2 - 1;
const rayCaster = new THREE.Raycaster();
rayCaster.setFromCamera(new THREE.Vector2(x, y), camera);
const intersections = rayCaster.intersectObjects(scene.children);
if(intersections.length) {
const obj = intersections[0].object;
obj.material.color.set('green');
}
});
这段逻辑的具体含义如果忘了,可以看一下射线与点击选中 3D 场景的物体 这节。
我们点击选中的物体设置绿色。
试一下:

然后我们用后期描边效果来表示选中:

const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
const v = new THREE.Vector2(window.innerWidth, window.innerWidth);
const outlinePass = new OutlinePass(v, scene, camera);
outlinePass.pulsePeriod = 1;
composer.addPass(outlinePass);
function render(time) {
composer.render();
// renderer.render(scene, camera);
requestAnimationFrame(render);
}
创建效果合成器 EffectComposer,然后添加两个后期通道 RenderPass、OutlinePass。
OutlinePass 设置闪烁周期是 1s
然后在渲染循环里调用 composer.render
这样点击的时候修改 selectedObjects 来给物体添加描边:

if(intersections.length) {
const obj = intersections[0].object;
// obj.material.color.set('green');
outlinePass.selectedObjects = [obj];
} else {
outlinePass.selectedObjects = [];
}
试一下:

这样,描边效果就加上了。
只是明显感觉场景变暗了。
这个是加了后期通道后的常见问题,后期通道那节讲过,加一下伽马校正就好了:

const gammaPass= new ShaderPass(GammaCorrectionShader);
composer.addPass(gammaPass);

这样,颜色就正常了。
案例代码上传了小册仓库
总结
这节我们实现了点击菜单项添加对应 Mesh 的功能,并且加上了点击选中的描边效果。
添加对应 Mesh 就是往 json 里添加对应的对象,然后渲染的时候把它渲染出来。
而选中描边就是通过 RayCaster 和后期通道 OutlinePass 实现的。
下节我们继续来做删除、编辑功能。