最近由於工作需要實現一些比較炫酷的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);
}
是不是非常簡單:
我們傳入一張圖片
效果:
是不是還挺炫酷