学习threejs,实现发光信息流效果

👨‍⚕️ 主页: gis分享者

👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!

👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • [1.1 ☘️THREE.ShaderMaterial](#1.1 ☘️THREE.ShaderMaterial)
      • [1.1.1 ☘️注意事项](#1.1.1 ☘️注意事项)
      • [1.1.2 ☘️构造函数](#1.1.2 ☘️构造函数)
      • [1.1.3 ☘️属性](#1.1.3 ☘️属性)
      • [1.1.4 ☘️方法](#1.1.4 ☘️方法)
  • 二、🍀实现发光信息流效果
    • [1. ☘️实现思路](#1. ☘️实现思路)
    • [2. ☘️代码样例](#2. ☘️代码样例)

一、🍀前言

本文详细介绍如何基于threejs在三维场景中实现发光信息流效果,亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️THREE.ShaderMaterial

THREE.ShaderMaterial使用自定义shader渲染的材质。 shader是一个用GLSL编写的小程序 ,在GPU上运行。

1.1.1 ☘️注意事项

  • ShaderMaterial 只有使用 WebGLRenderer 才可以绘制正常, 因为 vertexShader 和
    fragmentShader 属性中GLSL代码必须使用WebGL来编译并运行在GPU中。
  • 从 THREE r72开始,不再支持在ShaderMaterial中直接分配属性。 必须使用
    BufferGeometry实例,使用BufferAttribute实例来定义自定义属性。
  • 从 THREE r77开始,WebGLRenderTarget 或 WebGLCubeRenderTarget
    实例不再被用作uniforms。 必须使用它们的texture 属性。
  • 内置attributes和uniforms与代码一起传递到shaders。
    如果您不希望WebGLProgram向shader代码添加任何内容,则可以使用RawShaderMaterial而不是此类。
  • 您可以使用指令#pragma unroll_loop_start,#pragma unroll_loop_end
    以便通过shader预处理器在GLSL中展开for循环。 该指令必须放在循环的正上方。循环格式必须与定义的标准相对应。
  • 循环必须标准化normalized。
  • 循环变量必须是i。
  • 对于给定的迭代,值 UNROLLED_LOOP_INDEX 将替换为 i 的显式值,并且可以在预处理器语句中使用。
javascript 复制代码
#pragma unroll_loop_start
for ( int i = 0; i < 10; i ++ ) {

	// ...

}
#pragma unroll_loop_end

代码示例

javascript 复制代码
const material = new THREE.ShaderMaterial( {
	uniforms: {
		time: { value: 1.0 },
		resolution: { value: new THREE.Vector2() }
	},
	vertexShader: document.getElementById( 'vertexShader' ).textContent,
	fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );

1.1.2 ☘️构造函数

ShaderMaterial( parameters : Object )

parameters - (可选)用于定义材质外观的对象,具有一个或多个属性。 材质的任何属性都可以从此处传入(包括从Material继承的任何属性)。

1.1.3 ☘️属性

共有属性请参见其基类Material

.clipping : Boolean

定义此材质是否支持剪裁; 如果渲染器传递clippingPlanes uniform,则为true。默认值为false。

.defaultAttributeValues : Object

当渲染的几何体不包含这些属性但材质包含这些属性时,这些默认值将传递给shaders。这可以避免在缓冲区数据丢失时出错。

javascript 复制代码
this.defaultAttributeValues = {
	'color': [ 1, 1, 1 ],
	'uv': [ 0, 0 ],
	'uv2': [ 0, 0 ]
};

.defines : Object

使用 #define 指令在GLSL代码为顶点着色器和片段着色器定义自定义常量;每个键/值对产生一行定义语句:

javascript 复制代码
defines: {
	FOO: 15,
	BAR: true
}

这将在GLSL代码中产生如下定义语句:

javascript 复制代码
#define FOO 15
#define BAR true

.extensions : Object

一个有如下属性的对象:

javascript 复制代码
this.extensions = {
	derivatives: false, // set to use derivatives
	fragDepth: false, // set to use fragment depth values
	drawBuffers: false, // set to use draw buffers
	shaderTextureLOD: false // set to use shader texture LOD
};

.fog : Boolean

定义材质颜色是否受全局雾设置的影响; 如果将fog uniforms传递给shader,则为true。默认值为false。

.fragmentShader : String

片元着色器的GLSL代码。这是shader程序的实际代码。在上面的例子中, vertexShader 和 fragmentShader 代码是从DOM(HTML文档)中获取的; 它也可以作为一个字符串直接传递或者通过AJAX加载。

.glslVersion : String

定义自定义着色器代码的 GLSL 版本。仅与 WebGL 2 相关,以便定义是否指定 GLSL 3.0。有效值为 THREE.GLSL1 或 THREE.GLSL3。默认为空。

.index0AttributeName : String

如果设置,则调用gl.bindAttribLocation 将通用顶点索引绑定到属性变量。默认值未定义。

.isShaderMaterial : Boolean

只读标志,用于检查给定对象是否属于 ShaderMaterial 类型。

.lights : Boolean

材质是否受到光照的影响。默认值为 false。如果传递与光照相关的uniform数据到这个材质,则为true。默认是false。

.linewidth : Float

控制线框宽度。默认值为1。

由于OpenGL Core Profile与大多数平台上WebGL渲染器的限制,无论如何设置该值,线宽始终为1。

.flatShading : Boolean

定义材质是否使用平面着色进行渲染。默认值为false。

.uniforms : Object

如下形式的对象:

javascript 复制代码
{ "uniform1": { value: 1.0 }, "uniform2": { value: 2 } }

指定要传递给shader代码的uniforms;键为uniform的名称,值(value)是如下形式:

javascript 复制代码
{ value: 1.0 }

这里 value 是uniform的值。名称必须匹配 uniform 的name,和GLSL代码中的定义一样。 注意,uniforms逐帧被刷新,所以更新uniform值将立即更新GLSL代码中的相应值。

.uniformsNeedUpdate : Boolean

可用于在 Object3D.onBeforeRender() 中更改制服时强制进行制服更新。默认为假。

.vertexColors : Boolean

定义是否使用顶点着色。默认为假。

.vertexShader : String

顶点着色器的GLSL代码。这是shader程序的实际代码。 在上面的例子中,vertexShader 和 fragmentShader 代码是从DOM(HTML文档)中获取的; 它也可以作为一个字符串直接传递或者通过AJAX加载。

.wireframe : Boolean

将几何体渲染为线框(通过GL_LINES而不是GL_TRIANGLES)。默认值为false(即渲染为平面多边形)。

.wireframeLinewidth : Float

控制线框宽度。默认值为1。

由于OpenGL Core Profile与大多数平台上WebGL渲染器的限制,无论如何设置该值,线宽始终为1。

1.1.4 ☘️方法

共有方法请参见其基类Material

.clone () : ShaderMaterial this : ShaderMaterial

创建该材质的一个浅拷贝。需要注意的是,vertexShader和fragmentShader使用引用拷贝; attributes的定义也是如此; 这意味着,克隆的材质将共享相同的编译WebGLProgram; 但是,uniforms 是 值拷贝,这样对不同的材质我们可以有不同的uniforms变量。

二、🍀实现发光信息流效果

1. ☘️实现思路

通过threejs的ShaderMaterial自定义着色器实现发光信息流效果。具体代码参考下面代码样例。

2. ☘️代码样例

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            overflow: hidden;
            margin: 0;
        }
        #info {
            position: absolute;
            width: 100%;
            top: 50%;
            text-align: center;
            font-family: 'Orbitron', sans-serif;
            font-size: 2vh;
            color: black;
        }
    </style>
    <script type="importmap">
      {
        "imports": {
            "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.163.0/three.module.min.js",
            "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"
        }
    }
    </script>
</head>
<body>
<div id="info">patience... we're deforming the spheres</div>
<script type="x-shader/x-vertex" id="vertexshader">
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  }
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
  uniform sampler2D baseTexture;
  uniform sampler2D bloomTexture;
  varying vec2 vUv;
  void main() {
    gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) );
  }
</script>

<script>
  const noiseV3 = `
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
float permute(float x){return floor(mod(((x*34.0)+1.0)*x, 289.0));}
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;}
vec4 grad4(float j, vec4 ip){
  const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
  vec4 p,s;
  p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
  p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
  s = vec4(lessThan(p, vec4(0.0)));
  p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
  return p;
}
float snoise(vec4 v){
  const vec2  C = vec2( 0.138196601125010504,  // (5 - sqrt(5))/20  G4
                        0.309016994374947451); // (sqrt(5) - 1)/4   F4
// First corner
  vec4 i  = floor(v + dot(v, C.yyyy) );
  vec4 x0 = v -   i + dot(i, C.xxxx);
  vec4 i0;
  vec3 isX = step( x0.yzw, x0.xxx );
  vec3 isYZ = step( x0.zww, x0.yyz );
//  i0.x = dot( isX, vec3( 1.0 ) );
  i0.x = isX.x + isX.y + isX.z;
  i0.yzw = 1.0 - isX;
//  i0.y += dot( isYZ.xy, vec2( 1.0 ) );
  i0.y += isYZ.x + isYZ.y;
  i0.zw += 1.0 - isYZ.xy;
  i0.z += isYZ.z;
  i0.w += 1.0 - isYZ.z;
  // i0 now contains the unique values 0,1,2,3 in each channel
  vec4 i3 = clamp( i0, 0.0, 1.0 );
  vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
  vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
  //  x0 = x0 - 0.0 + 0.0 * C
  vec4 x1 = x0 - i1 + 1.0 * C.xxxx;
  vec4 x2 = x0 - i2 + 2.0 * C.xxxx;
  vec4 x3 = x0 - i3 + 3.0 * C.xxxx;
  vec4 x4 = x0 - 1.0 + 4.0 * C.xxxx;
// Permutations
  i = mod(i, 289.0);
  float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);
  vec4 j1 = permute( permute( permute( permute (
             i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
           + i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
           + i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
           + i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
// Gradients
// ( 7*7*6 points uniformly over a cube, mapped onto a 4-octahedron.)
// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
  vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
  vec4 p0 = grad4(j0,   ip);
  vec4 p1 = grad4(j1.x, ip);
  vec4 p2 = grad4(j1.y, ip);
  vec4 p3 = grad4(j1.z, ip);
  vec4 p4 = grad4(j1.w, ip);
// Normalise gradients
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;
  p4 *= taylorInvSqrt(dot(p4,p4));
// Mix contributions from the five corners
  vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
  vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4)            ), 0.0);
  m0 = m0 * m0;
  m1 = m1 * m1;
  return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
               + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
}
  `;
  const noise = `
  // https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
  /*float rand(vec2 c){
  return fract(sin(dot(c.xy ,vec2(12.9898,78.233))) * 43758.5453);
}*/
float noise(vec2 p, float freq ){
  float unit = 2.; //screenWidth/freq;
  vec2 ij = floor(p/unit);
  vec2 xy = mod(p,unit)/unit;
  //xy = 3.*xy*xy-2.*xy*xy*xy;
  xy = .5*(1.-cos(PI*xy));
  float a = rand((ij+vec2(0.,0.)));
  float b = rand((ij+vec2(1.,0.)));
  float c = rand((ij+vec2(0.,1.)));
  float d = rand((ij+vec2(1.,1.)));
  float x1 = mix(a, b, xy.x);
  float x2 = mix(c, d, xy.x);
  return mix(x1, x2, xy.y);
}
float pNoise(vec2 p, int res){
  float persistance = .5;
  float n = 0.;
  float normK = 0.;
  float f = 4.;
  float amp = 1.;
  int iCount = 0;
  for (int i = 0; i<50; i++){
    n+=amp*noise(p, f);
    f*=2.;
    normK+=amp;
    amp*=persistance;
    if (iCount == res) break;
    iCount++;
  }
  float nf = n/normK;
  return nf*nf*nf*nf;
}
  `;
  const flVert = `
  #include <common>
    #include <color_pars_vertex>
    #include <fog_pars_vertex>
    #include <logdepthbuf_pars_vertex>
    #include <clipping_planes_pars_vertex>
    uniform float linewidth;
    uniform vec2 resolution;
    attribute vec3 instanceStart;
    attribute vec3 instanceEnd;
    attribute vec3 instanceColorStart;
    attribute vec3 instanceColorEnd;
    #ifdef WORLD_UNITS
      varying vec4 worldPos;
      varying vec3 worldStart;
      varying vec3 worldEnd;
      varying vec2 vUv;
    #else
      varying vec2 vUv;
    #endif
      attribute float instanceDistanceStart;
      attribute float instanceDistanceEnd;
      varying float vLineDistance;
    void trimSegment( const in vec4 start, inout vec4 end ) {
      // trim end segment so it terminates between the camera plane and the near plane
      // conservative estimate of the near plane
      float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
      float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
      float nearEstimate = - 0.5 * b / a;
      float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
      end.xyz = mix( start.xyz, end.xyz, alpha );
    }
    void main() {
      #ifdef USE_COLOR
        vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
      #endif
        vLineDistance = ( position.y < 0.5 ) ? instanceDistanceStart : instanceDistanceEnd;
        vUv = uv;
      float aspect = resolution.x / resolution.y;
      // camera space
      vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
      vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
      #ifdef WORLD_UNITS
        worldStart = start.xyz;
        worldEnd = end.xyz;
      #else
        vUv = uv;
      #endif
      // special case for perspective projection, and segments that terminate either in, or behind, the camera plane
      // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
      // but we need to perform ndc-space calculations in the shader, so we must address this issue directly
      // perhaps there is a more elegant solution -- WestLangley
      bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
      if ( perspective ) {
        if ( start.z < 0.0 && end.z >= 0.0 ) {
          trimSegment( start, end );
        } else if ( end.z < 0.0 && start.z >= 0.0 ) {
          trimSegment( end, start );
        }
      }
      // clip space
      vec4 clipStart = projectionMatrix * start;
      vec4 clipEnd = projectionMatrix * end;
      // ndc space
      vec3 ndcStart = clipStart.xyz / clipStart.w;
      vec3 ndcEnd = clipEnd.xyz / clipEnd.w;
      // direction
      vec2 dir = ndcEnd.xy - ndcStart.xy;
      // account for clip-space aspect ratio
      dir.x *= aspect;
      dir = normalize( dir );
      #ifdef WORLD_UNITS
        // get the offset direction as perpendicular to the view vector
        vec3 worldDir = normalize( end.xyz - start.xyz );
        vec3 offset;
        if ( position.y < 0.5 ) {
          offset = normalize( cross( start.xyz, worldDir ) );
        } else {
          offset = normalize( cross( end.xyz, worldDir ) );
        }
        // sign flip
        if ( position.x < 0.0 ) offset *= - 1.0;
        float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) );
        // don't extend the line if we're rendering dashes because we
        // won't be rendering the endcaps
        #ifndef USE_DASH
          // extend the line bounds to encompass  endcaps
          start.xyz += - worldDir * linewidth * 0.5;
          end.xyz += worldDir * linewidth * 0.5;
          // shift the position of the quad so it hugs the forward edge of the line
          offset.xy -= dir * forwardOffset;
          offset.z += 0.5;
        #endif
        // endcaps
        if ( position.y > 1.0 || position.y < 0.0 ) {
          offset.xy += dir * 2.0 * forwardOffset;
        }
        // adjust for linewidth
        offset *= linewidth * 0.5;
        // set the world position
        worldPos = ( position.y < 0.5 ) ? start : end;
        worldPos.xyz += offset;
        // project the worldpos
        vec4 clip = projectionMatrix * worldPos;
        // shift the depth of the projected points so the line
        // segements overlap neatly
        vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd;
        clip.z = clipPose.z * clip.w;
      #else
        vec2 offset = vec2( dir.y, - dir.x );
        // undo aspect ratio adjustment
        dir.x /= aspect;
        offset.x /= aspect;
        // sign flip
        if ( position.x < 0.0 ) offset *= - 1.0;
        // endcaps
        if ( position.y < 0.0 ) {
          offset += - dir;
        } else if ( position.y > 1.0 ) {
          offset += dir;
        }
        // adjust for linewidth
        offset *= linewidth;
        // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
        offset /= resolution.y;
        // select end
        vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
        // back to clip space
        offset *= clip.w;
        clip.xy += offset;
      #endif
      gl_Position = clip;
      vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
      #include <logdepthbuf_vertex>
      #include <clipping_planes_vertex>
      #include <fog_vertex>
    }
  `;
  const flFrag = `
  uniform float time;
  uniform float bloom;
  uniform vec3 diffuse;
    uniform float opacity;
    uniform float linewidth;
    #ifdef USE_DASH
      uniform float dashOffset;
      uniform float dashSize;
      uniform float gapSize;
    #endif
    varying float vLineDistance;
    #ifdef WORLD_UNITS
      varying vec4 worldPos;
      varying vec3 worldStart;
      varying vec3 worldEnd;
      varying vec2 vUv;
    #else
      varying vec2 vUv;
    #endif
    #include <common>
    #include <color_pars_fragment>
    #include <fog_pars_fragment>
    #include <logdepthbuf_pars_fragment>
    #include <clipping_planes_pars_fragment>
    vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) {
      float mua;
      float mub;
      vec3 p13 = p1 - p3;
      vec3 p43 = p4 - p3;
      vec3 p21 = p2 - p1;
      float d1343 = dot( p13, p43 );
      float d4321 = dot( p43, p21 );
      float d1321 = dot( p13, p21 );
      float d4343 = dot( p43, p43 );
      float d2121 = dot( p21, p21 );
      float denom = d2121 * d4343 - d4321 * d4321;
      float numer = d1343 * d4321 - d1321 * d4343;
      mua = numer / denom;
      mua = clamp( mua, 0.0, 1.0 );
      mub = ( d1343 + d4321 * ( mua ) ) / d4343;
      mub = clamp( mub, 0.0, 1.0 );
      return vec2( mua, mub );
    }
    ${noise}
    void main() {
      #include <clipping_planes_fragment>
      #ifdef USE_DASH
        if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
        if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
      #endif
      float alpha = opacity;
      #ifdef WORLD_UNITS
        // Find the closest points on the view ray and the line segment
        vec3 rayEnd = normalize( worldPos.xyz ) * 1e5;
        vec3 lineDir = worldEnd - worldStart;
        vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd );
        vec3 p1 = worldStart + lineDir * params.x;
        vec3 p2 = rayEnd * params.y;
        vec3 delta = p1 - p2;
        float len = length( delta );
        float norm = len / linewidth;
        #ifndef USE_DASH
          #ifdef USE_ALPHA_TO_COVERAGE
            float dnorm = fwidth( norm );
            alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm );
          #else
            if ( norm > 0.5 ) {
              discard;
            }
          #endif
        #endif
      #else
        #ifdef USE_ALPHA_TO_COVERAGE
          // artifacts appear on some hardware if a derivative is taken within a conditional
          float a = vUv.x;
          float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
          float len2 = a * a + b * b;
          float dlen = fwidth( len2 );
          if ( abs( vUv.y ) > 1.0 ) {
            alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 );
          }
        #else
          if ( abs( vUv.y ) > 1.0 ) {
            float a = vUv.x;
            float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
            float len2 = a * a + b * b;
            if ( len2 > 1.0 ) discard;
          }
        #endif
      #endif

      float pn1 = abs(pNoise(vec2(vLineDistance * 0.05, time), 3)) * 0.75;
      float pn2 = pNoise(vec2(vLineDistance * 25. + time * 4., 0.123), 10) * 0.5 + 0.5;
      pn2 = clamp(pow(pn2, 4.), 0., 1.);
      vec3 c = mix(vec3(0, 0, 0.25), diffuse, pn1);
      c = mix(c, diffuse, pn2);
      vec4 diffuseColor = vec4( c, alpha );

      #include <logdepthbuf_fragment>
      #include <color_fragment>
      gl_FragColor = vec4( diffuseColor.rgb, alpha );
      #include <tonemapping_fragment>
      #include <encodings_fragment>
      #include <fog_fragment>
      #include <premultiplied_alpha_fragment>

      vec3 col = gl_FragColor.rgb;
      gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0), bloom);
      gl_FragColor.rgb = mix(gl_FragColor.rgb, mix(vec3(1), col, bloom), pn2);
    }
  `;
</script>
<script type="module">
  import * as THREE from "three";
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
  import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
  import { Line2 } from "three/addons/lines/Line2.js";
  import { LineMaterial } from "three/addons/lines/LineMaterial.js";
  import { LineGeometry } from "three/addons/lines/LineGeometry.js";
  import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
  import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
  import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
  import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
  const perlin = new ImprovedNoise();
  let v3 = new THREE.Vector3();
  let scene = new THREE.Scene();
  let camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 5000);
  camera.position.set(5, 2, 5).setLength(12);
  let renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(innerWidth, innerHeight);
  renderer.toneMapping = THREE.ReinhardToneMapping;
  document.body.appendChild(renderer.domElement);
  window.addEventListener("resize", () => {
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(innerWidth, innerHeight);
    m.resolution.set(innerWidth, innerHeight);
    bloomPass.resolution.set(innerWidth, innerHeight);
  })
  let controls = new OrbitControls(camera, renderer.domElement);
  controls.enablePan = false;
  controls.enableDamping = true;
  controls.minDistance = 1;
  controls.maxDistance = 15;
  // <CURVE>
  let curvePts = new Array(200).fill().map(p => {
    return new THREE.Vector3().randomDirection();
  })
  let curve = new THREE.CatmullRomCurve3(curvePts, true);
  let pts = curve.getSpacedPoints(200);
  pts.shift();
  curve = new THREE.CatmullRomCurve3(pts, true);
  pts = curve.getSpacedPoints(10000);
  pts.forEach(p => { p.setLength(4) });
  let n = new THREE.Vector3();
  let s = new THREE.Vector3(0.5, 0.5, 1.);
  pts.forEach(p => {
    deform(p);
  })
  let fPts = [];
  pts.forEach(p => { fPts.push(p.x, p.y, p.z) });
  let g = new LineGeometry();
  g.setPositions(fPts);
  let globalUniforms = {
    time: { value: 0 },
    bloom: { value: 0 }
  }
  let m = new LineMaterial({
    color: "magenta",
    worldUnits: true,
    linewidth: 0.0375,
    alphaToCoverage: true,
    onBeforeCompile: shader => {
      shader.uniforms.time = globalUniforms.time;
      shader.uniforms.bloom = globalUniforms.bloom;
      shader.vertexShader = flVert;
      shader.fragmentShader = flFrag;
    }
  });
  m.resolution.set(innerWidth, innerHeight);
  let l = new Line2(g, m);
  l.computeLineDistances();
  scene.add(l);
  // </CURVE>
  // <SPHERE>
  let sg = new THREE.IcosahedronGeometry(1, 70);
  for (let i = 0; i < sg.attributes.position.count; i++) {
    v3.fromBufferAttribute(sg.attributes.position, i);
    deform(v3);
    sg.attributes.position.setXYZ(i, v3.x, v3.y, v3.z);
  }
  let sm = new THREE.MeshBasicMaterial({
    color: 0x7f00ff,
    wireframe: true,
    transparent: true,
    onBeforeCompile: shader => {
      shader.uniforms.bloom = globalUniforms.bloom;
      shader.uniforms.time = globalUniforms.time;
      shader.vertexShader = `
      varying vec3 vN;
      ${shader.vertexShader}
    `.replace(
        `#include <begin_vertex>`,
        `#include <begin_vertex>
        vN = normal;
      `
      );
      //console.log(shader.vertexShader);
      shader.fragmentShader = `
      uniform float bloom;
      uniform float time;
      varying vec3 vN;
      ${noiseV3}
      ${shader.fragmentShader}
    `.replace(
        `#include <dithering_fragment>`,
        `#include <dithering_fragment>
        float ns = snoise(vec4(vN * 1.5, time * 0.5));
        ns = abs(ns);

        vec3 col = mix(diffuse, vec3(0, 1, 1) * 0.5, ns);

        gl_FragColor.rgb = mix(col, vec3(0), bloom);
        gl_FragColor.a = ns;
        gl_FragColor.rgb = mix(gl_FragColor.rgb, col, pow(ns, 16.));
      `
      );
    }
  });
  let sphere = new THREE.Mesh(sg, sm);
  scene.add(sphere);
  // </SPHERE>
  // <LINKS>
  let LINK_COUNT = 50;
  let linkPts = [];
  for (let i = 0; i < LINK_COUNT; i++) {
    let pS = new THREE.Vector3().randomDirection();
    let pE = new THREE.Vector3().randomDirection();
    let division = 100;
    for (let j = 0; j < division; j++) {
      let v1 = new THREE.Vector3().lerpVectors(pS, pE, j / division);
      let v2 = new THREE.Vector3().lerpVectors(pS, pE, (j + 1) / division);
      deform(v1, true);
      deform(v2, true);
      linkPts.push(v1, v2);
    }
  }
  let linkG = new THREE.BufferGeometry().setFromPoints(linkPts);
  let linkM = new THREE.LineDashedMaterial({
    color: 0xffff00,
    onBeforeCompile: shader => {
      shader.uniforms.time = globalUniforms.time;
      shader.uniforms.bloom = globalUniforms.bloom;
      shader.fragmentShader = `
      uniform float bloom;
      uniform float time;
      ${shader.fragmentShader}
    `.replace(
        `if ( mod( vLineDistance, totalSize ) > dashSize ) {
    discard;
  }`,
        ``
      )
        .replace(
          `#include <premultiplied_alpha_fragment>`,
          `#include <premultiplied_alpha_fragment>
        vec3 col = diffuse;
        gl_FragColor.rgb = mix(col * 0.5, vec3(0), bloom);

        float sig = sin((vLineDistance * 2. + time * 5.) * 0.5) * 0.5 + 0.5;
        sig = pow(sig, 16.);
        gl_FragColor.rgb = mix(gl_FragColor.rgb, col * 0.75, sig);
      `
        );
      //console.log(shader.fragmentShader);
    }
  });
  let link = new THREE.LineSegments(linkG, linkM);
  link.computeLineDistances();
  scene.add(link);
  // </LINKS>
  // <BACKGROUND>
  let bg = new THREE.SphereGeometry(1000, 64, 32);
  let bm = new THREE.ShaderMaterial({
    side: THREE.BackSide,
    uniforms: {
      bloom: globalUniforms.bloom,
      time: globalUniforms.time
    },
    vertexShader: `
    varying vec3 vNormal;
    void main() {
      vNormal = normal;
      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
  `,
    fragmentShader: `
    uniform float bloom;
    uniform float time;
    varying vec3 vNormal;
    ${noiseV3}
    void main() {
      vec3 col = vec3(0.012, 0, 0.1);
      float ns = snoise(vec4(vNormal, time * 0.1));
      col = mix(col * 5., col, pow(abs(ns), 0.125));
      col = mix(col, vec3(0), bloom);
      gl_FragColor = vec4( col, 1.0 );
    }
  `
  });
  let bo = new THREE.Mesh(bg, bm);
  scene.add(bo);
  // </BACKGROUND>
  // <BLOOM>
  const params = {
    exposure: 1,
    bloomStrength: 7,
    bloomThreshold: 0,
    bloomRadius: 0
  };
  const renderScene = new RenderPass(scene, camera);
  const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
  bloomPass.threshold = params.bloomThreshold;
  bloomPass.strength = params.bloomStrength;
  bloomPass.radius = params.bloomRadius;
  const bloomComposer = new EffectComposer(renderer);
  bloomComposer.renderToScreen = false;
  bloomComposer.addPass(renderScene);
  bloomComposer.addPass(bloomPass);
  const finalPass = new ShaderPass(
    new THREE.ShaderMaterial({
      uniforms: {
        baseTexture: { value: null },
        bloomTexture: { value: bloomComposer.renderTarget2.texture }
      },
      vertexShader: document.getElementById('vertexshader').textContent,
      fragmentShader: document.getElementById('fragmentshader').textContent,
      defines: {}
    }), 'baseTexture'
  );
  finalPass.needsSwap = true;
  const finalComposer = new EffectComposer(renderer);
  finalComposer.addPass(renderScene);
  finalComposer.addPass(finalPass);
  // </BLOOM>
  let clock = new THREE.Clock();
  info.style.visibility = "hidden";
  renderer.setAnimationLoop(() => {
    let t = clock.getElapsedTime();
    controls.update();
    globalUniforms.time.value = t;
    globalUniforms.bloom.value = 1;
    //renderer.setClearColor(0x000000);
    bloomComposer.render();
    globalUniforms.bloom.value = 0;
    //renderer.setClearColor(0x080032);
    finalComposer.render();
    //renderer.render(scene, camera);
  })
  function deform(p, useLength) {
    let mainR = 5;
    v3.copy(p).normalize();
    let len = p.length();
    let ns = perlin.noise(v3.x * 3, v3.y * 3, v3.z * 3);
    ns = Math.pow(Math.abs(ns), 0.5) * 0.25;
    let r = smoothstep(0.125, 0, Math.abs(v3.x)) - ns;
    p.setLength(mainR - Math.pow(r, 2) * 1);
    p.y *= 1 - 0.5 * smoothstep(0, -mainR, p.y);
    p.y *= 0.75;
    p.x *= 0.75;
    p.y *= 1 - 0.125 * smoothstep(mainR * 0.25, -mainR, p.z);
    p.x *= 1 - 0.125 * smoothstep(mainR * 0.25, -mainR, p.z);
    if (useLength) {
      p.multiplyScalar(len)
    };
    //p.y += 0.5;
  }
  //https://github.com/gre/smoothstep/blob/master/index.js
  function smoothstep(min, max, value) {
    var x = Math.max(0, Math.min(1, (value - min) / (max - min)));
    return x * x * (3 - 2 * x);
  };
</script>
</body>
</html>

效果如下:

相关推荐
军军君014 天前
Three.js基础功能学习十二:常量与核心
前端·javascript·学习·3d·threejs·three·三维
军军君0110 天前
Three.js基础功能学习七:加载器与管理器
开发语言·前端·javascript·学习·3d·threejs·三维
患得患失94922 天前
【ThreeJS】camera-controls---高性能动画相机控制库
threejs·camera-controls
接着奏乐接着舞。25 天前
3D地球可视化教程 - 第6篇:蜂巢网格与自定义几何体
前端·vue.js·3d·threejs
军军君011 个月前
Three.js基础功能学习一:环境资源及基础知识
开发语言·javascript·学习·3d·前端框架·threejs·三维
gis分享者1 个月前
学习threejs,结合anime.js打造炫酷文字粒子星空秀
动画·threejs·粒子·文字·anime·星空·炫酷
gis分享者1 个月前
学习threejs,生成复杂3D迷宫游戏
学习·游戏·3d·threejs·cannon·迷宫·cannon-es
gis分享者2 个月前
学习threejs,使用自定义GLSL 着色器,实现抽象艺术特效
threejs·着色器·glsl·shadermaterial·抽象艺术
allenjiao2 个月前
WebGPU vs WebGL:WebGPU什么时候能完全替代WebGL?Web 图形渲染的迭代与未来
前端·图形渲染·webgl·threejs·cesium·webgpu·babylonjs