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分享者18 小时前
学习threejs,使用RollControls相机控制器
threejs·相机控制器·rollcontrols
gis分享者3 天前
学习threejs,使用FlyControls相机控制器
threejs·相机控制器·flycontrols
gis分享者4 天前
学习threejs,使用TrackballControls相机控制器
threejs·trackball·相机控制器
gis分享者10 天前
学习threejs,导入assimp & assimp2json格式的模型
threejs·三维模型·assimp·assimp2json
gis分享者11 天前
学习threejs,导入AWD格式的模型
threejs·awd·three.awdloader
gis分享者17 天前
学习threejs,导入pdb格式的模型
threejs·pdb模型·three.pdbloader
gis分享者18 天前
学习threejs,THREE.CircleGeometry 二维平面圆形几何体
threejs·圆形几何体·circlegeometry
gis分享者19 天前
学习threejs,THREE.RingGeometry 二维平面圆环几何体
threejs·圆环几何体·ringgeometry
AllBlue20 天前
blender中合并的模型,在threejs中显示多个mesh;blender多材质烘培成一个材质
blender·threejs