likes
comments
collection
share

[ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

作者站长头像
站长
· 阅读数 218

3D夜景特效中经常会出现流动道路光束,常用是一种方法,建模师根据道路生成一张贴图texture,然后通过改变offset来实现光束流动的效果。 [ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

而今天我带大家实现另一种更炫酷的无限光束效果,如下图所见。

[ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

1.画光束

创建光束

这里的光束就是TubeGeometry管道,创建n条直的管道。


const spline = new THREE.LineCurve3(
              new THREE.Vector3(0, 0, that.height * 0.25),
              new THREE.Vector3(0, 0, -that.height * 0.75)
            );
 //直的管道
const geometry = new THREE.TubeGeometry(spline, that.height, that.lineWidth, 8, false);
const materials = [];
            const amount = that.amount;
            const step = (that.width - that.gap) / amount;
            for (let i = 0; i < amount; i++) {
              const c = new THREE.Color();
              const v = i / amount;
              c.setHSL(
                THREE.MathUtils.lerp(that.hueStart, that.hueEnd, v),
                1,
                THREE.MathUtils.lerp(that.lightStart, that.lightEnd, v)
              );
              const mesh = new THREE.Mesh(geometry, material);
              //x坐标平移到等比的位置
              mesh.position.x = i * step + (i > amount * 0.5-1 ? that.gap : 0);
              //y坐标随机上移
              mesh.position.y = Math.random() * 5;
              this.scene.add(mesh);
  • x坐标平移到等比的位置,中间间隔一段距离,用于区别不同方向的流动光束
  • y坐标随机上移,为了后面光束流动的时候更好看。

[ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

让光束流动起来

顶点着色器

varying vec2 vUv;
void main(void) {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

片元着色器

varying vec2 vUv;
uniform float uSpeed;
uniform float uTime;
uniform vec2 uFade;
uniform vec3 uColor;
uniform float uDirection;
void main() {
    vec3 color = uColor;
   
   //流动方向
    float s = -uTime * uSpeed;
    float v = 0.0;
    if(uDirection == 1.0) {
        v = vUv.x;
    } else {
        v = -vUv.x;
    }
    
    float d = mod((v + s), 1.0);
    if(d > uFade.y)
        discard;
    else {
        //平滑透明度渐变
        float alpha = smoothstep(uFade.x, uFade.y, d);
        //透明度太小时不显示
        if(alpha < 0.001)
            discard;
        gl_FragColor = vec4(color, alpha);
    }
}

参数说明

  • uSpeed流动速度
  • uTime随时间变化,范围0-1
  • uColor光束颜色
  • uFade光束渐变,x:开始渐变程度,y结束渐变程度
  • uDirection光束方向:1前进方向,0往回的方向
  • smoothstep(uFade.x, uFade.y, d)smoothstep平滑透明度渐变可以根据距离,从当前点往后可以形成渐变尾迹的效果
  • mod((v + s), 1.0)将(v + s)距离mod取模是为了让效果不停重复。

将材质改为ShaderMaterial,这里不用InstancedMesh是因为uniforms参数也不同的,material不能复用。

const commonUniforms = {
            uFade: { value: new THREE.Vector2(0, 0.6) } 
          };
const material = new THREE.ShaderMaterial({
                side: THREE.DoubleSide,
                transparent: true,
                uniforms: {
                  uColor: { value: c },
                  //uTime设置为随机的开始时间
                  uTime: { value: THREE.MathUtils.lerp(-1, 1, Math.random()) },
                  //左侧一半是往前,右侧一半是往回
                  uDirection: { value: i < amount * 0.5 ? 1 : 0 },
                  //随机加速
                  uSpeed: { value: THREE.MathUtils.lerp(1, 1.5, Math.random()) },
                  ...commonUniforms
                },
                vertexShader: ``,
                fragmentShader
              });

添加动画,让材质跟着时间动起来

this.materials.forEach((m) => {
              m.uniforms.uTime.value += this.speed;
              if (m.uniforms.uTime.value > 1) {
                m.uniforms.uTime.value = 0;
              }
            });

[ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

让光束发光

采用BloomPass后期特效

initBloom() {
          const params = {符合符合
            threshold: 0,
            strength: 0.5,
            radius: 0.5,
            exposure: 0.5
          };
          const renderScene = new RenderPass(this.scene, this.camera);
          //strength = 1, kernelSize = 25, sigma = 4          
          const bloomPass = new BloomPass(5, 20, 100);
          bloomPass.threshold = params.threshold;
          bloomPass.strength = params.strength;
          bloomPass.radius = params.radius;

          const composer = new EffectComposer(this.renderer);
          composer.addPass(renderScene);
          composer.addPass(bloomPass);
          const outputPass = new OutputPass();
          composer.addPass(outputPass);

          this.composer = composer;
        }
animate(){
 if (this.composer) {
            //必须关闭autoClear,避免渲染效果被清除
            this.renderer.autoClear = false;
            this.renderer.clear(); 

            this.composer.render();
            this.renderer.clearDepth(); 
          }

          this.renderer.render(this.scene, this.camera);
          }

[ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

2.光束顺着弯曲公路走

顶点着色器弄弯曲形状

float PI = acos(-1.0);
uniform vec2 uOffset;
varying vec2 vUv;
//随机左右偏移
float getMove(float u, float offset) {
    float a = u * PI * 2.0;
    return sin(a + PI * 0.25) * u * offset;
}
//随机上下偏移
float getHeight(float u, float offset) {
    float a = u * PI * 3.0;
    return cos(a) * u * offset;
}

void main(void) {
    vUv = uv;
    
    float m = getMove(uv.x, uOffset.x);
    float h = getHeight(uv.x, uOffset.y);

    vec3 newPosition = position;
    newPosition.x = newPosition.x + m;
    newPosition.y = newPosition.y + h;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}

将光束的顶点着色器改成对应的形状。 [ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

添加一条公路平面

添加公路平面。平面的面数有足够才可以形成曲面,所以widthSegmentsheightSegments的数量设置要注意。

const geometry = new THREE.PlaneGeometry(
                that.width,
                that.height,
                that.width * 0.25,
                that.height * 0.25
              );

const material = new THREE.ShaderMaterial({
                side: THREE.DoubleSide,
                transparent: true,
                uniforms: {
                  uColor: { value: new THREE.Color('gray') },                 
                  ...commonUniforms
                },
                vertexShader: ``,
                fragmentShader: `
              uniform vec3 uColor;
              void main() {
              gl_FragColor =vec4(uColor,0.6);
              } `
              });
              this.planeMat = material;
              const plane = new THREE.Mesh(geometry, material);
              plane.rotateX(-Math.PI * 0.5);
              plane.position.set(that.width * 0.5, -1, -that.height * 0.25);
              this.normalObj = plane;
              plane.visible = false;
              this.scene.add(plane);

注意

  • 公路平面最好是透明的。因为辉光效果与背景紧密关联,如果非透明状态,会使得辉光效果失效。可以看到,与背景相接的光束保有辉光效果,但在不透明的公路范围内的光束失去了辉光效果。

[ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

顶点着色器与光束的相同,只不过平面的z坐标对应3D形状的y坐标,代表上下高度,对应调整一下着色器代码即可。

newPosition.z= newPosition.z+h ;

3.画个爱心送给你

当然流动光速不限于道路特效,还能这么用,最适合送给你喜欢的人! [ThreeJs Shader]炫酷无限光束效果3D夜景特效中经常会出现流动道路光束,如何用Three.js搞一个炫酷的

  • 心形曲线

class HeartCurve extends THREE.Curve {
        constructor(scale = 1) {
          super();
          this.scale = scale;
        }

        getPoint(a, optionalTarget = new THREE.Vector3()) {
          const t = a * Math.PI * 2;
          const tx = 16 * Math.pow(Math.sin(t), 3);
          const ty = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
          const tz = 0;

          return optionalTarget.set(tx, ty, tz).multiplyScalar(this.scale);
        }
      }

然后添加两道流动光束,大功告成!

const commonUniforms = {
            uFade: { value: new THREE.Vector2(0, 0.5) },
            uDirection: { value: 1 },
            uSpeed: { value: 1 },
          };
          const vertexShader=`varying vec2 vUv;
                void main(void) {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                }`
                //同流动光束片元着色器
          const fragmentShader=``
//粉色半边心
{
              const c = new THREE.Color('pink');

              const material = new THREE.ShaderMaterial({
                side: THREE.DoubleSide,
                transparent: true,
                uniforms: {
                  uColor: { value: c },
                  uTime: { value: 0 }, 
                  ...commonUniforms
                },
                vertexShader  ,
                fragmentShader
              });
              materials.push(material);
              const mesh = new THREE.Mesh(geometry, material);
              this.scene.add(mesh);
            }
//蓝色半边心
            {
              const c = new THREE.Color('#00BFFF');

              const material = new THREE.ShaderMaterial({
                side: THREE.DoubleSide,
                transparent: true,
                uniforms: {
                  uColor: { value: c },
                  uTime: { value: 0.5 }, 
                  ...commonUniforms
                },
                vertexShader,
                fragmentShader
              });
              materials.push(material);
              const mesh = new THREE.Mesh(geometry, material);
              this.scene.add(mesh);
            }

注意: 两边的光束基本参数一致,uFade渐变尾迹范围占一半即0.5,开始的时间uTime相隔0.5,正好一边走一半。

Github地址

https://github.com/xiaolidan00/my-earth

转载自:https://juejin.cn/post/7386485874300223514
评论
请登录