threejs 实现镜面反射,只反射指定物体,背景透明

一、背景
最近在做数字孪生项目,使用threejs渲染模型,UI要求地面反射建筑物,也就是模型要有倒影。
二、调研
在官网找到一个镜面反射的例子(https://threejs.org/examples/?q=refle#webgl_mirror
如图:

和UI要的功能类似,但有缺陷
1、反射出了地面上所有的元素,连天空盒都反射出来了,我只想反射建筑物
2、没反射的地方,有颜色,盖住了镜面平面下的内容
如图:

三、如何只反射指定物体
通过阅读源码,我们发现在 Three.js 中,Reflector 类有一个 onBeforeRender 方法,我们可以称它为"渲染前回调"或"预渲染处理函数"。
如果能拿到这个钩子,在这个钩子里做过滤,问题就迎刃而解了。
但是,最怕有但是,但是这个方法没放出来,不能直接调,怎么办呢?
那就只能曲线救国,改它的方法!
如图:

解决方法,就是重写reflector对象的onBeforeRender方法
onBeforeRender方法传入一个 scene 对象,代表视图,也就是反射的内容,过滤scene,不需要的模型对象,隐藏掉(visible = false)
效果如下:

现在已经不反射天空盒了,只反射我想要的模型
但是,最怕有但是,但是不反射的地方变成黑色的了,修改镜面平面的材质透明度(opacity)不起效果,怎么办呢?
修改片元着色器。
四、修改片元着色器,使不反射的地方透明
reflector有shander属性,用于传入自定义着色器,着色器(Shader)是图形编程中的一个术语,指的是在图形处理单元(GPU)上运行的程序。这块涉及WebGL的知识,我不懂,不多言。

复制代码
Reflector.ReflectorShader = {

    uniforms: {

        'color': {
            value: null
        },

        'tDiffuse': {
            value: null
        },

        'textureMatrix': {
            value: null
        }

    },

    vertexShader: /* glsl */`
        uniform mat4 textureMatrix;
        varying vec4 vUv;

        #include <common>
        #include <logdepthbuf_pars_vertex>

        void main() {

            vUv = textureMatrix * vec4( position, 1.0 );

            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

            #include <logdepthbuf_vertex>

        }`,

    fragmentShader: /* glsl */`
        uniform vec3 color;
        uniform sampler2D tDiffuse;
        varying vec4 vUv;

        #include <logdepthbuf_pars_fragment>

        float blendOverlay( float base, float blend ) {

            return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );

        }

        vec3 blendOverlay( vec3 base, vec3 blend ) {

            return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );

        }

        void main() {

            #include <logdepthbuf_fragment>

            vec4 base = texture2DProj( tDiffuse, vUv );
            gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );

            #include <tonemapping_fragment>
            #include <encodings_fragment>

        }`
};

这是threejs源码,reflector类里定义的默认着色器,拷出来,改一下,再通过shader属性传进去。

加上这三行代码即可
效果如图

镜面平面透明了
五、如何调整镜面平面的透明度
刚才说了,调整镜面的透明度(opacity)已经不起效果了,我也不想细查为啥了
透明度也可以直接通过片元着色器修改

把红框内值改了就行了,数值范围是0~1
改为0.4后,效果如图:

注意:要想让镜面平面透明,一定要把镜面材质的是否支持透明度属性改为true,也就是支持透明


完整代码如下

复制代码
import { Reflector } from 'three/examples/jsm/objects/Reflector.js';
import { transparentMirrorShader } from '../xxxx.js'

// 创建镜面反射平面
createMirrorPlane() {
    // 创建一个圆形几何体
    const radius = 640; // 圆的半径
    const segments = 256; // 圆的细分数,细分越多圆形越平滑
    const geometry = new THREE.CircleGeometry(radius, segments);

    // 创建 Reflector
    const reflector = new Reflector(geometry, {
        color: 0x000000, // 设置反射颜色
        textureWidth: window.innerWidth * window.devicePixelRatio, // 反射纹理宽度
        textureHeight: window.innerHeight * window.devicePixelRatio, // 反射纹理高度
        shader: transparentMirrorShader,
        clipBias: 0.000 // 裁剪偏移量
    });

    const subModelNameList = ['wall', '天空盒']
    reflector.originOnBeforeRender = reflector.onBeforeRender;
    reflector.onBeforeRender = function (renderer, scene, camera) {
        const scene1 = scene.clone();
        scene1.traverse(child => {
            if (!child.isScene && !child.isLight && child.name && subModelNameList.some(ele => child.name.includes(ele))) {
                if (['wall'].some(ele => child.name.includes(ele))) {
                    child.visible = true;
                } else {
                    child.visible = false;
                }
            }
        });
        
        reflector.originOnBeforeRender(renderer, scene1, camera);
    }

    // 设置位置和旋转
    reflector.position.set(0, -0.01, 0);
    reflector.rotation.x = -Math.PI / 2;

    reflector.name = '镜面反射平面';

    reflector.material.transparent = true;

    // 添加到场景
    this.scene.add(reflector);
},
复制代码
export const transparentMirrorShader = {
    uniforms: {
        'color': {
            value: null
        },

        'tDiffuse': {
            value: null
        },

        'textureMatrix': {
            value: null
        }

    },

    vertexShader: /* glsl */`
        uniform mat4 textureMatrix;
        varying vec4 vUv;

        #include <common>
        #include <logdepthbuf_pars_vertex>

        void main() {

            vUv = textureMatrix * vec4( position, 1.0 );

            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

            #include <logdepthbuf_vertex>

        }`,

    fragmentShader: /* glsl */`
        uniform vec3 color;
        uniform sampler2D tDiffuse;
        varying vec4 vUv;

        #include <logdepthbuf_pars_fragment>

        float blendOverlay( float base, float blend ) {

            return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );

        }

        vec3 blendOverlay( vec3 base, vec3 blend ) {

            return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );

        }

        void main() {

            #include <logdepthbuf_fragment>

            vec4 base = texture2DProj( tDiffuse, vUv );

            // 检查是否有有效的反射纹理,如果没有则透明
            if (base.a < 0.1) {
                discard;
            }

            gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1 );

            #include <tonemapping_fragment>
            #include <encodings_fragment>

        }`
}

我是用立方体做例子,如果有实际模型,效果会更好一些。
欢迎指正。

相关推荐
gis分享者3 天前
学习threejs,使用PointLight点光源
threejs·点光源·pointlight
gis分享者7 天前
学习threejs,使用HemisphereLight半球光
threejs·hemispherelight·半球光
gis分享者11 天前
学习threejs,使用Lensflare模拟镜头眩光
threejs·lensflare·眩光
gis分享者14 天前
学习threejs,tga格式图片文件贴图
threejs·贴图·tga·tgaloader
gis分享者14 天前
学习threejs,pvr格式图片文件贴图
threejs·贴图·pvr
gis分享者1 个月前
学习threejs,使用OrbitControls相机控制器
threejs·相机·相机控制器·orbitcontrols
gis分享者1 个月前
学习threejs,使用RollControls相机控制器
threejs·相机控制器·rollcontrols
gis分享者1 个月前
学习threejs,使用FlyControls相机控制器
threejs·相机控制器·flycontrols
gis分享者1 个月前
学习threejs,使用TrackballControls相机控制器
threejs·trackball·相机控制器