Cesium自定义着色器-模型坐标系

140 阅读3分钟

书接上文:

我们实现了白膜的颜色修改,那如何实现白膜渐变效果呢?这里就涉及到了模型坐标系的概念,要想实现渐变效果,需要找到

  • 模型位置positionMC
  • 模型的坐标轴-即竖直方向

Cesium 使用多个坐标系系统:

1. 主要坐标系概览

坐标系缩写用途特点
世界坐标系WGS84全球定位经纬度高程
固定坐标系ECEF地心坐标地心为原点
眼坐标系EC相机相对相机为原点
模型坐标系MC模型局部模型自身坐标
窗口坐标系Window屏幕像素2D屏幕坐标

2. 详细坐标系说明

2.1 世界坐标系 (WGS84)

// 经纬度高程坐标系
const position = Cesium.Cartographic.fromDegrees(
    longitude, // 经度 (-180 到 180)
    latitude,  // 纬度 (-90 到 90)  
    height     // 高程 (米)
);

2.2 地心地固坐标系 (ECEF - Earth-Centered, Earth-Fixed)

// 以地心为原点的三维直角坐标系
const ecefPosition = Cesium.Cartesian3.fromDegrees(
    longitude, latitude, height
);
// X: 指向本初子午线与赤道交点
// Y: 指向东经90度与赤道交点  
// Z: 指向北极点

2.3 眼坐标系 (Eye Coordinates - EC)

// 以相机为原点的坐标系
// 在着色器中常用:normalEC, positionEC

2.4 模型坐标系 (Model Coordinates - MC)

// 模型自身的局部坐标系
// 在着色器中:positionMC, normalMC
// 原点通常是模型的中心或边界框中心

下面这里重点讲解模型坐标系:

3. 在自定义着色器中的坐标系

顶点着色器可用属性:

void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
    // 模型坐标系
    vec3 positionMC = vsInput.attributes.positionMC;    // 模型坐标位置
    vec3 normalMC = vsInput.attributes.normalMC;        // 模型坐标法线
    
    // 世界坐标系相关
    vec3 positionWC = vsOutput.positionWC;              // 世界坐标位置
    
    // 眼坐标系  
    vec3 positionEC = vsOutput.positionEC;              // 眼坐标位置
}

片元着色器可用属性:

void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
    // 从顶点着色器传递的变量
    vec3 positionEC = fsInput.attributes.positionEC;    // 眼坐标位置
    vec3 normalEC = fsInput.attributes.normalEC;        // 眼坐标法线
    
    // 模型坐标系(如果通过varying传递)
    // vec3 positionMC = v_positionMC;
}

4. 坐标系转换

4.1 内置转换函数:

// 模型坐标 → 眼坐标
vec3 positionEC = czm_modelToEyeCoordinates(positionMC);

// 眼坐标 → 裁剪坐标
vec4 positionCC = czm_modelViewProjection * vec4(positionMC, 1.0);

// 眼坐标 → 世界坐标
vec3 positionWC = czm_eyeToWindowCoordinates(positionEC);

4.2 实际应用示例:

// 计算相机距离(用于雾效、LOD等)
float distanceToCamera = length(fsInput.attributes.positionEC);

// 基于世界坐标的高度着色
vec3 positionWC = fsInput.attributes.positionWC;
float altitude = positionWC.z; // 注意:这可能是地心坐标的高度

5. 确定坐标系方向的调试方法

坐标轴可视化

  • fragmentMain: 片元着色器的主函数,每个像素都会执行一次
  • FragmentInput fsInput: 输入参数,包含像素的各种属性(位置、法线、UV等)
  • inout czm_modelMaterial material: 输入输出参数,用于设置材质的最终外观
  • positionMC: 模型坐标系(Model Coordinates)中的位置
  • fsInput.attributes.positionMC: 从输入参数中获取当前像素在模型坐标系中的位置
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material){
  // 
        vec3 positionMC = fsInput.attributes.positionMC;
    //     // 测试X轴 - 红色
    // material.diffuse = vec3(positionMC.x * 0.01, 0.0, 0.0);
    
    // // 测试Y轴 - 绿色  
    // // material.diffuse = vec3(0.0, positionMC.y * 0.01, 0.0);
    
    // // 测试Z轴 - 蓝色
    // material.diffuse = vec3(0.0, 0.0, positionMC.z * 0.01);

    material.diffuse = vec3(
        positionMC.x * 0.01,  // X轴 - 红色
        positionMC.y * 0.01,  // Y轴 - 绿色  
        positionMC.z * 0.01   // Z轴 - 蓝色
    );
  }

可以很明显看到模型的中心点位置

测试X轴 - 红色(左右)

测试Y轴 - 绿色 (前后)

测试Z轴 - 蓝色(上下)

我这里是z轴是垂直上下方向,y轴是前后方向, x轴是左右方向

为什么 Z 轴是垂直方向?

这是因为:

  1. positionMC 是模型局部坐标系
  2. 模型导入时可能使用了 Z-up 约定
  3. 不同的3D建模软件有不同的默认坐标系
    • 3ds Max, Blender: Z-up
    • Maya, Cinema4D: Y-up
    • Unity, Unreal: Y-up

6. 实现白膜竖直方向上的渐变

fragmentShaderText: `
      void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material){
        vec3 positionMC = fsInput.attributes.positionMC;
        
         material.diffuse = vec3(
            0,   
            positionMC.z * 0.005,           
            0.5 + positionMC.z  * 0.005
        );
  }
`,

7. 最佳实践建议

  1. 先测试确认坐标系方向再编写着色器
  2. 优先使用世界坐标系进行地理相关的计算
  3. 使用模型坐标系进行模型自身的特效
  4. 注意坐标系的缩放因子,不同模型可能有不同的单位