cesium FBO(四)自定义相机渲染到Canvas(离屏渲染)

前面几节的例子是将Cesium默认的相机渲染到纹理(RTT)或Canvas,这片文章讲解如何将自定义的一个camera的画面渲染到Canvas上,有了前面几篇的基础了,也能将自定义的画面渲染纹理、也可以灰度处理,原理是一样的。

一、效果

左上一是渲染到Canvas(灰度处理)的画面,右上一是渲染到Canvas正常画面,左下一是渲染到纹理的画面,右下一是自定义的camera视锥体。

二、实现

实现的原理还是基于第一篇的阐述的原理,主要分为以下步骤:

1.创建FBO

2.创建相机

3.将相机内容渲染到FBO

4.将FBO绘制到Canvas

通过前面几篇文章的讲解,其中1、4环节我们已经很熟悉了,这里说明一下2和3。

1)创建相机
复制代码
    createFboCamera(scene) {
      let camera = new Cesium.Camera(scene);
      camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(126.30158892175582, 35.02368795804565, 200000),
        orientation: {
          heading: Cesium.Math.toRadians(0.0), // 航向角,转换为弧度
          pitch: Cesium.Math.toRadians(-45),     // 俯仰角,转换为弧度  
          roll: Cesium.Math.toRadians(0.0),       // 翻滚角,转换为弧度
        }
      })

      //这里采用透视投影
      camera.frustum = new Cesium.PerspectiveFrustum({
        fov: Cesium.Math.toRadians(60),              // 视场角
        aspectRatio: 1, // 宽高比
        near: 1,            // 近裁剪面
        far: 500000               // 远裁剪面
      });

      let primitive = new Cesium.DebugCameraPrimitive({
        camera: camera,
        color: Cesium.Color.RED,
        show: true,
      });
      scene.primitives.add(primitive);
      return camera;
    }
2)将相机内容渲染到FBO

核心的思路是将场景默认的相机替换成自定义的相机,等渲染完成后再替换回来。主场景已经有一个事件循序在渲染了,我们再加一个势必会影响到性能。

复制代码
cameraRenderToFbo(fbo, scene, fboCamera) {
      // 保存原始相机,并将fbo相机设置为当前相机
      let camera = scene._defaultView.camera;
      scene._defaultView.camera = fboCamera;

      // 获取场景的帧状态、WebGL上下文和uniform状态
      const frameState = scene._frameState;
      const context = scene.context;
      const us = context.uniformState;

      // 设置默认视图
      const view = scene._defaultView;
      scene._view = view;

      // 更新场景帧状态
      scene.updateFrameState();

      // 设置3DTiles渲染通道状态
      const renderTilesetPassState = Cesium.Cesium3DTilePass.getPassOptions(Cesium.Cesium3DTilePass.RENDER);
      frameState.passes.render = true;
      frameState.passes.postProcess = scene.postProcessStages.hasSelected;
      frameState.tilesetPassState = renderTilesetPassState;

      // 设置背景颜色,如果不是HDR模式需要进行gamma校正
      let backgroundColor = Cesium.defaultValue(scene.backgroundColor, Cesium.Color.BLACK);
      let scratchBackgroundColor = new Cesium.Color();
      if (!scene._hdr) {
        backgroundColor = Cesium.Color.clone(backgroundColor, scratchBackgroundColor);
        backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma);
        backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma);
        backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma);
      }
      frameState.backgroundColor = backgroundColor;

      // 更新大气和雾效果
      frameState.atmosphere = scene.atmosphere;
      scene.fog.update(frameState);

      // 更新uniform状态
      us.update(frameState);

      // 处理阴影贴图
      const shadowMap = scene.shadowMap;
      if (Cesium.defined(shadowMap) && shadowMap.enabled) {
        if (!Cesium.defined(scene.light) || scene.light instanceof Cesium.SunLight) {
          // 将太阳方向取反,使其从太阳射向场景
          Cesium.Cartesian3.negate(us.sunDirectionWC, scene._shadowMapCamera.direction);
        } else {
          Cesium.Cartesian3.clone(scene.light.direction, scene._shadowMapCamera.direction);
        }
        frameState.shadowMaps.push(shadowMap);
      }

      // 清空命令列表
      scene._computeCommandList.length = 0;
      scene._overlayCommandList.length = 0;

      // 设置视口尺寸
      const viewport = view.viewport;
      viewport.x = 0;
      viewport.y = 0;
      viewport.width = context.drawingBufferWidth;
      viewport.height = context.drawingBufferHeight;

      // 设置通道状态
      const passState = view.passState;
      passState.framebuffer = fbo;
      passState.blendingEnabled = undefined;
      passState.scissorTest = undefined;
      passState.viewport = Cesium.BoundingRectangle.clone(viewport, passState.viewport);

      // 更新并执行渲染
      scene.updateEnvironment();
      scene.updateAndExecuteCommands(passState, backgroundColor);
      scene.resolveFramebuffers(passState);

      // 清理状态
      passState.framebuffer = undefined;
      context.endFrame();

      // 恢复原始相机和地球显示
      scene._defaultView.camera = camera;
      scene.globe.show = true;
    }

好啦,关于Cesium的FBO的话题暂时搞一段了,请各位老铁点个赞,评个论啥的,感谢。