Three.js 下雨特效(高级版本)很干!很难!很详细!

3afe982fef1290a663ba62fefbfb5da5.gif

大厂技术  高级前端  Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

带深度信息的下雨特效的实现

效果不错,实现的每一步真的很清楚。

阅读之前简略了解一下原理

1. 什么是深度?

深度,深度,深度就是三维世界中的坐标点,经过 MVP(modelMatrix viewMatrix projectionMatrix) 变化后,映射到相机空间的坐标中的 z 值范围 [0,1],z越大,距离相机越远。

2. 在WEBGL获取渲染信息

WEBGL中渲染信息存在 FBO(FrameBufferObject) 中,可以创建获取,详细百度哈,这里不是重点。

3. 在 three.js 中获取渲染信息

THREE.WebGLRenderTarget() 做了很好的封装,可以帮我们给三维场景自由拍照,并且获取到信息。

了解完了?那么!开始吧!

1. 基础模板的创建就直接跳过了
2. 首先确定下雨特效所在的空间,这里用一个BOX描述这个空间。然后在中间放置一个平面,用于挡雨。
box = new THREE.Box3(
        new THREE.Vector3(-200, 0, -200),
        new THREE.Vector3(200, 200, 200)
    );
    
    const geometry = new THREE.PlaneGeometry(100, 400)
    geometry.rotateX(-Math.PI / 2)

    const mesh = new THREE.Mesh(
        geometry,
        new THREE.MeshBasicMaterial({ side: THREE.DoubleSide })
    );
    mesh.position.y = 100
    scene.add(mesh);

是不是很简单?效果如下:

39daad6294a9ebc4cd029fd52e594d68.png
3. 渲染深度图
// 创建 renderTarget
    target = new THREE.WebGLRenderTarget(WIDTH, HEIGHT);
    target.texture.format = THREE.RGBFormat;
    target.texture.minFilter = THREE.NearestFilter;
    target.texture.magFilter = THREE.NearestFilter;
    target.texture.generateMipmaps = false;

    // 创建正交相机
    orthCamera = new THREE.OrthographicCamera();
    const center = new THREE.Vector3();
    box.getCenter(center);
    
    // 根据 BOX 设置正交相机的参数
    orthCamera.left = box.min.x - center.x;
    orthCamera.right = box.max.x - center.x;
    orthCamera.top = box.max.z - center.z;
    orthCamera.bottom = box.min.z - center.z;
    orthCamera.near = .1;
    orthCamera.far = box.max.y - box.min.y;
    
    // 设置正交相机的位置在 BOX 正上方
    orthCamera.position.copy(center);
    orthCamera.position.y += box.max.y - center.y;
    orthCamera.lookAt(center);

    // 更新正交相机投影矩阵和世界矩阵
    orthCamera.updateProjectionMatrix();
    orthCamera.updateWorldMatrix()

    // 创建个 helper 看看效果
    const helper = new THREE.CameraHelper(orthCamera)
    scene.add(helper);

正交相机创建完成了。待会用这个相机渲染depthScene获取深度信息。

acc35501a7938a5f34c513546383efef.png

创建 depthScene 。Scene.overrideMaterial 如果存在,会对场景中的每一个对象,强制使用这个材质渲染,这个场景只需要获取深度信息,所以这里覆盖所有对象本身的材质。

// 创建场景2,用于绘制深度图
    depthScene = new THREE.Scene();
    depthScene.overrideMaterial = new THREE.ShaderMaterial({
        vertexShader: `
        varying float color;

        void main() {
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
          color = gl_Position.z / 2.0 + 0.5;
        }
      `,
        fragmentShader: `
        varying float color;
        
        vec4 encodeFloat2RGBA(float v)
        {
            vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * v;
            enc = fract(enc);
            enc -= enc.yzww * vec4(1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0);
            return enc;
        }
        void main() {
            gl_FragColor = encodeFloat2RGBA(1.0 - color);
        }
      `,
    });

获取到正交相机渲染的贴图了,贴图里的颜色代表深度哦。

// 在这一步之后的渲染,渲染信息将会保存在target中,这里的信息是绘制深度的场景和正交相机。
    renderer.setRenderTarget(target);
    depthScene.children = [plane];
    renderer.render(depthScene, orthCamera);
    renderer.setRenderTarget(null);
4. 深度图有了,雨呢?先创建几何体吧。

雨水用 Mesh 创建,为什么不用 Points 呢?Points 创建的点会永远朝向我们哦。在上方会穿帮的。创建几何体,如下图,单个雨滴,是不是很简单,我们创建 6000 个吧。

2ef8b03a7527b70874bb2635babb02ed.png
const geometry = new THREE.BufferGeometry();

    const vertices = [];
    const poses = [];
    const uvs = [];
    const indices = [];

    for (let i = 0; i < 6000; i++) {
        const pos = new THREE.Vector3();
        pos.x = Math.random() * (box.max.x - box.min.x) + box.min.x;
        pos.y = Math.random() * (box.max.y - box.min.y) + box.min.y;
        pos.z = Math.random() * (box.max.z - box.min.z) + box.min.z;

        const height = (box.max.y - box.min.y) / 15;
        const width = height / 50;

        vertices.push(
            pos.x + width,
            pos.y + height,
            pos.z,
            pos.x - width,
            pos.y + height,
            pos.z,
            pos.x - width,
            pos.y,
            pos.z,
            pos.x + width,
            pos.y,
            pos.z
        );

        poses.push(
            pos.x,
            pos.y,
            pos.z,
            pos.x,
            pos.y,
            pos.z,
            pos.x,
            pos.y,
            pos.z,
            pos.x,
            pos.y,
            pos.z
        );

        uvs.push(1, 1, 0, 1, 0, 0, 1, 0);

        indices.push(
            i * 4 + 0,
            i * 4 + 1,
            i * 4 + 2,
            i * 4 + 0,
            i * 4 + 2,
            i * 4 + 3
        );
    }

    geometry.setAttribute(
        "position",
        new THREE.BufferAttribute(new Float32Array(vertices), 3)
    );
    geometry.setAttribute(
        "pos",
        new THREE.BufferAttribute(new Float32Array(poses), 3)
    );
    geometry.setAttribute(
        "uv",
        new THREE.BufferAttribute(new Float32Array(uvs), 2)
    );
    geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
材质创建

需要雨滴在水平方向上,永远朝向相机。所以我们将相机位置传入着色器。

f27b6ef9a29ffd32bdd0f34a3b9b8b8f.png
material = new THREE.MeshBasicMaterial({
        transparent: true,
        opacity: 0.8,
        depthWrite: false,
    });

    material.onBeforeCompile = function (shader, renderer) {
        const getFoot = `
            attribute vec3 pos;
            uniform float top;
            uniform float bottom;
            uniform float time;
            uniform mat4 cameraMatrix;
            varying float depth;
            varying vec2 depthUv;
            #include <common>
            float angle(float x, float y){
                return atan(y, x);  
            }
            
            // 计算更新过后的顶点坐标的偏移
            vec2 getFoot(vec2 camera,vec2 _n_pos,vec2 pos){
                vec2 position;

                float distanceLen = distance(pos, _n_pos);

                float a = angle(camera.x - _n_pos.x, camera.y - _n_pos.y);

                pos.x > _n_pos.x ? a -= 0.785 : a += 0.785; 

                position.x = cos(a) * distanceLen;
                position.y = sin(a) * distanceLen;
                
                return position + _n_pos;
            }
            `;
        const begin_vertex = `

            float height = top - bottom;

            vec3 _n_pos = vec3(pos.x, pos.y- height/30.,pos.z);

            vec2 foot = getFoot(vec2(cameraPosition.x, cameraPosition.z),  vec2(_n_pos.x, _n_pos.z), vec2(position.x, position.z));
            
            
            // 模拟雨滴下落位置。Bottom -> Bottom + Height 是雨滴下落空间。
            float y = _n_pos.y - bottom - height * fract(time);
            y += y < 0.0 ? height : 0.0;
     
            // 雨滴下落的百分比,即是雨滴的深度。 [0,1] 空间
            depth = (1.0 - y / height) ;
    
            // 更新顶点位置
            y += bottom;
            y += position.y - _n_pos.y;
            vec3 transformed = vec3( foot.x, y, foot.y );
            
            // 将顶点坐标与正交相机的投影矩阵的逆矩阵进行运算,得到顶点坐标在 [-1,1] 三维空间的数据。
            vec4 cameraDepth = cameraMatrix * vec4(transformed, 1.0);
            
            // 采样 uv
            depthUv = cameraDepth.xy/2.0 + 0.5;
            `;

        const depth_vary = `
            uniform sampler2D tDepth;
            uniform float opacity;
            varying float depth;
            varying vec2 depthUv;

            float decodeRGBA2Float(vec4 rgba)
            {
                return dot(rgba, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0));
            }
            `;

        const depth_frag = `
        
            // 对比深度值,如果深度值满足关系,不进行渲染。
            if(1.0 - depth < decodeRGBA2Float(texture2D( tDepth, depthUv ))) discard;
            vec4 diffuseColor = vec4( diffuse, opacity );
            `
        shader.vertexShader = shader.vertexShader.replace(
            "#include <common>",
            getFoot
        );
        shader.vertexShader = shader.vertexShader.replace(
            "#include <begin_vertex>",
            begin_vertex
        );
        shader.fragmentShader = shader.fragmentShader.replace('uniform float opacity;', depth_vary)
        shader.fragmentShader = shader.fragmentShader.replace('vec4 diffuseColor = vec4( diffuse, opacity );', depth_frag)

        shader.uniforms.cameraPosition = {
            value: new THREE.Vector3(0, 200, 0)
        }
        shader.uniforms.top = {
            value: box.max.y
        }
        shader.uniforms.bottom = {
            value: box.min.y
        }
        shader.uniforms.time = {
            value: 0
        }

        shader.uniforms.cameraMatrix = {
            value: new THREE.Matrix4()
        }
        shader.uniforms.tDepth = {
            value: target.texture
        }
        material.uniforms = shader.uniforms;
    };

正交相机拍的深度图类似这样。其中的颜色由顶点的 Z 值决定

4ea82aa05c82b92975637939dac19789.png
最后就是执行渲染了,如果需要动态获取深度图(例如,模型撑着雨伞在移动),可以在动画帧中执行哦,我下面注释了。
function render() {
    time = clock.getElapsedTime() / 2;

    if (material.uniforms) {
        material.uniforms.cameraPosition.value = camera.position;
        material.uniforms.time.value = time;

        material.uniforms.cameraMatrix.value = new THREE.Matrix4().multiplyMatrices(
            orthCamera.projectionMatrix,
            orthCamera.matrixWorldInverse
        );

        // renderer.setRenderTarget(target);
        // renderer.render(depthScene, orthCamera);
        // renderer.setRenderTarget(null);
        // material.uniforms.tDepth.value = target.texture;
    }

    renderer.render(scene, camera);
}
到此为止。下班。给个收藏给个赞!!!

源码

https://gitee.com/yjsdszz/three.js-advanced/blob/master/depth/depthRain.html

  • Node 社群

  • 我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
  • 最后不要忘了点个赞再走噢39a9bef40254afc482e256f718d7a979.png

1f9a3398ea22609ea196dbf3c928fea5.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: three.js 是一种用于创建3D图形和动画的JavaScript库。如果想在three.js中实现下雨效果,可以使用一些技术和方法来模拟雨滴的出现和下落。 首先,我们可以创建一个相机和场景来容纳雨滴的模拟。然后,我们可以使用几何体和材质来创建具有适当形状和外观的雨滴。可以选择使用小球体或圆柱体来表示雨滴,并使用材质使其看起来湿润。 接下来,我们需要对雨滴进行动画。可以使用three.js的动画循环来不断更新雨滴的位置和状态。可以在每个循环中将雨滴向下移动一点,并在其到达屏幕底部时重新设置其位置,以模拟雨滴的连续下落。 为了增加真实感,可以使用雨滴的粒子效果。可以通过在场景中生成多个具有不同位置、速度和大小的雨滴来创建这种粒子效果。可以使用随机数生成器来确定每个雨滴的属性,使其看起来更自然。 此外,为了增强雨滴效果,可以添加光线和阴影效果。可以使用three.js的光线和材质设置来为雨滴和场景添加光线效果。可以使用灯光来模拟阳光或其他光源,并使用阴影贴图来为雨滴创建阴影效果,使其看起来更加逼真。 通过以上方法,我们可以在three.js中实现下雨效果。这种效果会给场景带来动态和有趣的元素,增加了视觉吸引力和真实感。无论是用于游戏、可视化效果还是其他3D场景,下雨效果都可以为场景增添一些特殊的氛围和体验。 ### 回答2: three.js 是一个用于在网页上创建交互式 3D 图形的 JavaScript 库。要在 three.js 中实现下雨效果,可以通过创建多个雨滴的模型,然后设置它们的位置、移动方向和速度来模拟雨滴下落的动画。 首先,我们需要创建一个场景和相机,以及渲染器来显示效果。然后,我们可以创建一个雨滴的几何体,可以是立方体或者圆柱体,并设置它们的大小和颜色。然后,通过设置每个雨滴的初始位置,我们可以将它们放置在屏幕上方的不同位置,使它们看起来像是从天空落下。 在渲染循环中,我们可以通过每帧更新每个雨滴的位置,使它们沿着垂直向下的方向移动。可以通过给每个雨滴添加一个速度属性来控制它们的下落速度,并根据需要调整速度值以实现不同的下雨效果。 此外,为了使下雨效果更加真实,我们可以为每个雨滴添加一个透明度属性,使它们看起来渐隐渐现。可以通过在逐渐下降的过程中降低透明度的值,并在达到一定透明度后重置它们的位置来实现这个效果。 最后,为了保持动画的连续性,我们可以在雨滴超出屏幕底部后将它们重新放置到屏幕顶部,以实现无限下雨效果。 总结起来,通过在 three.js 中创建多个雨滴的模型,并设置它们的位置、速度和透明度,我们可以实现一个下雨效果。这样可以增加场景的动态感,使得用户在浏览网页时获得更加生动和有趣的体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值