Three.js 模糊反射(drei转原生)教程

发布时间:2026/7/2 4:47:20
Three.js 模糊反射(drei转原生)教程 模糊反射(drei转原生) ·Blur Reflect· ▶ 在线运行案例案例合集三维可视化功能案例threehub.cn开源仓库github地址https://github.com/z2586300277/three-cesium-examples400个案例代码:网盘链接你将学到什么onBeforeCompile 注入 GLSL 改造内置材质OrbitControls 相机轨道交互glTF/Draco 模型加载与优化水面反射/镜像材质requestAnimationFrame渲染循环与resize自适应效果说明本案例演示模糊反射(drei转原生)效果基于 WebGL 实现「模糊反射(drei转原生)」可视化效果附完整可运行源码核心用到 onBeforeCompile、OrbitControls、glTF/Draco。建议先打开文首在线案例查看动态画面再对照下方源码逐步理解。核心概念Scene / Camera / WebGLRenderer构成最小渲染闭环大场景可开logarithmicDepthBuffer缓解 Z-fighting。onBeforeCompile在 Three 拼好内置 shader 后替换#include片段适合在 PBR 材质上叠加大屏特效。OrbitControls提供轨道旋转/缩放开启enableDamping后需在 animate 中controls.update()。实现步骤搭建灯光与环境如有requestAnimationFrame 循环 update render代码要点import * as THREE from threeimport { OrbitControls } from three/examples/jsm/controls/OrbitControls.js import { GLTFLoader } from three/examples/jsm/loaders/GLTFLoader.js import { KawaseBlurPass } from postprocessing import { Pane } from tweakpaneconst box document.getElementById(box)const scene new THREE.Scene()const camera new THREE.PerspectiveCamera(50, box.clientWidth / box.clientHeight, 0.1, 1000)camera.position.set(0, 200, 200)const renderer new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })renderer.setClearColor(0x000000, 1)renderer.setSize(box.clientWidth, box.clientHeight)box.appendChild(renderer.domElement)scene.add(new THREE.AmbientLight(0xffffff, 0.3))const directionalLight new THREE.DirectionalLight(0xffffff, 1.5)directionalLight.position.set(0, 200, 200)scene.add(directionalLight)new GLTFLoader().load(FILE_HOST files/model/Fox.glb, (gltf) scene.add(gltf.scene))const controls new OrbitControls(camera, renderer.domElement)controls.enableDamping trueconst { DepthFormat, DepthTexture, LinearFilter, Matrix4, MeshStandardMaterial, PerspectiveCamera, Plane, UnsignedShortType, Vector3, Vector4, WebGLRenderTarget } THREEclass MeshReflectorMaterial extends MeshStandardMaterial { constructor(renderer, camera, scene, object, { mixBlur 0, mixStrength 1, resolution 256, blur [0, 0], minDepthThreshold 0.9, maxDepthThreshold 1, depthScale 0, depthToBlurRatioBias 0.25, mirror 0, distortion 1, mixContrast 1, distortionMap, reflectorOffset 0, bufferSamples 8, planeNormal new Vector3(0, 0, 1), parameters {} } {}) { super(parameters);this.gl renderer this.camera camera this.scene scene this.parent objectthis.hasBlur blur[0] blur[1] 0 this.reflectorPlane new Plane() this.normal new Vector3() this.reflectorWorldPosition new Vector3() this.cameraWorldPosition new Vector3() this.rotationMatrix new Matrix4() this.lookAtPosition new Vector3(0, -1, 0) this.clipPlane new Vector4() this.view new Vector3() this.target new Vector3() this.q new Vector4() this.textureMatrix new Matrix4() this.virtualCamera new PerspectiveCamera() this.reflectorOffset reflectorOffset; this.planeNormal planeNormalthis.setupBuffers(resolution, blur, bufferSamples);this.reflectorProps { mirror, textureMatrix: this.textureMatrix, mixBlur, tDiffuse: this.fbo1.texture, tDepth: this.fbo1.depthTexture, tDiffuseBlur: this.fbo2.texture, hasBlur: this.hasBlur, mixStrength, minDepthThreshold, maxDepthThreshold, depthScale, depthToBlurRatioBias, distortion, distortionMap, mixContrast, defines-USE_BLUR: this.hasBlur ? : undefined, defines-USE_DEPTH: depthScale 0 ? : undefined, defines-USE_DISTORTION: distortionMap ? : undefined, } }setupBuffers(resolution, blur, bufferSamples) { const parameters { minFilter: LinearFilter, magFilter: LinearFilter, encoding: this.gl.outputEncoding, }const fbo1 new WebGLRenderTarget(resolution, resolution, parameters) fbo1.depthBuffer true fbo1.depthTexture new DepthTexture(resolution, resolution) fbo1.depthTexture.format DepthFormat fbo1.depthTexture.type UnsignedShortTypeconst fbo2 new WebGLRenderTarget(resolution, resolution, parameters)if (this.gl.capabilities.isWebGL2) { fbo1.samples bufferSamples }this.fbo1 fbo1; this.fbo2 fbo2;this.kawaseBlurPass new KawaseBlurPass() this.kawaseBlurPass.setSize(blur[0], blur[1]) }beforeRender() { if (!this.parent) returnthis.reflectorWorldPosition.setFromMatrixPosition(this.parent.matrixWorld) this.cameraWorldPosition.setFromMatrixPosition(this.camera.matrixWorld) this.rotationMatrix.extractRotation(this.parent.matrixWorld)// was changed from this.normal.set(0, 0, 1) this.normal.copy(this.planeNormal) this.normal.applyMatrix4(this.rotationMatrix) this.reflectorWorldPosition.addScaledVector(this.normal, this.reflectorOffset) this.view.subVectors(this.reflectorWorldPosition, this.cameraWorldPosition) // Avoid rendering when reflector is facing away if (this.view.dot(this.normal) 0) return this.view.reflect(this.normal).negate() this.view.add(this.reflectorWorldPosition) this.rotationMatrix.extractRotation(this.camera.matrixWorld) this.lookAtPosition.set(0, 0, -1) this.lookAtPosition.applyMatrix4(this.rotationMatrix) this.lookAtPosition.add(this.cameraWorldPosition) this.target.subVectors(this.reflectorWorldPosition, this.lookAtPosition) this.target.reflect(this.normal).negate() this.target.add(this.reflectorWorldPosition) this.virtualCamera.position.copy(this.view) this.virtualCamera.up.set(0, 1, 0) this.virtualCamera.up.applyMatrix4(this.rotationMatrix) this.virtualCamera.up.reflect(this.normal) this.virtualCamera.lookAt(this.target) this.virtualCamera.far this.camera.far // Used in WebGLBackground this.virtualCamera.updateMatrixWorld() this.virtualCamera.projectionMatrix.copy(this.camera.projectionMatrix)// Update the texture matrix this.textureMatrix.set(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0) this.textureMatrix.multiply(this.virtualCamera.projectionMatrix) this.textureMatrix.multiply(this.virtualCamera.matrixWorldInverse) this.textureMatrix.multiply(this.parent.matrixWorld)this.reflectorPlane.setFromNormalAndCoplanarPoint(this.normal, this.reflectorWorldPosition) this.reflectorPlane.applyMatrix4(this.virtualCamera.matrixWorldInverse) this.clipPlane.set(this.reflectorPlane.normal.x, this.reflectorPlane.normal.y, this.reflectorPlane.normal.z, this.reflectorPlane.constant) const projectionMatrix this.virtualCamera.projectionMatrix this.q.x (Math.sign(this.clipPlane.x) projectionMatrix.elements[8]) / projectionMatrix.elements[0] this.q.y (Math.sign(this.clipPlane.y) projectionMatrix.elements[9]) / projectionMatrix.elements[5] this.q.z -1.0 this.q.w (1.0 projectionMatrix.elements[10]) / projectionMatrix.elements[14] // Calculate the scaled plane vector this.clipPlane.multiplyScalar(2.0 / this.clipPlane.dot(this.q))// Replacing the third row of the projection matrix projectionMatrix.elements[2] this.clipPlane.x projectionMatrix.elements[6] this.clipPlane.y projectionMatrix.elements[10] this.clipPlane.z 1.0 projectionMatrix.elements[14] this.clipPlane.w }update() { if (this.parent.material ! this) return;this.parent.visible false const currentXrEnabled this.gl.xr.enabled const currentShadowAutoUpdate this.gl.shadowMap.autoUpdatethis.beforeRender() this.gl.xr.enabled false this.gl.shadowMap.autoUpdate false this.gl.setRenderTarget(this.fbo1) this.gl.state.buffers.depth.setMask(true) if (!this.gl.autoClear) this.gl.clear()this.gl.render(this.scene, this.virtualCamera)if (this.hasBlur) { this.kawaseBlurPass.render(this.gl, this.fbo1, this.fbo2); }this.gl.xr.enabled currentXrEnabled this.gl.shadowMap.autoUpdate currentShadowAutoUpdate this.parent.visible true this.gl.setRenderTarget(null) }onBeforeCompile(shader, ...args) { super.onBeforeCompile(shader, ...args);if (this.defines undefined) this.defines {}if (!this.defines.USE_UV) { this.defines.USE_UV }if (this.reflectorProps[defines-USE_BLUR] ! undefined) this.defines.USE_BLUR if (this.reflectorProps[defines-USE_DEPTH] ! undefined) this.defines.USE_DEPTH if (this.reflectorProps[defines-USE_DISTORTION] ! undefined) this.defines.USE_DISTORTION let props this.reflectorProps;for (let prop in props) { shader.uniforms[prop] { get value() { return props[prop] } } }shader.vertexShader uniform mat4 textureMatrix; varying vec4 my_vUv; ${shader.vertexShader}shader.vertexShader shader.vertexShader.replace( #include , /glsl/#include my_vUv textureMatrix * vec4( position, 1.0 ); gl_Position projectionMatrixmodelViewMatrixvec4( position, 1.0 );)shader.fragmentShader /glsl/uniform sampler2D tDiffuse; uniform sampler2D tDiffuseBlur; uniform sampler2D tDepth; uniform sampler2D distortionMap; uniform float distortion; uniform float cameraNear; uniform float cameraFar; uniform bool hasBlur; uniform float mixBlur; uniform float mirror; uniform float mixStrength; uniform float minDepthThreshold; uniform float maxDepthThreshold; uniform float mixContrast; uniform float depthScale; uniform float depthToBlurRatioBias; varying vec4 my_vUv; ${shader.fragmentShader}shader.fragmentShader shader.fragmentShader.replace( #include , /glsl/#include float distortionFactor 0.0; #ifdef USE_DISTORTION distortionFactor texture2D(distortionMap, vUv).r * distortion; #endif vec4 new_vUv my_vUv; new_vUv.x distortionFactor; new_vUv.y distortionFactor; vec4 base texture2DProj(tDiffuse, new_vUv); vec4 blur texture2DProj(tDiffuseBlur, new_vUv); vec4 merge base; #ifdef USE_NORMALMAP vec2 normal_uv vec2(0.0); vec4 normalColor texture2D(normalMap, vUv); vec3 my_normal normalize( vec3( normalColor.r2.0 - 1.0, normalColor.b, normalColor.g2.0 - 1.0 ) ); vec3 coord new_vUv.xyz / new_vUv.w; normal_uv coord.xy coord.zmy_normal.xz0.05 * normalScale; vec4 base_normal texture2D(tDiffuse, normal_uv); vec4 blur_normal texture2D(tDiffuseBlur, normal_uv); merge base_normal; blur blur_normal; #endif float depthFactor 0.0001; float blurFactor 0.0; #ifdef USE_DEPTH vec4 depth texture2DProj(tDepth, new_vUv); depthFactor smoothstep(minDepthThreshold, maxDepthThreshold, 1.0-(depth.r * depth.a)); depthFactor * depthScale; depthFactor max(0.0001, min(1.0, depthFactor)); #ifdef USE_BLUR blur blur * min(1.0, depthFactor depthToBlurRatioBias); merge merge * min(1.0, depthFactor 0.5); #else merge merge * depthFactor; #endif #endif float reflectorRoughnessFactor roughness; #ifdef USE_ROUGHNESSMAP vec4 reflectorTexelRoughness texture2D( roughnessMap, vUv ); reflectorRoughnessFactor * reflectorTexelRoughness.g; #endif #ifdef USE_BLUR blurFactor min(1.0, mixBlur * reflectorRoughnessFactor); merge mix(merge, blur, blurFactor); #endif vec4 newMerge vec4(0.0, 0.0, 0.0, 1.0); newMerge.r (merge.r - 0.5) * mixContrast 0.5; newMerge.g (merge.g - 0.5) * mixContrast 0.5; newMerge.b (merge.b - 0.5) * mixContrast 0.5; diffuseColor.rgb diffuseColor.rgb((1.0 - min(1.0, mirror)) newMerge.rgbmixStrength);) } }const tunnel new THREE.Mesh(new THREE.TorusKnotGeometry(20, 2, 100, 16), new THREE.MeshStandardMaterial({ color: 0x00ff00, transparent: true, opacity: 0.3 })) tunnel.position.set(0, 80, 50) scene.add(tunnel)const material new MeshReflectorMaterial(renderer, camera, scene, {}, { resolution: 1024, blur: [512, 128], mixBlur: 5, mixStrength: 5, mixContrast: 1, mirror: 1, parameters:{ roughnessMap: new THREE.TextureLoader().load(FILE_HOST images/drei/roughness.jpg), normalMap: new THREE.TextureLoader().load(FILE_HOST images/drei/normal.jpg), normalScale: new THREE.Vector2(1, 1) } }) const plane new THREE.Mesh(new THREE.PlaneGeometry(500, 500),material) plane.position.y -1 plane.rotation.x -Math.PI / 2 material.parent plane scene.add(plane)const { reflectorProps } materialanimate() function animate() { requestAnimationFrame(animate) tunnel.rotation.z 0.02 tunnel.rotation.x 0.02 material.update() controls.update() renderer.render(scene, camera) }const pane new Pane(); pane.addBinding(reflectorProps, mixBlur, { min: 0, max: 20 }); pane.addBinding(reflectorProps, mixStrength, { min: 0, max: 20 }); pane.addBinding(reflectorProps, mirror, { min: 0, max: 1 }); pane.addBinding(reflectorProps, mixContrast, { min: 0, max: 5 });完整源码GitHub小结本文提供模糊反射(drei转原生)完整 Three.js 源码与在线 Demo建议先运行案例再改 uniform/参数做二次实验更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库