|
@@ -0,0 +1,187 @@
|
|
|
|
+<template>
|
|
|
|
+ <div ref="wrap" class="three-wrap"></div>
|
|
|
|
+</template>
|
|
|
|
+<script setup>
|
|
|
|
+import * as THREE from "three";
|
|
|
|
+import * as SceneUtils from "three/examples/jsm/utils/SceneUtils";
|
|
|
|
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
|
|
|
+import Stats from "three/examples/jsm/libs/stats.module";
|
|
|
|
+import * as dat from "dat.gui";
|
|
|
|
+import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
|
|
+
|
|
|
|
+// 导入HDR加载器
|
|
|
|
+import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
|
|
|
|
+import {
|
|
|
|
+ activeShadow,
|
|
|
|
+ addResizeAdapter,
|
|
|
|
+ getScence,
|
|
|
|
+ initAxes,
|
|
|
|
+ initBoxMesh,
|
|
|
|
+ initCamera,
|
|
|
|
+ initControl,
|
|
|
|
+ initGui,
|
|
|
|
+ initHemiLight,
|
|
|
|
+ initPointLight,
|
|
|
|
+ initPlaneMesh,
|
|
|
|
+ initRender,
|
|
|
|
+ initSphereMesh,
|
|
|
|
+ initStatus,
|
|
|
|
+ startAnimate,
|
|
|
|
+ makeStar,
|
|
|
|
+ initSpotLight,
|
|
|
|
+ getCanvasSize,
|
|
|
|
+} from "./util";
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * @type {import("vue").Ref<HTMLDivElement>}
|
|
|
|
+ */
|
|
|
|
+const wrap = ref();
|
|
|
|
+const gui = initGui();
|
|
|
|
+const status = initStatus();
|
|
|
|
+
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
+ gui.destroy();
|
|
|
|
+});
|
|
|
|
+let render;
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ if (render) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ render = initRender(wrap.value);
|
|
|
|
+ const camera = initCamera(wrap.value);
|
|
|
|
+ const scene = getScence();
|
|
|
|
+
|
|
|
|
+ const axes = initAxes();
|
|
|
|
+
|
|
|
|
+ const control = initControl(camera, render);
|
|
|
|
+
|
|
|
|
+ const plane = initPlaneMesh();
|
|
|
|
+ const box = initBoxMesh();
|
|
|
|
+ const sphere = initSphereMesh();
|
|
|
|
+ const plight = initPointLight();
|
|
|
|
+ const star = makeStar();
|
|
|
|
+ const slight = initSpotLight();
|
|
|
|
+
|
|
|
|
+ // 环境贴图
|
|
|
|
+ const rgbeloader = new RGBELoader();
|
|
|
|
+ rgbeloader.load(require("./assets/meadow_2k.hdr"), (envMap) => {
|
|
|
|
+ envMap.mapping = THREE.EquirectangularReflectionMapping;
|
|
|
|
+ scene.background = envMap;
|
|
|
|
+ scene.environment = envMap;
|
|
|
|
+ plane.material.envMap = envMap;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 纹理贴图
|
|
|
|
+ const textureLoader = new THREE.TextureLoader();
|
|
|
|
+ textureLoader.load(
|
|
|
|
+ require("./assets/Tiles130_1K-PNG/Tiles130_1K-PNG_Color.png"),
|
|
|
|
+ (texture) => {
|
|
|
|
+ plane.material.map = texture;
|
|
|
|
+ sphere.material.map = texture;
|
|
|
|
+ plane.material.transparent = true;
|
|
|
|
+ sphere.material.transparent = true;
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // aoMap 环境遮挡贴图
|
|
|
|
+
|
|
|
|
+ box.position.set(0, 5, 0);
|
|
|
|
+ sphere.position.set(20, 10, 10);
|
|
|
|
+ plight.position.set(40, 30, 0);
|
|
|
|
+ star.position.set(0, 20, 0);
|
|
|
|
+ slight.position.set(-30, 20, 0);
|
|
|
|
+ activeShadow(box, plane, sphere, plight, star, slight);
|
|
|
|
+
|
|
|
|
+ scene.add(box).add(plane).add(sphere).add(plight).add(star).add(slight);
|
|
|
|
+
|
|
|
|
+ wrap.value.appendChild(render.domElement);
|
|
|
|
+ wrap.value.appendChild(status.dom);
|
|
|
|
+
|
|
|
|
+ scene.add(axes);
|
|
|
|
+ render.render(scene, camera);
|
|
|
|
+ addResizeAdapter(camera, render, wrap.value);
|
|
|
|
+
|
|
|
|
+ const raycaster = new THREE.Raycaster();
|
|
|
|
+ const mouse = new THREE.Vector2();
|
|
|
|
+ wrap.value.addEventListener("click", (e) => {
|
|
|
|
+ const { layerX, layerY } = e;
|
|
|
|
+
|
|
|
|
+ mouse.x = layerX / (getCanvasSize(wrap.value).width / 2) - 1;
|
|
|
|
+ mouse.y = -(layerY / (getCanvasSize(wrap.value).height / 2) - 1);
|
|
|
|
+
|
|
|
|
+ // 通过摄像机和鼠标坐标更新射线
|
|
|
|
+ raycaster.setFromCamera(mouse, camera);
|
|
|
|
+
|
|
|
|
+ const intersects = raycaster.intersectObjects(scene.children);
|
|
|
|
+
|
|
|
|
+ scene.children.forEach((it) => {
|
|
|
|
+ if (it.isMesh) {
|
|
|
|
+ it.material._select = false;
|
|
|
|
+ if (it.material._originColor) {
|
|
|
|
+ it.material.color.set(it.material._originColor);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ intersects.forEach((it, index) => {
|
|
|
|
+ if (it.object.isMesh) {
|
|
|
|
+ if (index === 0 && it.object.material._select !== true) {
|
|
|
|
+ it.object.material._select = true;
|
|
|
|
+ it.object.material._originColor = it.object.material.color.getHex();
|
|
|
|
+ it.object.material.color.set(0xff0000);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const starFolder = gui.addFolder("星星");
|
|
|
|
+ starFolder.add(star.position, "x").min(-50).max(50).name("坐标X");
|
|
|
|
+ starFolder.add(star.position, "y").min(20).max(100).name("坐标Y");
|
|
|
|
+ starFolder
|
|
|
|
+ .add(star.material, "emissiveIntensity")
|
|
|
|
+ .min(1)
|
|
|
|
+ .max(10)
|
|
|
|
+ .name("自发光强度");
|
|
|
|
+
|
|
|
|
+ starFolder
|
|
|
|
+ .add(star.children[0], "intensity")
|
|
|
|
+ .min(0)
|
|
|
|
+ .max(1000)
|
|
|
|
+ .name("光照强度");
|
|
|
|
+
|
|
|
|
+ const slightFolder = gui.addFolder("聚光灯");
|
|
|
|
+ slightFolder
|
|
|
|
+ .add(slight.position, "x")
|
|
|
|
+ .min(-50)
|
|
|
|
+ .max(50)
|
|
|
|
+ .name("聚光灯X")
|
|
|
|
+ .onChange(() => {
|
|
|
|
+ slight.target.position.set(0, 0, 0);
|
|
|
|
+ });
|
|
|
|
+ slightFolder
|
|
|
|
+ .add(slight.position, "y")
|
|
|
|
+ .min(0)
|
|
|
|
+ .max(50)
|
|
|
|
+ .name("聚光灯Y")
|
|
|
|
+ .onChange(() => {
|
|
|
|
+ slight.target.position.set(0, 0, 0);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ slightFolder.add(slight, "angle").min(0).max(Math.PI).name("angle");
|
|
|
|
+ slightFolder.add(slight, "penumbra").min(0).max(1).name("penumbra");
|
|
|
|
+ slightFolder.add(slight, "decay").min(0).max(10).name("decay");
|
|
|
|
+
|
|
|
|
+ startAnimate(() => {
|
|
|
|
+ status.update();
|
|
|
|
+ control.update();
|
|
|
|
+ render.render(scene, camera);
|
|
|
|
+ });
|
|
|
|
+});
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style lang="less" scoped>
|
|
|
|
+.three-wrap {
|
|
|
|
+ position: relative;
|
|
|
|
+}
|
|
|
|
+</style>
|