356 lines
12 KiB
Vue
356 lines
12 KiB
Vue
<template>
|
|
<div class="c-shader" ref="container"></div>
|
|
</template>
|
|
|
|
<script>
|
|
import gsap from "gsap";
|
|
import * as THREE from "three";
|
|
import * as Stats from "stats.js";
|
|
|
|
import colorVertex from "@/assets/shader/color_vertex.glsl";
|
|
import colorFrag from "@/assets/shader/color_fragment.glsl";
|
|
import { GUI } from "three/examples/jsm/libs/dat.gui.module.js";
|
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
|
|
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
|
|
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
|
|
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
|
|
|
|
export default {
|
|
name: "color",
|
|
props: {
|
|
msg: String,
|
|
},
|
|
data() {
|
|
return {
|
|
container: null,
|
|
camera: null,
|
|
scene: null,
|
|
renderer: null,
|
|
mouse: new THREE.Vector2(),
|
|
raycaster: new THREE.Raycaster(),
|
|
gui: new GUI(),
|
|
|
|
ENTIRE_SCENE: 0,
|
|
BLOOM_SCENE: 1,
|
|
materials: {},
|
|
params: {
|
|
exposure: 1,
|
|
bloomStrength: 5,
|
|
bloomThreshold: 0,
|
|
bloomRadius: 0,
|
|
scene: "Scene with Glow",
|
|
},
|
|
};
|
|
},
|
|
created() {},
|
|
mounted() {
|
|
this.initStage();
|
|
this.addAnimation();
|
|
},
|
|
methods: {
|
|
initStage() {
|
|
//基础参数
|
|
this.container = this.$refs.container;
|
|
this.camera = new THREE.Camera();
|
|
this.camera.position.z = 1;
|
|
this.scene = new THREE.Scene();
|
|
|
|
this.bloomLayer = new THREE.Layers();
|
|
this.bloomLayer.set(this.BLOOM_SCENE);
|
|
|
|
this.darkMaterial = new THREE.MeshBasicMaterial({ color: "black" });
|
|
|
|
// 定义 webglrender
|
|
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
this.renderer.toneMapping = THREE.ReinhardToneMapping;
|
|
this.container.appendChild(this.renderer.domElement);
|
|
|
|
// 定义相机
|
|
this.camera = new THREE.PerspectiveCamera(
|
|
40,
|
|
window.innerWidth / window.innerHeight,
|
|
1,
|
|
200
|
|
);
|
|
this.camera.position.set(0, 0, 30);
|
|
this.camera.lookAt(0, 0, 0);
|
|
|
|
// 定义操控
|
|
this.controls = new OrbitControls(
|
|
this.camera,
|
|
this.renderer.domElement
|
|
);
|
|
this.controls.maxPolarAngle = Math.PI * 0.5;
|
|
this.controls.minDistance = 1;
|
|
this.controls.maxDistance = 200;
|
|
this.controls.addEventListener("change", this.render);
|
|
|
|
// 添加全局环境光
|
|
this.scene.add(new THREE.AmbientLight(0x404040));
|
|
|
|
this.renderScene = new RenderPass(this.scene, this.camera);
|
|
|
|
this.bloomPass = new UnrealBloomPass(
|
|
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
1.5,
|
|
0.4,
|
|
0.85
|
|
);
|
|
this.bloomPass.threshold = this.params.bloomThreshold;
|
|
this.bloomPass.strength = this.params.bloomStrength;
|
|
this.bloomPass.radius = this.params.bloomRadius;
|
|
|
|
this.bloomComposer = new EffectComposer(this.renderer);
|
|
this.bloomComposer.renderToScreen = false;
|
|
this.bloomComposer.addPass(this.renderScene);
|
|
this.bloomComposer.addPass(this.bloomPass);
|
|
|
|
this.finalPass = new ShaderPass(
|
|
new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
baseTexture: { value: null },
|
|
bloomTexture: {
|
|
value: this.bloomComposer.renderTarget2.texture,
|
|
},
|
|
},
|
|
vertexShader: colorVertex,
|
|
fragmentShader: colorFrag,
|
|
defines: {},
|
|
}),
|
|
"baseTexture"
|
|
);
|
|
this.finalPass.needsSwap = true;
|
|
|
|
this.finalComposer = new EffectComposer(this.renderer);
|
|
this.finalComposer.addPass(this.renderScene);
|
|
this.finalComposer.addPass(this.finalPass);
|
|
|
|
// 添加点击事件
|
|
window.addEventListener("pointerdown", this.onPointerDown);
|
|
|
|
this.gui
|
|
.add(this.params, "scene", [
|
|
"Scene with Glow",
|
|
"Glow only",
|
|
"Scene only",
|
|
])
|
|
.onChange((value) => {
|
|
switch (value) {
|
|
case "Scene with Glow":
|
|
this.bloomComposer.renderToScreen = false;
|
|
break;
|
|
case "Glow only":
|
|
this.bloomComposer.renderToScreen = true;
|
|
break;
|
|
case "Scene only":
|
|
// nothing to do
|
|
break;
|
|
}
|
|
|
|
this.render();
|
|
});
|
|
|
|
this.folder = this.gui.addFolder("Bloom Parameters");
|
|
|
|
this.folder
|
|
.add(this.params, "exposure", 0.1, 2)
|
|
.onChange((value) => {
|
|
this.renderer.toneMappingExposure = Math.pow(value, 4.0);
|
|
this.render();
|
|
});
|
|
|
|
this.folder
|
|
.add(this.params, "bloomThreshold", 0.0, 1.0)
|
|
.onChange((value) => {
|
|
this.bloomPass.threshold = Number(value);
|
|
this.render();
|
|
});
|
|
|
|
this.folder
|
|
.add(this.params, "bloomStrength", 0.0, 10.0)
|
|
.onChange((value) => {
|
|
this.bloomPass.strength = Number(value);
|
|
this.render();
|
|
});
|
|
|
|
this.folder
|
|
.add(this.params, "bloomRadius", 0.0, 1.0)
|
|
.step(0.01)
|
|
.onChange((value) => {
|
|
this.bloomPass.radius = Number(value);
|
|
this.render();
|
|
});
|
|
|
|
// 设置场景
|
|
this.setupScene();
|
|
},
|
|
onPointerDown(event) {
|
|
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
|
|
this.raycaster.setFromCamera(this.mouse, this.camera);
|
|
this.intersects = this.raycaster.intersectObjects(
|
|
this.group.children,
|
|
false
|
|
);
|
|
if (this.intersects.length > 0) {
|
|
const object = this.intersects[0].object;
|
|
gsap.to(object.scale, {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
onUpdate: () => {
|
|
this.render();
|
|
},
|
|
});
|
|
object.layers.toggle(this.BLOOM_SCENE);
|
|
this.render();
|
|
}
|
|
},
|
|
onWindowResize(event) {
|
|
const width = window.innerWidth;
|
|
const height = window.innerHeight;
|
|
|
|
this.camera.aspect = width / height;
|
|
this.camera.updateProjectionMatrix();
|
|
|
|
this.renderer.setSize(width, height);
|
|
|
|
this.bloomComposer.setSize(width, height);
|
|
this.finalComposer.setSize(width, height);
|
|
|
|
this.render();
|
|
},
|
|
setupScene() {
|
|
this.scene.traverse(this.disposeMaterial);
|
|
this.scene.children.length = 0;
|
|
|
|
const geometry = new THREE.IcosahedronGeometry(1, 15);
|
|
this.group = new THREE.Group();
|
|
for (let i = 0; i < 50; i++) {
|
|
const color = new THREE.Color();
|
|
color.setHSL(Math.random(), 0.7, Math.random() * 0.2 + 0.05);
|
|
|
|
const material = new THREE.MeshBasicMaterial({ color: color });
|
|
const sphere = new THREE.Mesh(geometry, material);
|
|
sphere.position.x = Math.random() * 30 - 15;
|
|
sphere.position.y = Math.random() * 30 - 15;
|
|
sphere.position.z = Math.random() * 30 - 15;
|
|
sphere.position
|
|
.normalize()
|
|
.multiplyScalar(Math.random() * 5.0 + 2.5);
|
|
sphere.scale.setScalar(Math.random() * Math.random() + 0.5);
|
|
this.scene.add(sphere);
|
|
|
|
// if (Math.random() < 0.25)
|
|
sphere.layers.enable(this.BLOOM_SCENE);
|
|
|
|
this.group.add(sphere);
|
|
}
|
|
this.scene.add(this.group);
|
|
this.render();
|
|
},
|
|
disposeMaterial(obj) {
|
|
if (obj.material) {
|
|
obj.material.dispose();
|
|
}
|
|
},
|
|
animate() {
|
|
// requestAnimationFrame(this.animate);
|
|
// this.render();
|
|
},
|
|
render() {
|
|
switch (this.params.scene) {
|
|
case "Scene only":
|
|
this.renderer.render(this.scene, this.camera);
|
|
break;
|
|
case "Glow only":
|
|
this.renderBloom(false);
|
|
break;
|
|
case "Scene with Glow":
|
|
default:
|
|
// render scene with bloom
|
|
this.renderBloom(true);
|
|
|
|
// render the entire scene, then render bloom scene on top
|
|
this.finalComposer.render();
|
|
break;
|
|
}
|
|
// this.renderer.render(this.scene, this.camera);
|
|
},
|
|
renderBloom(mask) {
|
|
if (mask === true) {
|
|
this.scene.traverse(this.darkenNonBloomed);
|
|
this.bloomComposer.render();
|
|
this.scene.traverse(this.restoreMaterial);
|
|
} else {
|
|
this.camera.layers.set(this.BLOOM_SCENE);
|
|
this.bloomComposer.render();
|
|
this.camera.layers.set(this.ENTIRE_SCENE);
|
|
}
|
|
},
|
|
darkenNonBloomed(obj) {
|
|
if (obj.isMesh && this.bloomLayer.test(obj.layers) === false) {
|
|
this.materials[obj.uuid] = obj.material;
|
|
obj.material = this.darkMaterial;
|
|
}
|
|
},
|
|
restoreMaterial(obj) {
|
|
if (this.materials[obj.uuid]) {
|
|
obj.material = this.materials[obj.uuid];
|
|
delete this.materials[obj.uuid];
|
|
}
|
|
},
|
|
addAnimation() {
|
|
// 自旋转
|
|
gsap.to(this.group.rotation, {
|
|
y: -Math.PI * 2,
|
|
duration: 2,
|
|
// yoyo: true,
|
|
// repeat: -1,
|
|
onUpdate: () => {
|
|
// this.renderer.toneMappingExposure = Math.pow(
|
|
// this.params.exposure,
|
|
// 4.0
|
|
// );
|
|
this.render();
|
|
// console.log(this.params.scene);
|
|
},
|
|
});
|
|
// 光闪烁
|
|
// gsap.to(this.params, {
|
|
// exposure: 1.1,
|
|
// duration: 2,
|
|
// yoyo: true,
|
|
// repeat: -1,
|
|
// onUpdate: () => {
|
|
// this.renderer.toneMappingExposure = Math.pow(
|
|
// this.params.exposure,
|
|
// 4.0
|
|
// );
|
|
// this.render();
|
|
// // console.log(this.params.scene);
|
|
// },
|
|
// });
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
<style scoped lang="less">
|
|
.c-shader {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
position: relative;
|
|
z-index: 0;
|
|
.select-btn {
|
|
// background-color: #fff;
|
|
.paCenterBottom(2%,50%,auto,10);
|
|
}
|
|
}
|
|
</style>
|