ThreeJS 官方案例学习(webgl_clipping_stencil)
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 { 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,
model: null,
clock: new THREE.Clock(),
params: {
animate: true,
planeX: {
constant: 0,
negated: false,
displayHelper: false,
},
planeY: {
constant: 0,
negated: false,
displayHelper: false,
},
planeZ: {
constant: 0,
negated: false,
displayHelper: false,
},
},
planeHelpers: null,
planeObjects: [],
planes: [
new THREE.Plane(new THREE.Vector3(- 1, 0, 0), 0),
new THREE.Plane(new THREE.Vector3(0, - 1, 0), 0),
new THREE.Plane(new THREE.Vector3(0, 0, - 1), 0),
],
};
},
mounted() {
this.init()
this.animate()
window.addEventListener("resize", this.onWindowSize)
},
beforeUnmount() {
console.log('beforeUnmount===============');
this.container = null
this.scene = null
this.camera = null
this.renderer = null
this.controller = null
this.stats = null
this.object = null
},
methods: {
init() {
this.container = document.getElementById('container')
this.startTime = Date.now();
this.setScene()
this.setCamera()
this.setRenderer()
this.setController()
this.addHelper()
this.setLight()
this.setMesh()
this.addStatus()
},
setScene() {
this.scene = new THREE.Scene()
},
setCamera() {
this.camera = new THREE.PerspectiveCamera(36, this.container.clientWidth / this.container.clientHeight, 1, 100)
this.camera.position.set(2, 2, 2)
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,
logarithmicDepthBuffer: true,
})
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.shadowMap.enabled = true;
this.renderer.localClippingEnabled = true;
this.renderer.setClearColor(0x263238);
this.container.appendChild(this.renderer.domElement);
},
setController() {
this.controller = new OrbitControls(this.camera, this.renderer.domElement);
this.controller.minDistance = 2;
this.controller.maxDistance = 20;
this.controller.enableDamping = true;
this.controller.dampingFactor = 0.04;
},
setMesh() {
const geometry = new THREE.TorusKnotGeometry(0.4, 0.15, 220, 60);
this.object = new THREE.Group();
this.scene.add(this.object);
const planeGeom = new THREE.PlaneGeometry(4, 4);
for (var i = 0; i < 3; i++) {
const poGroup = new THREE.Group();
const plane = this.planes[i];
const stencilGroup = this.createPlaneStencilGroup(geometry, plane, i + 1);
const planeMat = new THREE.MeshStandardMaterial({
color: 0xe91e63,
metalness: 0.1,
roughness: 0.75,
clippingPlanes: this.planes.filter((p) => p !== plane),
stencilWrite: true,
stencilRef: 0,
stencilFunc: THREE.NotEqualStencilFunc,
stencilFail: THREE.ReplaceStencilOp,
stencilZFail: THREE.ReplaceStencilOp,
stencilZPass: THREE.ReplaceStencilOp,
});
const po = new THREE.Mesh(planeGeom, planeMat);
po.onAfterRender = (renderer) => {
renderer.clearStencil();
};
po.renderOrder = i + 1.2;
this.object.add(stencilGroup);
poGroup.add(po);
this.planeObjects.push(po);
this.scene.add(poGroup);
}
const material = new THREE.MeshStandardMaterial({
color: 0xffc107,
metalness: 0.1,
roughness: 0.75,
side: THREE.DoubleSide,
clippingPlanes: this.planes,
clipShadows: true,
})
const clippedColorFront = new THREE.Mesh(geometry, material);
clippedColorFront.castShadow = true;
clippedColorFront.renderOrder = 16;
this.object.add(clippedColorFront);
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(9, 9, 1, 1),
new THREE.ShadowMaterial({
color: 0x000000,
opacity: 0.25,
side: THREE.DoubleSide,
})
);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -1;
ground.receiveShadow = true;
this.scene.add(ground);
const gui = new GUI()
gui.add(this.params, "animate");
const planeX = gui.addFolder("planeX");
planeX.add(this.params.planeX, 'displayHelper').onChange((v) => (this.planeHelpers[0].visible = v));
planeX.add(this.params.planeX, "constant", -1, 1).step(0.01).onChange((d) => (this.planes[0].constant = d));
planeX.add(this.params.planeX, "negated").onChange(() => {
this.planes[0].negate();
this.params.planeX.constant = this.planes[0].constant;
});
planeX.open();
const planeY = gui.addFolder("planeY");
planeY.add(this.params.planeY, "displayHelper").onChange((v) => (this.planeHelpers[1].visible = v));
planeY.add(this.params.planeY, "constant", -1, 1).onChange((d) => (this.planes[1].constant = d));
planeY.add(this.params.planeY, "negated").onChange(() => {
this.planes[1].negate();
this.params.planeY.constant = this.planes[1].constant;
});
planeY.open();
const planeZ = gui.addFolder("planeZ");
planeZ.add(this.params.planeZ, "displayHelper").onChange((v) => (this.planeHelpers[2].visible = v));
planeZ.add(this.params.planeZ, "constant", -1, 1).onChange((d) => (this.planes[2].constant = d));
planeZ.add(this.params.planeZ, "negated").onChange(() => {
this.planes[2].negate();
this.params.planeZ.constant = this.planes[2].constant;
});
planeZ.open();
},
createPlaneStencilGroup(geometry, plane, renderOrder) {
const group = new THREE.Group();
const baseMat = new THREE.MeshBasicMaterial();
baseMat.depthWrite = false;
baseMat.depthTest = false;
baseMat.colorWrite = false;
baseMat.stencilWrite = true;
baseMat.stencilFunc = THREE.AlwaysStencilFunc;
const mat0 = baseMat.clone();
mat0.side = THREE.BackSide;
mat0.clippingPlanes = [plane];
mat0.stencilFail = THREE.IncrementWrapStencilOp;
mat0.stencilZFail = THREE.IncrementWrapStencilOp;
mat0.stencilZPass = THREE.IncrementWrapStencilOp;
const mesh0 = new THREE.Mesh(geometry, mat0);
mesh0.renderOrder = renderOrder;
group.add(mesh0);
const mat1 = baseMat.clone();
mat1.side = THREE.FrontSide;
mat1.clippingPlanes = [plane];
mat1.stencilFail = THREE.DecrementWrapStencilOp;
mat1.stencilZFail = THREE.DecrementWrapStencilOp;
mat1.stencilZPass = THREE.DecrementWrapStencilOp;
const mesh1 = new THREE.Mesh(geometry, mat1);
mesh1.renderOrder = renderOrder;
group.add(mesh1);
return group;
},
addHelper() {
let helper = new THREE.CameraHelper(this.camera);
let axisHelper = new THREE.AxesHelper(150);
let gridHelper = new THREE.GridHelper(100, 30, 0x2C2C2C, 0x888888);
this.planeHelpers = this.planes.map(p => new THREE.PlaneHelper(p, 2, 0xffffff))
this.planeHelpers.map(ph => {
ph.visible = false
this.scene.add(ph)
})
},
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(0xffffff, 1.5);
this.scene.add(ambientLight);
const spotLight = new THREE.SpotLight(0xffffff, 60);
spotLight.angle = Math.PI / 5;
spotLight.penumbra = 0.2;
spotLight.position.set(2, 3, 3);
spotLight.castShadow = true;
spotLight.shadow.camera.near = 3;
spotLight.shadow.camera.far = 10;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(5, 10, 7.5);
dirLight.castShadow = true;
dirLight.shadow.camera.right = 2;
dirLight.shadow.camera.left = - 2;
dirLight.shadow.camera.top = 2;
dirLight.shadow.camera.bottom = - 2;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
this.scene.add(dirLight);
},
addStatus() {
this.stats = new Stats();
this.container.appendChild(this.stats.dom);
},
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();
if (this.params.animate) {
this.object.rotation.x += delta * 0.5;
this.object.rotation.y += delta * 0.2;
}
for (let i = 0; i < this.planeObjects.length; i++) {
const plane = this.planes[i];
const po = this.planeObjects[i];
plane.coplanarPoint(po.position);
po.lookAt(
po.position.x - plane.normal.x,
po.position.y - plane.normal.y,
po.position.z - plane.normal.z
);
}
requestAnimationFrame(this.animate);
this.controller.update();
this.stats.update();
this.renderer.render(this.scene, this.camera);
},
},
};
</script>
<style>
#container {
position: absolute;
width: 100%;
height: 100%;
}
</style>