Cesium中通过射线计算日照

Cesium中通过射线计算日照

在这里插入图片描述

前段时间接触到一个需求,需要实时的计算建筑的日照,通常优先通过shadow map来实现。通过shadow map可以直接获取某一时刻的光照信息,累积不同太阳光位置的shadow map即可得到物体表面的光照时长。

不过本人技术有限,还没能力手撕shadow map,之前看了光线追踪的内容,考虑到也可以通过射线求交的方法来计算物体表面是否直达光源,来累积光照时长。

累积光照时长需要解决一下几个问题:

  • 对于复杂几何结构的物体不可能直接求交,算法过于复杂
  • 需要知道不同时刻的太阳位置
  • 不同的日照时长通过不同的颜色来表达

OBB

这里仅实现对立方体的遮挡计算,即通过计算物体的有向外包矩形来概化射线求交。

太阳位置

Cesium中内置了计算太阳位置的方法,通过Simon1994PlanetaryPositions可以计算得到某时刻的太阳在地球坐标系内的位置。对应代码中的SunHelper.js文件中的方法。

let transforMatrix=Transforms.computeTemeToPseudoFixedMatrix(date);
let sunpos=Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(date);
Matrix3.multiplyByVector(transforMatrix,sunpos,sunpos);

由于晚上没必要计算日照,所以多加一步,在物体的位置生成一个平面,当作地平线,在平面以下的当作晚上,在平面以上的当作白天。仅在平面以上的太阳位置需要参与计算

颜色表达

能累积到光照的次数后,即可以直接换算到不同的颜色,可以用LUT,也直接除一个整数限制到1以内即可

实现流程

起止日期计算太阳位置

这里计算得到的太阳位置需要换成光线方向,并不是需要真的太阳位置。太阳位置减去物体位置得到大致的光线方向。如下sunposs存储筛选后的太阳光线方向。

const startDate = JulianDate.fromDate(new Date("2023/07/02 18:00:00"));
viewer.clockViewModel.currentTime = startDate;
const hours = 24;
const sunposs = [];
for (let i = 0; i < hours; i++) {
  const date = new JulianDate();
  JulianDate.addHours(startDate, i, date);
  console.log(date);
  let sunpos = ComputeSunPos(date);
  const dir = new Cartesian3();
  Cartesian3.subtract(sunpos, suninitpos, dir);
  Cartesian3.normalize(dir, dir);
  const dist = Plane.getPointDistance(tgplane, sunpos);
  if (dist > 0) {
    sunposs.push(dir);
  }
}

生成立方体

我手上没有实际的楼的数据,就手动创建了几个立方体,来计算遮挡。

可以参看createBox方法。

立方体即当作OBB,传入FragmentShader中参与相交计算。OBB通过一个立方体中心,和立方体的三个半轴来构造。数据也仅传入立方体中心和三个半轴。

最开始担心精度抖动问题,立方体中心点采用了EncodedCartesian3进行了高低位拆分。中心和半轴依次存入boxcentersboxaxies中。

提交数据到GPU

数据还是通过uniform类型来提交,暂未考虑数据过大的问题。

不支持uniform数组

调试的时候发现,通过{type: , value: }提交数据后,无法正确传入到shader中。翻看createUniformArray.js中的代码发现UniformArrayFloatVec3.set方法,对于提交的数组并没有正确解析(也可能是没找到正确的用法),这里我直接改了对应的代码,在设置value的时候判断一下是不是数组。

const value = this.value instanceof Array?this.value:this.value.value;

数据提交还是正常的fabric,里面的rtc_lxs也是测试精度抖动问题,感觉实际并没有多少效果。

fabric: {
      uniforms: {
        lxs: { type: `vec3[${sunposs.length}]`, value: sunposs },
        boxcenters: { type: `vec3[${boxcenters.length}]`, value: boxcenters },
        boxaxies: { type: `vec3[${boxaxies.length}]`, value: boxaxies },
        rtc_lxs:new Cartesian3(-2764233.530084816, 4787599.944020384, 3170398.735383637)
      }
    }

立方体求交

OBB求交就不多说了,还是用的PlaneSet,即一对平面,立方体为三组平面,通过计算射线的进去,离开的时间来判断是否相交。

在FS中实现相交算法后,即可遍历太阳位置和立方体,判断每个太阳下,立方体直接的相互遮挡,记录相交次数。

在立方体相交时,剔除法线和光线朝向反向的顶点,保证背面不累计次数。

具体代码,看仓库里面吧。懒得说了。

现有问题

目前计算过于耗时,在距离立方体近的视角下,帧数只有个位数,后面有时间再优化一下。

在这里插入图片描述

边缘计算抖动: 在能射到光源和不能射到光源的交界处,计算总是抖动,边界及其不稳定,这个不知道啥问题,有大佬懂得可以指教一下。

代码没整理,后期优化。

仓库地址

YHLpuyu/cesium_shine: cesium实现日照分析,纯几何方法 (github.com)

更新

在计算相交时,可以先通过顶点法线和光线方向来剔除一部分顶点的计算,反向的顶点一定不会暴露在光源下。大概可以减少一半的像素计算量。

for(int i=0;i<suncount;i++){
    float lxs_front=dot(v_normal,lxs_0[i]);
    if(lxs_front<0.01) continue; //剔除反向的像素
    shinecount+=(1.-step(0.1,sunshine(i,pos,boxuniform_count)));
  }
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
计算射线与模型的交点,可以使用Cesium的Ray类和IntersectionTests类,具体步骤如下: 1. 创建射线,可以使用Cesium的Ray类,代码如下: ```javascript const ray = new Cesium.Ray(origin, direction); ``` 其,origin是射线的起点,direction是射线的方向向量。 2. 获取模型的包围盒,可以使用Cesium的BoundingSphere类,代码如下: ```javascript const modelMatrix = model.modelMatrix; const boundingSphere = model.boundingSphere.clone(); boundingSphere.transform(modelMatrix); ``` 其,model是Cesium的Model类,boundingSphere是模型的包围球,modelMatrix是模型的变换矩阵。 3. 计算射线与包围球的交点,如果没有交点则直接返回,代码如下: ```javascript const intersection = Cesium.IntersectionTests.raySphere(ray, boundingSphere); if (!intersection) { return null; } ``` 4. 计算射线与模型的交点,可以使用Cesium的IntersectionTests类,代码如下: ```javascript const result = new Cesium.Cartesian3(); Cesium.IntersectionTests.raySphere(ray, boundingSphere, result); const pickResult = viewer.scene.pickFromRay(ray, [model]); ``` 其,result是交点的输出参数,pickResult是拾取结果,包含了交点的位置和模型的信息。 5. 将交点的坐标转换到世界坐标系,代码如下: ```javascript const worldPosition = Cesium.Matrix4.multiplyByPoint(modelMatrix, result, new Cesium.Cartesian3()); ``` 这样就可以计算射线与模型的交点了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值