官方文档示例
https://github.com/mrdoob/three.js/blob/master/examples/webgl_postprocessing_outline.html
<template>
<div ref="threeRef"></div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import {GLTFLoader} from "three/addons/loaders/GLTFLoader.js";
import {TransformControls} from "three/examples/jsm/controls/TransformControls.js";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js"
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js"
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js"
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js"
let threeRef=ref()
const scene=new THREE.Scene()
scene.background=new THREE.Color('#2fa799')
const camera=new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.y=200 // 40
camera.position.x=0
camera.position.z=100 // 30
// 相机朝向
camera.lookAt(scene.position)
// 坐标轴
const axes=new THREE.AxesHelper(50)
scene.add(axes)
// 渲染器
const renderer=new THREE.WebGLRenderer({antialias:true})
renderer.setSize(window.innerWidth,window.innerHeight)
renderer.render( scene, camera );
renderer.outputEncoding=THREE.sRGBEncoding
// 监听窗口变化
window.addEventListener('resize',()=>{
camera.aspect=window.innerWidth/window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth,window.innerHeight)
},false)
// 相机
const controls=new OrbitControls(camera,renderer.domElement)
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 点光源
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.set(0, 3, 0);
scene.add(pointLight);
// 初始射线发射器
const transformControls=new TransformControls(camera,renderer.domElement)
scene.add(transformControls)
let model
// 加载模型
const gltfloader = new GLTFLoader()
gltfloader.load('./dwendwen.gltf',event=>{
model=event.scene
model.scale.set(8*10,8*10,8*10)// 缩放
model.translateZ(7*10)// 平移
model.position.y=4*10// 位置
model.rotateX(Math.PI/-4)// 旋转
scene.add(model)
model.traverse(child => {
if (child.isMesh) {
child.material.basicMaterial = child.material;
}
});
})
let outlinePass,
renderPass
// 调用此方法传入数组
const outlineObj=(selectedObjects)=>{
// 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。
window.composer = new EffectComposer(renderer)
// 新建一个场景通道 为了覆盖到原理来的场景上
renderPass = new RenderPass(scene, camera)
window.composer.addPass(renderPass);
// 物体边缘发光通道
outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera, selectedObjects)
outlinePass.selectedObjects = selectedObjects
outlinePass.edgeStrength = 10.0 // 边框的亮度
outlinePass.edgeGlow = 1// 光晕[0,1]
outlinePass.usePatternTexture = false // 是否使用父级的材质
outlinePass.edgeThickness = 1.0 // 边框宽度
outlinePass.downSampleRatio = 1 // 边框弯曲度
outlinePass.pulsePeriod = 5 // 呼吸闪烁的速度
outlinePass.visibleEdgeColor.set(parseInt(0x00ff00)) // 呼吸显示的颜色
outlinePass.hiddenEdgeColor = new THREE.Color(222,222,222) // 呼吸消失的颜色,0,0,0
outlinePass.clear = true
window.composer.addPass(outlinePass)
// 自定义的着色器通道 作为参数
let effectFXAA = new ShaderPass(FXAAShader)
effectFXAA.uniforms.resolution.value.set(1 / window.innerWidth, 1 / window.innerHeight)
effectFXAA.renderToScreen = true
window.composer.addPass(effectFXAA)
}
renderer.domElement.addEventListener('mousemove', function(event) {
let mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
let raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
let intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
outlineObj([intersects[0].object])
}
});
// 地图
{
const planeSize = 30;
const loader = new THREE.TextureLoader();
const texture = loader.load('https://threejs.org/manual/examples/resources/images/checker.png');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = planeSize / 2;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneGeometry(100, 100);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}
// 方块
{
const pieceJ = new THREE.BoxGeometry(100, 40, 100)
const pieceC = new THREE.MeshBasicMaterial({color: '#808080'})
const pieceW = new THREE.Mesh(pieceJ, pieceC)
pieceW.position.x = -25
pieceW.position.y = -25
pieceW.position.z = 50
scene.add(pieceW)
}
const init=()=>{
threeRef.value?.appendChild(renderer.domElement)
controls.update()
requestAnimationFrame(init)
renderer.render( scene, camera );
if (window.composer) {
window.composer.render()
}
}
onMounted(()=>{
init()
})
</script>
<style scoped lang="less">
</style>
示例中composer挂到window上也会卡,慎用,有时间再研究叭