作者:nannan
目录
小编有幸参加国网三维性能测评,在本次测评中,要求各厂商做出闪电特效功能。以往我们做天气特效大都是粒子系统,但是闪电特效使用粒子系统做出来效果不是很理想。非常感谢超图研究院三维研发人员的帮助,为我们提供了全新的解决方案。
一、解决思路
1.1 打开场景
var promise = viewer.scene.open("http://www.supermapol.com/realspace/services/3D-CBD-2/rest/realspace");
1.2 制作闪电特效
制作闪电特效需要创建片段着色器。下面是代码思路:
(1)首先定义了一些辅助函数 hash
、hash2
、noise2
、fbm2
和 dseg
用于生成噪声和计算距离等操作。
float hash(float x)
{
return fract(21654.6512 * sin(385.51 * x));
}
float hash(vec2 p)
{
return fract(1654.65157 * sin(15.5134763 * p.x + 45.5173247 * p.y + 5.21789));
}
vec2 hash2(vec2 p)
{
return vec2(hash(p * .754), hash(1.5743 * p + 4.5476351));
}
vec2 add = vec2(1.0, 0.0);
vec2 noise2(vec2 x)
{
vec2 p = floor(x);
vec2 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
vec2 res = mix(mix(hash2(p),
hash2(p + add.xy), f.x),
mix(hash2(p + add.yx), hash2(p + add.xx), f.x), f.y);
return res;
}
vec2 fbm2(vec2 x)
{
vec2 r = vec2(0.0);
float a = 1.0;
for (int i = 0; i < 8; i++)
{
r += noise2(x) * a;
x *= 2.;
a *= .5;
}
return r;
}
float dseg(vec2 ba, vec2 pa)
{
float h = clamp(dot(pa, ba) / dot(ba, ba), -0.2, 1.);
return length(pa - ba * h);
}
(2)定义了三个 uniform 变量:colorTexture
(颜色纹理)、fall_interval
(下降间隔)、mix_factor
(混合因子),以及一个 varying 变量 v_textureCoordinates
(纹理坐标)。
uniform sampler2D colorTexture;
uniform float fall_interval;
uniform float mix_factor;
(3)main
函数是片段着色器的入口点。在这里进行了如下操作:
- 计算当前像素点的坐标
uv
。 - 根据时间和位置计算一些参数,如
iTime
、p
、tgt
等。 - 根据位置
p
计算出颜色值c
。 - 根据算法生成闪电效果的颜色
col
。 - 循环计算闪电路径,更新颜色值
col
。 - 最终根据混合因子
mix_factor
将计算得到的颜色值与输入的颜色纹理进行混合,并输出最终的颜色。
void main(void){
vec2 uv = gl_FragCoord.xy;
float iTime = czm_frameNumber * fall_interval * clamp(fall_interval * 0.1, 0.01, 0.1);
vec2 p = uv / czm_viewport.zw;
vec2 d;
vec2 tgt = vec2(1., -1.);
float c = 0.;
if (p.y >= 0.)
c = (1. - (fbm2((p + .2) * p.y + .1 * iTime)).x) * p.y;
else
c = (1. - (fbm2(p + .2 + .1 * iTime)).x) * p.y * p.y;
vec3 col = vec3(0.);
vec3 col1 = c * vec3(.3, .5, 1.);
float mdist = 100000.;
float t = hash(floor(5. * iTime));
tgt += 4. * hash2(tgt + t) - 1.5;
if (hash(t + 2.3) > .6)
for (int i = 0; i < 100; i++) {
vec2 dtgt = tgt - p;
d = .05 * (vec2(-.5, -1.) + hash2(vec2(float(i), t)));
float dist = dseg(d, dtgt);
mdist = min(mdist, dist);
tgt -= d;
c = exp(-1.2 * dist) + exp(-55. * mdist);
col = c * vec3(.7, .8, 1.);
}
col += col1;
gl_FragColor = mix(texture2D(colorTexture, v_textureCoordinates), vec4(col, 0.0), mix_factor);
}
这段代码实现了一个基于噪声和算法生成的闪电特效的片段着色器。它通过计算像素点的位置和时间,以及噪声函数的运算,生成了一种类似闪电的视觉效果,并将其与输入的颜色纹理进行混合,最终输出到屏幕上。
1.3 控制闪电动画
控制闪电动画,我们需要在场景渲染的纹理或上一个后期处理阶段的输出上运行后期处理阶段,并把后处理阶段添加到集合中。
let LightningPostStage = new SuperMap3D.PostProcessStage({//在场景渲染的纹理或上一个后期处理阶段的输出上运行后期处理阶段。
fragmentShader: fragmentShaderSource,//执行此后处理阶段时要使用的片段着色器。
uniforms: {//一个对象,其属性将用于设置着色器制服。属性可以是常量值或函数。常量值也可以是用作纹理的 URI、数据 URI 或 HTML 元素。
mix_factor: 0.5,//混合系数0-1之间的数
fall_interval: 0.7,//0-1之间的数
}
})
const postProcessStage = viewer.scene.postProcessStages.add(LightningPostStage);//将后处理阶段添加到集合中。
二、完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>闪电</title>
<link href="../../Build/SuperMap3D/Widgets/widgets.css" rel="stylesheet">
<link href="./css/bootstrap.min.css" rel="stylesheet">
<link href="./css/pretty.css" rel="stylesheet">
<link href="./css/bootstrap-select.min.css" rel="stylesheet">
<script src="./js/jquery.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/bootstrap-select.min.js"></script>
<script src="./js/config.js"></script>
<script type="text/javascript" src="../../Build/SuperMap3D/SuperMap3D.js"></script>
</head>
<body>
<div id="Container"></div>
<div id='loadingbar' class="spinner">
<div class="spinner-container container1">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container2">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container3">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
</div>
<script>
function onload(SuperMap3D) {
// 通过config.js中的getEngineType,获取引擎类型(EngineType)用于设置启动方式
var EngineType = getEngineType();
var viewer = new SuperMap3D.Viewer('Container', {
sceneModePicker: true,
navigation: false,
contextOptions: {
contextType: 2, // Webgl2:2 ; WebGPU:3
},
});
window.viewer = viewer;
viewer.scenePromise.then(function (scene) {
init(SuperMap3D, scene, viewer);
});
}
function init(SuperMap3D, scene, viewer) {
viewer.resolutionScale = window.devicePixelRatio;
var promise = viewer.scene.open("http://www.supermapol.com/realspace/services/3D-CBD-2/rest/realspace");
SuperMap3D.when(promise, function () {
// 开启雨景
viewer.scene.postProcessStages.rain.enabled = true;
viewer.scene.postProcessStages.rain.uniforms.angle = 0;
viewer.scene.postProcessStages.rain.uniforms.speed = 14;
const fragmentShaderSource = `
float hash(float x)
{
return fract(21654.6512 * sin(385.51 * x));
}
float hash(vec2 p)
{
return fract(1654.65157 * sin(15.5134763 * p.x + 45.5173247 * p.y + 5.21789));
}
vec2 hash2(vec2 p)
{
return vec2(hash(p * .754), hash(1.5743 * p + 4.5476351));
}
vec2 add = vec2(1.0, 0.0);
vec2 noise2(vec2 x)
{
vec2 p = floor(x);
vec2 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
vec2 res = mix(mix(hash2(p),
hash2(p + add.xy), f.x),
mix(hash2(p + add.yx), hash2(p + add.xx), f.x), f.y);
return res;
}
vec2 fbm2(vec2 x)
{
vec2 r = vec2(0.0);
float a = 1.0;
for (int i = 0; i < 8; i++)
{
r += noise2(x) * a;
x *= 2.;
a *= .5;
}
return r;
}
float dseg(vec2 ba, vec2 pa)
{
float h = clamp(dot(pa, ba) / dot(ba, ba), -0.2, 1.);
return length(pa - ba * h);
}
uniform sampler2D colorTexture;
uniform float fall_interval;
uniform float mix_factor;
varying vec2 v_textureCoordinates;
void main(void){
vec2 uv = gl_FragCoord.xy;
float iTime = czm_frameNumber * fall_interval * clamp(fall_interval * 0.1, 0.01, 0.1);
vec2 p = uv / czm_viewport.zw;
vec2 d;
vec2 tgt = vec2(1., -1.);
float c = 0.;
if (p.y >= 0.)
c = (1. - (fbm2((p + .2) * p.y + .1 * iTime)).x) * p.y;
else
c = (1. - (fbm2(p + .2 + .1 * iTime)).x) * p.y * p.y;
vec3 col = vec3(0.);
vec3 col1 = c * vec3(.3, .5, 1.);
float mdist = 100000.;
float t = hash(floor(5. * iTime));
tgt += 4. * hash2(tgt + t) - 1.5;
if (hash(t + 2.3) > .6)
for (int i = 0; i < 100; i++) {
vec2 dtgt = tgt - p;
d = .05 * (vec2(-.5, -1.) + hash2(vec2(float(i), t)));
float dist = dseg(d, dtgt);
mdist = min(mdist, dist);
tgt -= d;
c = exp(-1.2 * dist) + exp(-55. * mdist);
col = c * vec3(.7, .8, 1.);
}
col += col1;
gl_FragColor = mix(texture2D(colorTexture, v_textureCoordinates), vec4(col, 0.0), mix_factor);
}`
let LightningPostStage = new SuperMap3D.PostProcessStage({//在场景渲染的纹理或上一个后期处理阶段的输出上运行后期处理阶段。
fragmentShader: fragmentShaderSource,//执行此后处理阶段时要使用的片段着色器。
uniforms: {//一个对象,其属性将用于设置着色器制服。属性可以是常量值或函数。常量值也可以是用作纹理的 URI、数据 URI 或 HTML 元素。
mix_factor: 0.5,//混合系数0-1之间的数
fall_interval: 0.7,//0-1之间的数
}
})
const postProcessStage = viewer.scene.postProcessStages.add(LightningPostStage);//将后处理阶段添加到集合中。
$('#loadingbar').remove();
})
}
if (typeof SuperMap3D !== 'undefined') {
window.startupCalled = true;
onload(SuperMap3D);
}
</script>
</body>
</html>
三、最终成果
闪电特效