公司动态
cesium修改源码加pass记录
如何在 Cesium Scene.js 中插入自定义 Pass为了实现漂亮的天气需要在 Cesium 的渲染管线中插入自己的 Pass。本文使用的 Cesium 版本是1.144修改源码后需要重新npx gulp build才能生效。目标在packages/engine/Source/Scene/Scene.js的resolveFramebuffers流程中插入一个自定义 Pass。典型执行时机是后处理之前这样既能拿到当前帧的场景颜色又能避免被后处理重复影响。自定义 Pass 的接口一个自定义 Pass 至少需要三个生命周期方法constmyPass{initialize:asyncfunction(){/* 创建 shader/framebuffer/texture */},destroy:function(){/* 释放资源 */},execute:function(context,passState,frameState,sceneTexture){/* 每帧执行 */},};initialize负责异步准备资源execute直接拿到当前场景颜色纹理sceneTexture进行绘制。步骤 1创建 WeatherPass.js在packages/engine/Source/Scene/下新建WeatherPass.jsimportBoundingRectanglefrom../Core/BoundingRectangle.js;importColorfrom../Core/Color.js;importdefinedfrom../Core/defined.js;importdestroyObjectfrom../Core/destroyObject.js;importDrawCommandfrom../Renderer/DrawCommand.js;importPassfrom../Renderer/Pass.js;importRenderStatefrom../Renderer/RenderState.js;importShaderProgramfrom../Renderer/ShaderProgram.js;importShaderSourcefrom../Renderer/ShaderSource.js;importVertexArrayfrom../Renderer/VertexArray.js;importBlendingStatefrom../Scene/BlendingState.js;constvertexShaderSourcein vec2 position; in vec2 st; out vec2 v_textureCoordinates; void main() { gl_Position vec4(position, 0.0, 1.0); v_textureCoordinates st; };constfragmentShaderSourcein vec2 v_textureCoordinates; uniform sampler2D sceneTexture; out vec4 out_FragColor; void main() { vec3 color texture(sceneTexture, v_textureCoordinates).rgb; // 这里可以叠加天气效果雨丝、雾气、体积云合成等 // 示例做一个简单的蓝色雾效叠加 float fog 1.0 - v_textureCoordinates.y; color mix(color, vec3(0.7, 0.8, 0.95), fog * 0.1); out_FragColor vec4(color, 1.0); };functioncreateFullscreenQuad(context){constpositionsnewFloat32Array([-1.0,-1.0,0.0,0.0,1.0,-1.0,1.0,0.0,1.0,1.0,1.0,1.0,-1.0,1.0,0.0,1.0,]);constindicesnewUint16Array([0,1,2,0,2,3]);returnVertexArray.fromGeometry({context:context,geometry:{attributes:{position:{componentDatatype:ComponentDatatype.FLOAT,componentsPerAttribute:2,values:positions,},st:{componentDatatype:ComponentDatatype.FLOAT,componentsPerAttribute:2,values:positions,},},indices:indices,primitiveType:PrimitiveType.TRIANGLES,boundingSphere:newBoundingSphere(Cartesian3.ZERO,1.0),},attributeLocations:{position:0,st:1,},});}functionWeatherPass(context){this._contextcontext;this._vaundefined;this._shaderProgramundefined;this._drawCommandundefined;}WeatherPass.prototype.initializeasyncfunction(){constcontextthis._context;this._vacreateFullscreenQuad(context);this._shaderProgramShaderProgram.fromCache({context:context,vertexShaderSource:newShaderSource({sources:[vertexShaderSource]}),fragmentShaderSource:newShaderSource({sources:[fragmentShaderSource]}),attributeLocations:{position:0,st:1,},});constthatthis;this._drawCommandnewDrawCommand({primitiveType:PrimitiveType.TRIANGLES,vertexArray:this._va,shaderProgram:this._shaderProgram,uniformMap:{sceneTexture:function(){returnthat._sceneTexture;},},renderState:RenderState.fromCache({blending:BlendingState.Alpha_BLEND,depthTest:{enabled:false,},}),pass:Pass.OPAQUE,owner:this,boundingVolume:newBoundingSphere(Cartesian3.ZERO,1.0),});};WeatherPass.prototype.executefunction(context,passState,frameState,sceneTexture,){this._sceneTexturesceneTexture;// 直接绘制到调用方设置的 passState.framebuffer 上this._drawCommand.execute(context,passState);};WeatherPass.prototype.isDestroyedfunction(){returnfalse;};WeatherPass.prototype.destroyfunction(){this._vathis._vathis._va.destroy();this._shaderProgramthis._shaderProgramthis._shaderProgram.destroy();returndestroyObject(this);};exportdefaultWeatherPass;注意上面的代码片段为了简洁省略了部分 import如ComponentDatatype、PrimitiveType、BoundingSphere、Cartesian3实际文件里需要从对应模块引入。步骤 2在 View.js 中初始化和销毁打开packages/engine/Source/Scene/View.js在构造函数里创建 Pass 实例importWeatherPassfrom./WeatherPass.js;functionView(scene,camera,context){// ... 已有代码 ...letweatherPass;if(scene._useWeather!false){// 你可以根据 scene 的开关决定是否启用weatherPassnewWeatherPass(context);weatherPass.initialize();// async这里不 await如果资源依赖异步加载可改为 await}// ...this.weatherPassweatherPass;}在View.prototype.destroy里释放View.prototype.destroyfunction(){// ... 已有销毁代码 ...this.weatherPassthis.weatherPassthis.weatherPass.destroy();returndestroyObject(this);};也可以直接放在 Scene.js 里吗可以但不推荐。技术上完全可以在Scene构造函数里直接this._weatherPass new WeatherPass(this._context)在destroy里释放在resolveFramebuffers里直接this._weatherPass.execute(...)。但 Cesium 的架构是Scene负责调度View负责持有渲染资源sceneFramebuffer、globeDepth、postProcessStages等都在View.js里。把自定义 Pass 也放View.js生命周期和资源管理更一致后续升级或维护也更清晰。除非这个 Pass 是全局单例、不依赖具体 View否则建议跟随View创建和销毁。步骤 3在 Scene.js 的 resolveFramebuffers 中插入执行点打开packages/engine/Source/Scene/Scene.js找到Scene.prototype.resolveFramebuffersScene.prototype.resolveFramebuffersfunction(passState){constcontextthis._context;constenvironmentStatethis._environmentState;constviewthis._view;const{globeDepth,translucentTileClassification}view;if(defined(globeDepth)){globeDepth.prepareColorTextures(context);}...// 自定义 Weather Pass插入点在这里if(defined(view.weatherPass)){// 输出目标如果用后处理写到 sceneFramebuffer否则写回 originalFramebufferpassState.framebufferusePostProcess?sceneFramebuffer.framebuffer:originalFramebuffer;constsceneTexturesceneFramebuffer.getColorTexture(0);view.weatherPass.execute(context,passState,this._frameState,sceneTexture,);}// 后处理if(usePostProcess){view.sceneFramebuffer.prepareColorTextures(context);letinputFramebuffersceneFramebuffer;if(useGlobeDepthFramebuffer){inputFramebufferglobeFramebuffer;}constpostProcessthis.postProcessStages;constcolorTextureinputFramebuffer.getColorTexture(0);constidTextureidFramebuffer.getColorTexture(0);constdepthTexture(globeFramebuffer??sceneFramebuffer).getDepthStencilTexture();postProcess.execute(context,colorTexture,depthTexture,idTexture);postProcess.copy(context,originalFramebuffer);}// 4. 无后处理、但有 globe depth 时复制颜色回 originalFramebufferif(!usePostProcessuseGlobeDepthFramebuffer){passState.framebufferoriginalFramebuffer;globeDepth.executeCopyColor(context,passState);}};这样天气 Pass 就在后处理之前执行并且会把结果直接写进当前目标 framebuffer。关键注意点1. WebGL State 保存与恢复自定义 Pass 如果直接用原生 WebGL必须保存/恢复 Cesium 的 WebGL stateconstpreviousFramebufferpassState.framebuffer;constpreviousViewportpassState.viewport;// ...passState.framebufferpreviousFramebuffer;passState.viewportpreviousViewport;如果是通过DrawCommand执行如上例Cesium 会帮你管理大部分 state。2. HDR 空间如果scene.highDynamicRange truesceneTexture是浮点/半浮点纹理值可能超过 1.0。天气效果应该在线性/HDR 空间计算不要内部做 gamma 校正或 clamp 到[0, 1]让 Cesium 的 tonemapping 后处理去处理。3. Depth Texture如果需要场景深度例如体积云和地形正确相交可以从globeFramebuffer或sceneFramebuffer拿 depth textureconstdepthTexture(globeFramebuffer??sceneFramebuffer).getDepthStencilTexture();然后作为 uniform 传进 shader。4. 开关控制建议在Scene构造函数里加一个开关functionScene(options){// ...this._useWeatheroptions.useWeather??true;// ...}这样用户可以按需启用/禁用自定义 Pass。总结在 Cesium 1.144 中插入自定义 Pass 的标准流程是实现 Pass 类提供initialize/execute/destroy三个方法。在View.js中创建实例跟随场景生命周期初始化和销毁也可以直接放Scene.js但推荐View.js。在Scene.resolveFramebuffers中插入执行点选择合适时机通常是在后处理之前。注意 state、HDR 和 depth避免破坏 Cesium 的渲染管线。注意修改viewport可能需要你自己调用glViewport来更新。修改后记得重新构建npx gulp build然后启动开发服务器验证效果nodeserver.js