03-Cesium自定义着色器完整代码以及注释

文章展示了如何使用CesiumJavaScript库创建一个自定义着色器,包括顶点着色器和片段着色器的代码,以实现3D平面上动态颜色变化的效果。通过设置顶点数据、纹理坐标和统一变量,实现了对3D图形的渲染控制。

1. 效果展示

2. 完整代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>自定义着色器完整代码</title>
    <!-- 线上 -->
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.107.1/Build/Cesium/Cesium.js"></script>
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.107.1/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />

    <style>
      #map {
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
      }
    </style>
  </head>

  <body>
    <div id="map"></div>
    <script>
      Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhZjZkZDAwZC1mNTFhLTRhOTEtOGExNi00MzRhNGIzMDdlNDQiLCJpZCI6MTA1MTUzLCJpYXQiOjE2NjA4MDg0Njd9.qajeJtc4-kppqfR1--Y2FqCu5r3TE1xYYGnEQhy-JF0'
      const viewer = new Cesium.Viewer('map', {
        infoBox: false,
        shadows: true,
        terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY,
        terrain: Cesium.Terrain.fromWorldTerrain(),
      })

      //平面的顶点数据
      let points = [110.2, 20.0, 112.2, 20.0, 110.2, 22.0, 112.2, 22.0]
      let positions = Cesium.Cartesian3.fromDegreesArray(points)
      //顶点对应的纹理坐标数据
      let sts = [0, 0, 0, 1, 1, 0, 1, 1]

      //顶点卷绕的方式
      let positionIndex = [0, 1, 2, 1, 2, 3]

      function primitivePlaneShader(options) {
        console.log(options, 'options')
        let viewer = options.viewer
        let vertexShader = v_shader()
        let fragShader = f_shader()
        let sts = new Uint8Array(options.sts) //纹理数据
        let positionIndex = new Uint16Array(options.positionIndex) //顶点索引数据
        let tempPosition = []
        for (var i = 0; i < options.positions.length; i++) {
          tempPosition.push(options.positions[i].x)
          tempPosition.push(options.positions[i].y)
          tempPosition.push(options.positions[i].z)
        }
        positions = new Float64Array(tempPosition) //顶点数据
        let geometry = createGeometry(positions, sts, positionIndex) //几何体
        let appearance = createAppearance(vertexShader, fragShader) //外观
        //primitive方式加载
        viewer.scene.primitives.add(
          new Cesium.Primitive({
            geometryInstances: new Cesium.GeometryInstance({
              //渲染的几何体
              geometry: geometry,
            }),
            appearance: appearance, //外观
            asynchronous: false,
          })
        )
      }
      function createAppearance(vertexShader, fragShader) {
        return new Cesium.Appearance({
          material: new Cesium.Material({
            /* 在Cesium中,fabric 是用于创建着色器材质(Shader Material)的对象。
            它允许您定义一种着色器材质类型,并通过uniforms(统一变量)传递外部数据给着色器。 */
            fabric: {
              /* 
              uniforms是一个包含统一变量(Uniform)的对象。
              统一变量是一种在着色器程序中定义的全局变量,
              它们的值可以在渲染过程中从CPU(JavaScript代码)传递给GPU(着色器代码)。 */
              uniforms: {
                speed: 0.1,
              },
              source: getMS(),
            },
          }),
          translucent: false, //显示不为半透明
          renderState: {
            blending: Cesium.BlendingState.PRE_MULTIPLIED_ALPHA_BLEND, //使用Alpha混合功能启用混合
            depthTest: { enabled: true }, //深度检测
            depthMask: true, //将深度值写入深度缓冲区
          },
          fragmentShaderSource: fragShader, //片段着色器
          vertexShaderSource: vertexShader, //顶点着色器
        })
      }
      //构建几何体
      function createGeometry(positions, sts, positionIndex) {
        return new Cesium.Geometry({
          attributes: {
            //几何顶点属性
            position: new Cesium.GeometryAttribute({
              componentDatatype: Cesium.ComponentDatatype.DOUBLE, //数据类型
              componentsPerAttribute: 3, //定义几个为一组
              values: positions, //坐标值
            }),
            st: new Cesium.GeometryAttribute({
              componentDatatype: Cesium.ComponentDatatype.FLOAT, //数据类型
              componentsPerAttribute: 2, //定义几个为一组
              values: sts, //坐标值
            }),
          },
          indices: positionIndex, //顶点索引
          primitiveType: Cesium.PrimitiveType.TRIANGLES, //图元类型
          boundingSphere: Cesium.BoundingSphere.fromVertices(positions), //包围球
        })
      }
      function v_shader() {
        // 这段着色器代码的作用是将顶点变换到裁剪空间,并传递纹理坐标给片段着色器,为后续的光栅化和渲染阶段做准备。
        return `
                in vec3 position3DHigh;//这是一个输入变量,表示高精度的3D位置。通常情况下,这个变量的值是由CesiumJS引擎在渲染过程中提供的,它表示当前处理的顶点或片段的高精度3D位置。
            		in vec3 position3DLow;//这也是一个输入变量,表示低精度的3D位置。在CesiumJS中,当高精度3D位置无法完全表示时,这个变量提供额外的信息,以便更准确地确定位置。
            		in float batchId;//这是一个输入变量,表示批次ID。在CesiumJS中,通常用于将相同类型的几何体分组在一起,以优化渲染性能。
            		in vec2 st;//这是一个输入变量,表示纹理坐标。
            		out vec2 v_st; //这是一个输出变量,表示处理后的纹理坐标。
            		void main() {
                  // czm_computePosition() //计算模型视图相对眼睛坐标空间中的顶点位置。
                  //czm_modelViewProjectionRelativeToEye//变换顶点位置到裁剪空间,并将结果保存在position中。
            		  vec4 position = czm_modelViewProjectionRelativeToEye *czm_computePosition();
                  //将输入变量st赋值给输出变量v_st,这样纹理坐标将在顶点着色器中传递给片段着色器。
            		   v_st = st;
                   //将经过变换后的顶点位置position赋值给内置变量gl_Position,这样OpenGL将使用这个值来确定顶点在屏幕上的位置。
            		   gl_Position = position;
            		}
              `
      }

      function f_shader() {
        return `
              in vec2 v_st;//这是一个输入变量,表示纹理坐标。在片段着色器中,v_st变量将包含插值后的纹理坐标值,这些值在顶点着色器中计算并传递给片段着色器。
              //uniform float speed;
              void main() {
                //这一行将纹理坐标(v_st)转换为范围在[-1, 1]的坐标(position)。在OpenGL中,纹理坐标的范围通常是[0, 1],而转换后的坐标在顶点着色器的插值过程中进行了变换,使其范围变为[-1, 1]。
            		   vec2 position = -1.0 + 2.0 *v_st;
                   //这行代码调用一个函数getSpeed(),该函数返回一个浮点数,表示速度。
            		   float speed = getSpeed();
                   //这一行根据帧号(czm_frameNumber)和速度(speed)计算时间(time)。czm_frameNumber是CesiumJS引擎提供的一个内置变量,表示当前渲染帧的序号。
            		   float time= czm_frameNumber *speed;
                   //计算颜色
            		   float r = abs( cos( position.x * position.y + time / 5.0 ));
            		   float g = abs( sin( position.x * position.y + time / 4.0 ) );
            		   float b = abs( cos( position.x * position.y + time / 3.0 ) );
                   //并将其赋值给特殊的输出变量out_FragColor,这个变量是CesiumJS中用于存储片段着色器输出颜色的内置变量。
            		   out_FragColor = vec4( r, g, b, 1.0 );
            		}

              `
      }

      function getMS() {
        return `#version 300 es
              uniform float speed;
              float getSpeed(){
            		  return speed;
            		}
              `
      }

      var plane = new primitivePlaneShader({
        viewer: viewer,
        positions: positions,
        sts: sts,
        positionIndex: positionIndex,
      })
      viewer.scene.camera.setView({
        destination: new Cesium.Cartesian3.fromDegrees(110.2, 20.0, 500000),
      })
    </script>
  </body>
</html>

 

<think> 我们正在处理Cesium自定义材质着色器编译错误的调试问题。根据用户之前的问题和错误信息("Unable to infer material type: [object Object]"),我们已经解决了材质注册和实例化的问题。现在用户询问调试着色器编译错误的方法。 在Cesium中,当自定义材质的GLSL代码有错误时,会在控制台输出错误信息。但是,这些错误信息可能不够详细,因此我们需要一些调试技巧。 以下是调试Cesium着色器编译错误的步骤: 1. **捕获着色器编译错误**: Cesium在创建材质时,如果着色器编译失败,会在控制台输出错误信息。我们可以通过`viewer.scene.renderError`事件来捕获这些错误。 2. **使用简单测试着色器**: 将复杂的着色器代码替换为最简单的着色器(例如,只输出红色),然后逐步添加代码,直到找到出错的部分。 3. **利用Cesium的调试工具**: Cesium提供了一些调试工具,例如`Cesium.createShaderProgram`和`Cesium.ShaderProgram`,可以用于编译和链接着色器,并获取详细的错误信息。 4. **使用外部GLSL编辑器**: 使用支持GLSL语法高亮和错误检查的编辑器(如GlslEditor)来编写和测试着色器代码,然后再集成到Cesium中。 5. **检查浏览器开发者工具**: 在浏览器的开发者工具中,可以查看着色器源码和编译状态。例如,在Chrome中,可以通过“开发者工具”->“Sources”->“Shaders”查看编译后的着色器。 具体步骤: **步骤1:监听渲染错误事件** ```javascript viewer.scene.renderError.addEventListener(function(scene, error) { console.error('渲染错误:', error); }); ``` **步骤2:创建着色器程序并检查编译状态** 我们可以使用Cesium的低级API来编译着色器并获取编译日志: ```javascript // 顶点着色器源码(使用Cesium默认的顶点着色器) const vertexShaderSource = ` attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec4 color; attribute float batchId; varying vec4 v_color; void main() { vec4 p = czm_computePosition(); gl_Position = czm_modelViewProjectionRelativeToEye * p; v_color = color; } `; // 片段着色器源码(自定义材质) const fragmentShaderSource = `...你的着色器代码...`; // 创建着色器程序 const shaderProgram = Cesium.ShaderProgram.fromCache({ context: viewer.scene.context, vertexShaderSource: vertexShaderSource, fragmentShaderSource: fragmentShaderSource, attributeLocations: { position3DHigh: 0, position3DLow: 1, color: 2, batchId: 3 } }); // 检查编译状态 if (!shaderProgram._vertexShaderCompiled) { console.error("顶点着色器编译错误:", shaderProgram._vertexShaderCompilationLog); } if (!shaderProgram._fragmentShaderCompiled) { console.error("片段着色器编译错误:", shaderProgram._fragmentShaderCompilationLog); } ``` **步骤3:使用GlslEditor进行离线调试** 将着色器代码复制到GlslEditor中,利用其错误提示功能检查语法错误。GlslEditor支持实时预览和错误提示[^1]。 **步骤4:在Cesium中输出着色器源码** 当材质创建失败时,Cesium会将错误的着色器源码输出到控制台。我们可以复制这些源码到外部工具进行调试。 **步骤5:逐步简化着色器** 将复杂的着色器拆解,逐步注释掉部分代码,直到错误消失,从而定位错误的位置。 **示例:简化后的测试着色器** ```glsl czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); // 只输出红色,测试基本功能 material.diffuse = vec3(1.0, 0.0, 0.0); material.alpha = 1.0; return material; } ``` 如果这个最简单的着色器能够工作,再逐步添加功能(如使用uniform变量、数学计算、纹理采样等),直到找出导致错误的代码段。 **常见错误原因**: - 语法错误(缺少分号、括号不匹配等) - 未声明的变量 - 类型不匹配 - 函数参数错误 - 使用了Cesium未提供的内置变量或函数 **在自定义材质中获取更多调试信息**: 如果着色器编译通过但渲染效果不正确,可以使用`discard`语句来定位问题区域。例如,在着色器的特定分支中丢弃像素,观察渲染结果。 ```glsl if (condition) { discard; // 丢弃像素(显示为透明) } ``` **总结调试步骤**: 1. 使用简单着色器测试,确认基本功能是否正常。 2. 逐步添加代码,每次添加后测试编译。 3. 利用Cesium的`ShaderProgram`获取编译日志。 4. 使用GlslEditor提前检查语法错误[^1]。 5. 在浏览器开发者工具中查看运行时着色器源码和变量。 通过以上方法,可以高效定位并解决着色器编译错误。 §§相关问题§§ 1. 如何在Cesium中实现动态变化的材质效果? 2. Cesium中如何优化自定义材质的性能? 3. 在Cesium中如何为3D模型应用自定义材质? </think> ### 调试Cesium着色器编译错误的完整指南 在Cesium中调试GLSL着色器编译错误需要系统化的方法。以下是关键步骤和工具: #### 1. **捕获编译错误信息** ```javascript // 监听场景渲染错误事件 viewer.scene.renderError.addEventListener(function(scene, error) { console.error("渲染错误:", error); // 获取着色器编译日志 const shaders = scene.context.shaderCache; shaders.shaders.forEach(shader => { if (!shader.compileStatus) { console.error("顶点着色器错误:", shader.vertexShaderCompilationLog); console.error("片段着色器错误:", shader.fragmentShaderCompilationLog); } }); }); ``` #### 2. **使用GLSL编辑器实时调试** 推荐使用GlslEditor工具进行离线调试(支持实时错误提示)[^1]: 1. 复制着色器代码到编辑器 2. 开启**实时预览模式** 3. 使用**浮点数滑块**动态调整uniform值 4. 查看**错误面板**获取详细编译信息 ```glsl // 示例:添加调试输出 czm_material czm_getMaterial(czm_materialInput i) { czm_material m = czm_getDefaultMaterial(i); // 调试输出:红色表示错误区域 if (isnan(i.st.x)) { m.diffuse = vec3(1.0, 0.0, 0.0); return m; } // ...原始代码... } ``` #### 3. **浏览器开发者工具调试** 1. **Chrome/Firefox**: - 打开开发者工具 → Sources → Shaders - 查看编译后的着色器代码 - 使用`console.log`等效: ```glsl // 输出变量值(需转换为颜色) m.diffuse = vec3(fract(st.x), fract(st.y), 0.0); ``` 2. **专用扩展**: - WebGL Inspector(捕获着色器状态) - Spector.js(帧调试器) #### 4. **逐步简化法定位错误** ```javascript // 步骤1:使用最小化着色器测试 const testMaterial = new Cesium.Material({ fabric: { source: `czm_material czm_getMaterial(czm_materialInput i) { czm_material m = czm_getDefaultMaterial(i); m.diffuse = vec3(1.0, 0.0, 0.0); // 纯红色 return m; }` } }); // 步骤2:逐步添加复杂逻辑 // 步骤3:使用discard关键字隔离问题区域 if (problemCondition) { discard; // 丢弃问题像素 } ``` #### 5. **常见错误类型及解决** | 错误类型 | 检测方法 | 解决方案 | |---------|---------|---------| | 语法错误 | GLSL编译器报行号 | 检查缺少分号/括号[^3] | | 类型不匹配 | 变量值可视化 | 显式类型转换`float(value)` | | 未初始化 | 赋默认值测试 | 初始化所有变量 | | 精度问题 | 添加`precision highp float;` | 统一变量精度 | | 纹理采样 | 替换纯色测试 | 检查UV坐标范围 | #### 6. **高级调试技巧** 1. **边界值注入**: ```glsl // 显示UV边界 if(st.x < 0.01 || st.x > 0.99 || st.y < 0.01 || st.y > 0.99) { m.diffuse = vec3(0.0, 1.0, 0.0); // 绿色边框 } ``` 2. **Uniform动态监控**: ```javascript // 实时更新uniform值观察变化 let time = 0; viewer.scene.preUpdate.addEventListener(() => { arrowMaterial.uniforms.time = time++; }); ``` 3. **使用Cesium内置函数**: ```glsl // 替代自定义计算 vec3 worldPos = czm_eyeToWindowCoordinates(eyePos); // 避免坐标转换错误 ``` > **重要提示**:在Cesium Sandcastle中测试时可启用"Disable shader optimization"选项,保留原始变量名便于调试[^2]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值