ThreeJS 官方案例学习(webgl_decals)
1.效果图
2.源码
<template>
<div>
<div id="container"></div>
</div>
</template>
<script>
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
import { DecalGeometry } from 'three/examples/jsm/geometries/DecalGeometry.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import gsap from 'gsap';
export default {
data() {
return {
container: null,
scene: null,
camera: null,
renderer: null,
controller: null,
stats: null,
mixer: null,
mesh: null,
gui: null,
clock: new THREE.Clock(),
raycaster: new THREE.Raycaster(),
params: null,
intersection: {
intersects: false,
point: new THREE.Vector3(),
normal: new THREE.Vector3()
},
mouse: new THREE.Vector2(),
intersects: [],
mouseHelper: null,
line: null,
position: new THREE.Vector3(),
orientation: new THREE.Euler(),
size: new THREE.Vector3(10, 10, 10),
decals: [],
};
},
mounted() {
this.params = {
minScale: 10,
maxScale: 20,
rotate: true,
clear: () => {
this.removeDecals();
}
}
this.init()
this.animate()
window.addEventListener("resize", this.onWindowSize)
let moved = false;
this.controller.addEventListener('change', () => { moved = true; });
window.addEventListener("pointerdown", () => { moved = false })
window.addEventListener("pointerup", (event) => {
if (moved === false) {
this.checkIntersection(event.clientX, event.clientY)
if (this.intersection.intersects) this.shoot()
}
})
window.addEventListener('pointermove', this.onPointerMove);
},
beforeUnmount() {
console.log('beforeUnmount===============');
this.container = null
this.scene = null
this.camera = null
this.renderer = null
this.controller = null
this.stats = null
this.mixer = null
this.model = null
},
methods: {
init() {
this.container = document.getElementById('container')
this.setScene()
this.setCamera()
this.setRenderer()
this.setController()
this.addHelper()
this.setLight()
this.setGltfLoader()
this.addStatus()
},
setScene() {
this.scene = new THREE.Scene()
},
setCamera() {
this.camera = new THREE.PerspectiveCamera(60, this.container.clientWidth / this.container.clientHeight, 1, 1000)
this.camera.position.z = 120
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.camera.lookAt(new THREE.Vector3(0, 0, 0))
this.scene.add(this.camera)
},
setRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
})
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.sortObjects = false;
this.container.appendChild(this.renderer.domElement);
},
setController() {
this.controller = new OrbitControls(this.camera, this.renderer.domElement);
this.controller.minDistance = 50;
this.controller.maxDistance = 200;
},
addHelper() {
let helper = new THREE.CameraHelper(this.camera);
let axisHelper = new THREE.AxesHelper(150);
let gridHelper = new THREE.GridHelper(100, 30, 0x2C2C2C, 0x888888);
this.mouseHelper = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 10), new THREE.MeshNormalMaterial());
this.mouseHelper.visible = false;
this.scene.add(this.mouseHelper);
},
setPMREMGenerator() {
const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
this.scene.environment = pmremGenerator.fromScene(new RoomEnvironment(this.renderer), 0.04).texture;
},
setLight() {
const ambientLight = new THREE.AmbientLight(0x666666);
this.scene.add(ambientLight);
const dirLight1 = new THREE.DirectionalLight(0xffddcc, 3);
dirLight1.position.set(1, 0.75, 0.5);
this.scene.add(dirLight1);
const dirLight2 = new THREE.DirectionalLight(0xccccff, 3);
dirLight2.position.set(- 1, 0.75, - 0.5);
this.scene.add(dirLight2);
},
addStatus() {
this.stats = new Stats();
this.container.appendChild(this.stats.dom);
},
setGltfLoader() {
let that = this
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints([new THREE.Vector3(), new THREE.Vector3()]);
this.line = new THREE.Line(geometry, new THREE.LineBasicMaterial());
this.scene.add(this.line);
const textureLoader = new THREE.TextureLoader()
const map = textureLoader.load(require("../../../public/model/gltf/LeePerrySmith/Map-COL.jpg"));
map.colorSpace = THREE.SRGBColorSpace;
const specularMap = textureLoader.load('../../../public/models/gltf/LeePerrySmith/Map-SPEC.jpg');
const normalMap = textureLoader.load('../../../public/models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg');
loader.load('/model/gltf/LeePerrySmith/LeePerrySmith.glb', (gltf) => {
that.mesh = gltf.scene.children[0];
that.mesh.material = new THREE.MeshPhongMaterial({
specular: 0x111111,
map: map,
specularMap: specularMap,
shininess: 25,
});
that.scene.add(that.mesh)
that.mesh.scale.set(10, 10, 10)
}, undefined, (err => {
console.error(err)
}))
const gui = new GUI();
gui.add(this.params, 'minScale', 1, 30);
gui.add(this.params, 'maxScale', 1, 30);
gui.add(this.params, 'rotate');
gui.add(this.params, 'clear');
gui.open();
},
onPointerMove(event) {
if (event.isPrimary) {
this.checkIntersection(event.clientX, event.clientY);
}
},
checkIntersection(x, y) {
if (this.mesh === undefined) return
this.mouse.x = (x / window.innerWidth) * 2 - 1;
this.mouse.y = - (y / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera(this.mouse, this.camera);
this.raycaster.intersectObject(this.mesh, false, this.intersects);
if (this.intersects.length > 0) {
const p = this.intersects[0].point
this.mouseHelper.position.copy(p);
this.intersection.point.copy(p);
const n = this.intersects[0].face.normal.clone();
n.transformDirection(this.mesh.matrixWorld);
n.multiplyScalar(10);
n.add(this.intersects[0].point);
this.intersection.normal.copy(this.intersects[0].face.normal);
this.mouseHelper.lookAt(n);
const positions = this.line.geometry.attributes.position;
positions.setXYZ(0, p.x, p.y, p.z);
positions.setXYZ(1, n.x, n.y, n.z);
positions.needsUpdate = true;
this.intersection.intersects = true;
this.intersects.length = 0;
} else {
this.intersection.intersects = false;
}
},
shoot() {
this.position.copy(this.intersection.point);
this.orientation.copy(this.mouseHelper.rotation);
if (this.params.rotate) this.orientation.z = Math.random() * 2 * Math.PI;
const scale = this.params.minScale + Math.random() * (this.params.maxScale - this.params.minScale);
this.size.set(scale, scale, scale);
const textureLoader = new THREE.TextureLoader()
const decalDiffuse = textureLoader.load('/textures/decal/decal-diffuse.png');
decalDiffuse.colorSpace = THREE.SRGBColorSpace;
const decalNormal = textureLoader.load('/textures/decal/decal-normal.jpg');
const decalMaterial = new THREE.MeshPhongMaterial({
specular: 0x444444,
map: decalDiffuse,
normalMap: decalNormal,
normalScale: new THREE.Vector2(1, 1),
shininess: 30,
transparent: true,
depthTest: true,
depthWrite: false,
polygonOffset: true,
polygonOffsetFactor: - 4,
wireframe: false,
});
const material = decalMaterial.clone();
material.color.setHex(Math.random() * 0xffffff);
const m = new THREE.Mesh(new DecalGeometry(this.mesh, this.position, this.orientation, this.size), material);
this.decals.push(m);
this.scene.add(m);
},
removeDecals() {
this.decals.forEach((d) => {
this.scene.remove(d);
});
this.decals.length = 0;
},
onWindowSize() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio)
},
animate() {
const delta = this.clock.getDelta();
requestAnimationFrame(this.animate);
this.controller.update(delta);
this.stats.update();
this.renderer.render(this.scene, this.camera);
},
},
};
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}
</style>