cesium编程中级(十六)cesium-texture3d

cesium编程中级(十六)cesium-texture3d

来由

今天来学习沙盒中的示例 Volumetric Cloud,其中用到了 Texture3D,Texture3D是1.132 版本中新增的功能。

之前也有过一个云的示例: cloud parameters,使用Cesium.CloudCollection 来实现的,本质上是构建了一个云的二维 texture,然后去渲染,所以旋转的时候,发现像是一个billboard一样,一直只有一面对着你,感觉不够”三维”,

Volumetric Cloud不一样,旋转之后是可以看出变化的,感觉上是真的三维。

介绍

这个示例很长,610行代码(Cesium1.136版本),由几个部分组成,里面有一些注释,我们来捋一遍:

  1. 第1行,引入Cesium;
  2. 第2~111行,引用开源代码(three.js),生成噪声,会用就行了;
  3. 第112~202行,构建自定义GeometryPrimitive(值得学习);
  4. 第203~260行,构建Texture3D(makeTexture3D);
  5. 第261~385行,顶点和片元着色器(重点学习);
  6. 第386~495行,构建场景,调用之前的GeometryPrimitive,有基础的扫一眼就明白了;
  7. 第496~610行,构建左上角的交互控件和样式,不是重点;

那么重点很明显了:

  1. 学习构建 自定义GeometryPrimitive
  2. 学习调用开源代码生成Texture3D需要的数据,那么以后再来Texture4D,Texture5D…,一样的调用,毕竟自己写这个噪点生成的代码,要求太高;
  3. 功能实现的顶点和片元着色器代码;

重点

1. 自定义GeometryPrimitive

GeometryPrimitive 继承自 geometry,需要实现主要方法,我们把骨架抽出来,

class GeometryPrimitive {
    constructor(geometry, options) {
        this.options = options;
        this.geometry = geometry;
    }
    update(frameState) {
        //.....
        //构建DrawCommand
        this._drawCommand = new DrawCommand({
            //....
        });
    }
    destroy() {
        return destroyObject(this);
    }
    isDestroyed() {
        return false;
    }
}

调用的地方:

const primitive = new GeometryPrimitive(boxGeometry, {
  uniformMap: cmdUniforms,
  vertexShaderSource: vertexShader,
  fragmentShaderSource: fragmentShader,
  renderState: renderState,
  modelMatrix,
  pass: Cesium.Pass.TRANSLUCENT,
});

这个样子看,是不是没有那么难了,我们以后自己也可以照这个套路写自定义的GeometryPrimitive

2. 生成Texture3D

这里构建了一个长度为 层列 的数据,用三阶循环填充,可以理解成想组织的数据是尺寸相同,叠在一起的多张相片,把每一行用剪刀裁剪开,然后拼成一个长条。数据通过Cesium.Texture3D封装

new Cesium.Texture3D({
    context: context,
    width: size,
    height: size,
    depth: size,
    flipY: false,
    pixelFormat: Cesium.PixelFormat.RED,
    pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE,
    source: {
      arrayBufferView: data,  //传入数据
      width: size,
      height: size,
      depth: size,
    },
    sampler: new Cesium.Sampler({
      minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
      magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR,
    }),
  });

//...

const texture3D = makeTexture3D(viewer.scene.context);

// 调用更底层的  gl.generateMipmap 方法
texture3D.generateMipmap();
/*
Cesium-1.136\packages\engine\Source\Renderer\Texture3D.js

Texture3D.prototype.generateMipmap = function (hint) {
  //...

  const gl = this._context._gl;
  const target = this._textureTarget;

  gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(target, this._texture);
  gl.generateMipmap(target);
  gl.bindTexture(target, null);
};

*/

到这里,数据准备完成

3. 着色器

顶点着色器

通过顶点着色器获取了两个信息,相机的位置和朝向

片元着色器

  1. 求交 hitBox(…) 计算射线与单位立方体 [±0.5] 的入口 t0 和出口 t1,没有交点就 discard;
  2. 插值 步长 delta = 1 /steps(steps由外部传入,这里是100),就是将 t0 和 t1 的连线插值100层;
  3. 采样 sample1(p) 直接 texture(map, p).r 拿值; shading(…) 用中心差分算梯度;
  4. 体渲染 阈值 threshold、软过渡 range、不透明度 opacity 都是外部 uniform; 前面累积颜色 ac,前面乘 (1-ac.a) 做正面混合,alpha>0.95 提前跳出。
  5. 后处理 linearToSRGB + Cesium 内置 czm_gammaCorrect 做伽玛校正,最后 out_FragColor 输出。

整个代码都是从threejs那边借鉴过来,所以其实我觉得关注点应该在于,着色器移植应该注意什么问题,也许更贴合实际一点

后记

之前我自己也花了不少时间做过这个示例的迁移,虽然出了一个版本,但是总感觉做的不好,当时的纹理是通过Cesium.Texture 传入的,因为当时还没有Cesium.Texture3D,看到这个示例,感觉很亲切。

发表评论