用three.js实现炫酷的城市扫光效果

3,170 阅读4分钟

最近由于工作需要实现一些比较炫酷的3d城市扫光效果,现在分享给大家思路和方法 先看效果图:在这里插入图片描述在这里插入图片描述具体怎么实现的那? 首先像扫光这种效果是3D比较基本的效果,我们可以使用后处理、模版、材质三种方式来实现,鉴于后处理和模版这两块用three.js构建起来如果大家不熟悉会非常困难(但是维护起来确非常简单,如果实现了MRT,用后处理的方式性能也是最好的),因此本篇主要讲述如何用材质的方式来实现扫光效果。 用材质实现也有两种思路: 第一种,我们可以回忆下点光源是如何实现的,就是在片元着色器判断该点光源到每一个片元的距离,对吧?按照这种思路,我们如果实现圆环形扫光就可以直接在片元判断该片元是否在这个环形区域内就搞定了,如果是片形扫光我们可以判断该片元是否在该包围盒内对吧。 下面看下片形扫光着色器代码 vertexShader:

varying vec4 v_position;
#include <common>
#include <logdepthbuf_pars_vertex>//这个是为了支持logDepth
#ifdef USE_RELATIVE
uniform mat4 relativeModelMatrix;
#endif

        void main() {

        vec4 pos = vec4(position, 1.0);

        gl_Position = projectionMatrix * modelViewMatrix * pos;
        #include <logdepthbuf_vertex>

       #ifdef USE_RELATIVE
        v_position = relativeModelMatrix * pos;
        #else
         v_position = pos;
        #endif

      }
fragmentShader:
      uniform vec3 color;
      uniform vec3 maxPos;
      uniform vec3 minPos;
      varying vec4 v_position;
      #ifdef USE_UVMAP
        uniform sampler2D uvMap;
      #endif
      uniform vec3 direction;
    #include <logdepthbuf_pars_fragment>
      bool isMax(vec3 a, vec3 b){
         float ax = a.x;
         float ay = a.y;
         float az = a.z;
         float bx = b.x;
         float by = b.y;
         float bz = b.z;
         return ax > bx && ay > by && az > bz;
     }
      bool isMin(vec3 a, vec3 b){
         float ax = a.x;
         float ay = a.y;
         float az = a.z;
         float bx = b.x;
         float by = b.y;
         float bz = b.z;
         return ax < bx && ay < by && az < bz;
     }
      void main() {
      #include <logdepthbuf_fragment>
        float subb = length(maxPos.z - minPos.z);
        float gap = length(v_position.z - minPos.z);
        vec2 texCoord = vec2((gap / subb), 0.5);
        gl_FragColor = mix(vec4(0,0,0,0),vec4(color, 1.0),float(isMin(v_position.xyz,maxPos) && isMax(v_position.xyz,minPos)));
        #ifdef USE_UVMAP
           gl_FragColor.a *= texture2D( uvMap, texCoord ).r;//可以传入一个渐变贴图,防止扫光太生硬
        #endif
      }

看一下是不是非常简单,就是模拟一个包围盒,将该包围盒的minPos和maxPos传入,然后在片元判断每个片元的position是否在该包围盒里,也就是 gl_FragColor = mix(vec4(0,0,0,0),vec4(color, 1.0),float(isMin(v_position.xyz,maxPos) && isMax(v_position.xyz,minPos)));如果在包围盒里就渲染扫光区域,如果不在就渲染成透明的,这样就有了扫光效果。 我们分析下用这种方式实现扫光的利与弊: 好处:1.用这种方式实现你就可以随意控制包围盒,可以平着扫,可以斜着扫(斜着扫会有冲击的效果) 2.性能也不错 3.通用性强,任意的物体你都可以扫 弊端:1.用这种方式维护起来非常困难,因为在地球上实现扫光,包围盒的构建还是比较麻烦的,而且大尺度下精度不够,还需要使用相对值。 2.如果要实现圆形扫光或者任意图形扫光又要再写一个材质,用户很难自定义。 下图是该材质下的效果,还是很炫酷的,哈哈: 在这里插入图片描述在这里插入图片描述 下面介绍第二种方式:计算uv 这种思路非常简单,就是哪些物体需要扫光效果,我们就为它们整体计算uv,为这个区域预先计算出水平包围盒,然后整体去计算uv,如果我们不需要冲击的效果,可以让物体的高度不影响uv的权重,如果想有冲击的效果则高度应该给一个权重(注:如果实在不理解怎么计算uv,而且你又是用geojson创建的楼宇,那你可以用经纬度直接计算uv,这种方式最简单。所有uv的计算建议在worker中计算) 预处理完uv之后,我们可以想象一下,我们贴什么图,就可以扫出什么形状来,而且还可以用材质动画作出更加炫酷的效果,这些效果可以直接给美工,让他们去做,哈哈,是不是顿时轻松了许多(本人建议,效果方面能传图片的千万不要写shader,因为如果可以用图片实现,那么想要什么效果,用户可以直接去找美术,哈哈哈) 好了,看看着色器吧 vertexShader:

#include <common>
#include <logdepthbuf_pars_vertex>
varying vec2 vp;
uniform mat3 uvTransform;
attribute vec2 coordinates;
void main() {
        vec4 pos = vec4(position, 1.0);
          vec2 vUv = ( uvTransform * vec3( coordinates, 1 ) ).xy;
          vp = vUv;
        gl_Position = projectionMatrix * modelViewMatrix * pos;
         #include <logdepthbuf_vertex>
}

fragmentShader:

                varying vec2 vp;
                uniform vec3 scanningColor;
				uniform sampler2D map;
                
                #include <logdepthbuf_pars_fragment>
               
                void main(){
                #include <logdepthbuf_fragment>
             float r = texture2D(map, vp).r;
              gl_FragColor = r * vec4(scanningColor, 1.0);
                }

是不是非常简单: 我们传入一张图片 在这里插入图片描述效果: 在这里插入图片描述是不是还挺炫酷