Cesium 体积云实践总结

本文介绍了如何在Cesium中实现体积云,通过自定义primitive和体渲染技术,结合perlin和worley分形噪声,以及Three.js的源码改造,创建出具有立体效果的云区域。作者分享了关键代码段和参数调整对效果的影响,尽管只实现了局部云区,但为全局云区的扩展提供了基础。
摘要由CSDN通过智能技术生成

Cesium 体积云实践总结

先上结果图
在这里插入图片描述
在这里插入图片描述

基础知识学习和参考

实现体积云有三个重要的知识点:自定义primitive、体渲染、perlin+worley 分形噪声混合。

自定义primitive和体渲染

感谢大佬 Bro_Of_Nagi 的文章,关于自定义primitive和体渲染参考此文章,大佬不止给出了思路还有git源码。
由于我使用的cesium版本不便于修改源码,所以使用的是2d纹理存储体数据的方法,使用cesium源码的可以参考大佬的新文章直接修改为使用Texture3D。
源码中比较关键的是这个/texture3D/src/lxs_volumn.js,要理解几个关键部分:

  1. 体数据生成(205行),其实就是三维数组用一个Uint8Array存储,每个位置都存放一个0-255的数表示噪声大小,通常表示透明度。
  2. 射线碰撞取样(51行),计算相机到几何box的射线后,采用步进的方式获取每步的体数据进行叠加,从而实现立体效果。

成功应用起来的话,可以得到如文章截图的有很多立体彩色方块的box。

然后参考threejs的体积云源码webgl2_volume_cloud,参考(227行)开始的内容重写关于采样累加计算的部分逻辑。
重写后如下,我将参数都直接写成了数字,这些参数不同得到的效果也不同,可以在threejs的example webgl2_volume_cloud中调节查看。

gl_FragColor = vec4(0.8,0.8,0.8,0.);
for ( float t = bounds.x; t < bounds.y; t += delta ){
    
    float d = getData(p  + halfdim);
    
    d = smoothstep( 0.25 - 0.05, 0.25 + 0.05, d ) * 0.1;
    float col = shading(p + halfdim) * 3.0 +(( p.x + p.z ) * 0.25 ) + 0.2 ;//threejs与cesium的轴方向不同所以是z
    gl_FragColor.rgb += ( 1.0 - gl_FragColor.a ) * d * col;
    gl_FragColor.a += ( 1.0 - gl_FragColor.a ) * d;
    if ( gl_FragColor.a >= 0.95 ) break;

    p+=rayDir*delta;
}

这样修改后,可以看到cesium中渲染出来的和threejs example中的效果是一样的一团云。

perlin+worley 分形噪声混合

看源码中使用的noise方法其实就是threejs源码中用到的perlin噪声算法,这在生成一团云时看起来还行,但是如果要生成一大片的云区还是不太够的。
通过查询资料(参考这篇实时体积云渲染(地平线):二.Perlin噪声和Worley噪声,直接看FBM、Worley噪声、Perlin-Worley噪声),采用 Perlin-Worley噪声得到的云区图还是比较仿真的。开整!

既然是Perlin-Worley噪声,那么Perlin噪声的算法threejs源码中提供了,还需要Worley噪声。在网上搜索了很多资料后找到了一段glsl语法实现的Worley噪声二维算法,我将其改成了三维算法,后续又转换为了js以节省算力,各位可以自行转一下。

// 这个方法里的常量不同得到的结果就不同
vec3 random(vec3 st){
    return  fract(
        sin(
            vec3(
                dot(st, vec3(127.1,311.7, 51.1)),
                dot(st, vec3(269.5,183.3, 23.)),
                dot(st, vec3(305.2,250.3, 113.))
            )
        ) * 43758.5453
    );
}
float getWorleyNoise(vec3 pos_lxs){
    vec3 pos = pos_lxs/(halfdim*2.);
    vec3 p = clamp(pos,0.,1.  );
    p *= 4.;
    vec3 i = floor(p); // 获取当前网格索引i
    vec3 f = fract(p); // 获取当前片元在网格内的相对位置
    float F1 = 1.;
    // 遍历当前像素点相邻的9个网格特征点
    for (int j = -1; j <= 1; j++) {
        for (int k = -1; k <= 1; k++) {
            for (int l = -1; l <= 1; l++) {

                vec3 neighbor = vec3(float(j), float(k), float(l));
                vec3 point = random(i + neighbor);
                float d = length(point + neighbor - f);
                F1 = min(F1,d);
            }
        }
    }
    return F1;
}

获得Worley噪声算法后,修改生成体数据的算法,加入Worley噪声的fbm,同时将原有Texture参数中pixelFormat的alpha通道改为RGBA通道,以便传输新增的噪声数据。

/**
 * 生成体数据
 */
const size = 128;
//data在0~255之间
const data = new Uint8Array(size * size * size * 4);//增加到4通道
let dx, dy, dz;
let i = 0;
for (let z = 0; z < size; z++) {
    for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
            /* 实际代码并非如此,本段只是示意 */
            dx = x * 1.0 / size;
            dy = y * 1.0 / size;
            dz = z * 1.0 / size;
            const d = noise(dx * 6.5, dy * 6.5, dz * 6.5);
            data[i] = d * 128 + 128;//r通道存储Perlin噪声,此处可对Perlin噪声也进行fbm混合,生成更混乱的图案
            // 之后3个通道分别存储3个不同波高的worley噪声,在shader里再进行混合。
            data[i+1] = 1 - worley.noise(p[0] % 1, p[1] % 1, p[2] % 1)//对1求余避免超界
            data[i+2] = 1 - worley.noise(p[0]*2 % 1, p[1]*2 % 1, p[2]*2 % 1)
            data[i+3] = 1 - worley.noise(p[0]*4 % 1, p[1]*4 % 1, p[2]*4 % 1)
            i++
        }
    }
}
this.texture=new Texture3D({
	width:size,
	height:size,
	depth:size,
	context: context,
	flipY: false,
	pixelFormat: Cesium.PixelFormat.RGBA,//改成RGBA以传输4个通道
	pixelDataType: Cesium.ComponentDatatype.fromTypedArray(
	    this.data
	),
	source: {
	    width: texture_size,
	    height: texture_size,
	    arrayBufferView: this.data,
	},
	sampler: new Cesium.Sampler({
	    minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
	    magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR,
	}),
})

之后在shader中使用这个材质时,就可以通过读取不同通道来获取对应的噪声图了。关于混合比例,我采用的是 ( texture.g * 0.625 ) + ( texture.b * 0.25 ) + ( texture.a * 0.125 ); 混合的fbm,再用texture.r减去fbm,并重映射到0-1的范围。

总结

我目前仅做到了一片区域的云区,范围设太大的话性能也不好,看到有其他大佬做了全球云区,根据实际云图生成的,可惜没有透露做法。不过目前这个效果暂时可用,就先这样吧。

  • 23
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值