zoukankan      html  css  js  c++  java
  • 溯源:Cesium.Appearance 中的顶点着色器

    对于一个 MaterialAppearance 对象来说,它的顶点着色器代码默认为:

    attribute vec3 position3DHigh;
    attribute vec3 position3DLow;
    attribute vec3 normal;
    attribute vec2 st;
    attribute float batchId;
    
    varying vec3 v_positionEC;
    varying vec3 v_normalEC;
    varying vec2 v_st;
    
    void main()
    {
      vec4 p = czm_computePosition();
    
      v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates
      v_normalEC = czm_normal * normal;                         // normal in eye coordinates
      v_st = st;
    
      gl_Position = czm_modelViewProjectionRelativeToEye * p;
    }
    

    0. 预备知识

    Cesium 拥有一个小规模的内置 glsl 库,预置了非常多 czm_ 开头的函数、结构、常量。在自定义着色器的 Appearance、Material 类中,允许直接使用,Cesium 会自动把着色器代码合并、链接。

    1. czm_computePosition()

    很不幸的是,这个函数并不是完整地内置在 Source/Shaders 下的,Source/Shaders/Builtin/computePosition.glsl 这里面只有函数的声明,并没有,它的函数实现是动态生成的。

    幸运的是,通过全文检索,我找到了它的生成代码,它位于 Primitive.js 文件中:

    这个函数有点长,大家可以自己去看源代码

    Primitive._modifyShaderPosition = function (
      primitive,
      vertexShaderSource,
      scene3DOnly
    ) {
      // ...
    };
    

    Cesium 会用正则去匹配你写的 attribute vec3 XXX3DHighattribute vec3 XXX3DLow 这两个顶点属性(attribute)中的 XXX,并以之构造函数名 czm_computeXXX,当然它会把首字母给你大写了以符合函数命名规范。

    默认情况下,这个 XXX 就等于 position。所以,抽丝剥茧,在我们关心的三维模式下,这个函数其实可以省略成这样:

    attribute vec3 position3DHigh;
    attribute vec3 position3DLow;
    
    vec4 czm_computePosition() {
      return czm_translateRelativeToEye(position3DHigh, position3DLow);
    }
    

    到这一步,看到实际上作用的函数是 czm_translateRelativeToEye(vec3, vec3),而这一个函数在 内置的 glsl 中是有的了。

    2. czm_translateRelativeToEye(vec3, vec3)

    vec4 czm_translateRelativeToEye(vec3 high, vec3 low)
    {
        vec3 highDifference = high - czm_encodedCameraPositionMCHigh;
        vec3 lowDifference = low - czm_encodedCameraPositionMCLow;
    
        return vec4(highDifference + lowDifference, 1.0);
    }
    

    它的作用是,将世界坐标减去相机中心坐标,返回一个齐次坐标,即将世界坐标平移到相机坐标系下,而不旋转。

    有的朋友可能会有两个疑问:①世界坐标在哪里?②相机坐标在哪里?

    对于返回值 vec4(highDifference + lowDifference, 1.0) 这个有 WebGL 和图形变换基础的朋友应该不用做多解释,它就是一个齐次坐标。

    那么,世界坐标和相机坐标呢?代码中明明是 high、low、czm_encodedCameraPositionMCHigh、czm_encodedCameraPositionMCLow 啊?

    这要从一个不在 API 中的类:EncodedCartesian3 说起了。

    补充 EncodedCartesian3:编码后的笛卡尔坐标

    众所周知,64位浮点数(双精度)和 32位浮点数的精度是不一样的,也正是因为 Cesium 的设计初衷:世界级高精度三维地图引擎,导致了空间中的坐标值数值比较大。不要拘泥于地表,Cesium 的范围大至太阳系。

    所以,直接将 Cartesian3 的坐标值传递给 WebGL 顶点着色器,有可能在重重 MVP 矩阵计算下丢失精度,出现图形顶点不准确、坐标抖动问题。

    参考文献:Precisions, Precisions | STK Components for .NET 2020 r4 (agi.com)

    数学大佬就想出了这个编码后的笛卡尔坐标对象,将原来的笛卡尔坐标拆成两个,一大一小,大的叫 High,小的叫 Low,实际上两者相加后,与原始 Cartesian3 几乎是一致的。

    可以这么创建编码后的笛卡尔坐标:

    const eCartesian3 = Cesium.EncodedCartesian3.fromCartesian(new Cesium.Cartesian3(-2644963.9889313546, 5763731.142118295, 2199400.7089496767))
    

    还原也很简单:

    const origin = Cesium.Cartesian3.add(eCartesian3.high, eCartesian3.low)
    

    具体算法很简单,读者有兴趣可以自己去看源码。

    现在,我们继续原来的讨论。

    把 glsl 的代码稍微整理一下,不难看出其实是这样算的:

    return vec4(high + low - (czm_encodedCameraPositionMCHigh + czm_encodedCameraPositionMCLow), 1.0);
    

    而前面的 high + low,根据刚才的理论,即传入的两个 attribute vec3 的相加,也就是原始世界坐标。

    后面的 czm_encodedCameraPositionMCHigh + czm_encodedCameraPositionMCLow,这两个并不是静态的内置变量,而是写在 AutomaticUniforms.js 中的一个动态 uniform vec3 值,它俩其实也是 EncodedCartesian3 的 high、low 分量,相加的结果,根据上面的结论,代表的是相机在世界坐标系统中的坐标。

    世界坐标 - 相机中心坐标,这是什么?平移啊!

    这就解释了 czm_translateRelativeToEye 这个函数名的语义了。

    3. czm_modelViewProjectionRelativeToEye

    这是一个动态的 uniform,写在 AutomaticUniforms.js 中,看看代码:

    czm_modelViewProjectionRelativeToEye: new AutomaticUniform({
      size: 1,
      datatype: WebGLConstants.FLOAT_MAT4,
      getValue: function (uniformState) {
        return uniformState.modelViewProjectionRelativeToEye;
      },
    }),
    

    它是个4x4的矩阵,作用是将相机坐标转换至裁剪坐标。

    所以不难得出顶点着色器的最后一行代码含义:

    gl_Position = czm_modelViewProjectionRelativeToEye * p;
    

    它就是把 p 这个由 czm_computePosition 而实际是由 czm_translateRelativeToEye 函数(三维模式下)算来的齐次坐标,转换到裁剪坐标,并交给 gl_Position 这个 WebGL 变量,进入片元着色阶段。

    4. AutomaticUniforms.js

    上面提及两次这个文件,这个文件的设置了一堆 glsl uniform 标识的 glsl 量,这些量会根据 Cesium 当前的状态(例如相机位置等)实时计算,并传入着色器代码中。

    5. 顶点着色器修改

    有了上述基础,想修改 Primitive 的顶点就十分容易了,最简单的思路是,根据 position3DHighposition3DLow 算出原始的世界坐标,这个时候就跟 Cesium Cartesian3 API 是一样的数值了,充分利用 cesium 提供的预置 glsl 工具函数、常量、结构体,按常规思路去计算你想要的结果,就可以完成对顶点的重塑。

    而 “地形压平”、“3DTiles 瓦片压平” 的思路就是基于此,但是,找到地形的 Primitive、3DTiles 的 Primitive,还有很长一段路要走,譬如 Cesium 对数据的跟新机制、渲染循环机制、视锥体与绘制命令的机制的熟读等等知识。

  • 相关阅读:
    iOS resign code with App Store profile and post to AppStore
    HTTPS科普扫盲帖 对称加密 非对称加密
    appid 评价
    使用Carthage安装第三方Swift库
    AngularJS:何时应该使用Directive、Controller、Service?
    xcode7 The operation couldn't be completed.
    cocoapods pod install 安装报错 is not used in any concrete target
    xcode7 NSAppTransportSecurity
    learning uboot how to set ddr parameter in qca4531 cpu
    learning uboot enable protect console
  • 原文地址:https://www.cnblogs.com/onsummer/p/14141907.html
Copyright © 2011-2022 走看看