当前位置: 首页 > news >正文

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

模糊反射(drei转原生) ·Blur Reflect· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • 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 'three'

    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js" import { KawaseBlurPass } from "postprocessing" import { Pane } from 'tweakpane'

    const 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 = true

    const { DepthFormat, DepthTexture, LinearFilter, Matrix4, MeshStandardMaterial, PerspectiveCamera, Plane, UnsignedShortType, Vector3, Vector4, WebGLRenderTarget } = THREE

    class 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 = object

    this.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 = planeNormal

    this.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 = UnsignedShortType

    const 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) return

    this.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.autoUpdate

    this.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 } = material

    animate() 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 开源仓库
http://www.jsqmd.com/news/1106745/

相关文章:

  • LangGraph实战:构建有状态AI工作流引擎
  • 移动端AI落地实战:从模型部署到商业验证的完整链路
  • logback实战详解fileNamePattern配置问题%d多级日期文件夹
  • Dify接入高德地图MCP服务详细配置教程
  • 当反射内存环网中出现“Own Data”指示灯不亮的情况,可能的原因和排查步骤
  • 记一次内存溢出的分析经历
  • 耶鲁牛津剑桥等全球EMBA精英集聚复旦,拓数派董事长冯雷全英文授课“用Ontology实现零代码构建智能体”
  • 洗牙并非简单清洁:规范洁牙科普指南
  • Gemini AI工具全家桶深度应用指南
  • Java毕业设计-基于 SpringBoot 的线上手办周边商城系统的设计与实现 基于 SpringBoot 的动漫手办周边电商管理系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • LabVIEW让故障排查从“猜“变“算“
  • 2026年7月电锅炉厂家的选择应该考虑哪些因素?
  • 最近体验了一下 Visible Coding,AI 编程方式确实变了
  • SIGMOD 2025论文深度解读
  • AI 写了 500 行代码,上线后发现漏了 3 个接口、2 个路由、1 个菜单 —— 这套方法论让这种事再也没发生过
  • AI Agent实战:我用Gemini批量完成了《道德经》解读
  • 魔兽争霸3优化终极指南:如何免费解锁300帧高帧率游戏体验
  • 产品 | 《深渊世界》:潜入深海,开启生存冒险之旅!
  • 好用还专业!AI论文工具2026最新测评与推荐
  • 计算机Java毕设实战-基于 SpringBoot 的医院床位调度管理系统的设计与实现 基于 SpringBoot 的住院信息登记与运维系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • Java毕业设计-基于 SpringBoot 的医院住院部综合管理系统的设计与实现 基于 SpringBoot 的住院患者病房管控系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • CSDN-视频采集芯片选型指南
  • 量子修正黑洞热力学:模型构建与数值计算实践
  • 编写轻量级框架
  • 摩尔投票法:线性时间寻找多数元素的优雅算法
  • 基于LTC6903与PIC18的数字控制振荡器设计与实现
  • AI落地五大硬核挑战与可验证工程解决方案
  • CS2200-CP与PIC18F25K40高精度计时系统设计指南
  • python下载
  • AI交互数字人:智能一体机场景落地核心优势