1. 需求背景
最近的项目是为某楼盘进行太阳光照模拟,目标是模拟从早上8点到下午5点的日照变化。为了实现这一功能,我们需要计算观测点的太阳高度角和方位角,并根据这些数据在场景中动态调整太阳的位置与光照效果。此外,光照的色彩变化也需要逐步过渡,以实现更逼真的效果。
2. 天文背景
太阳的高度角和方位角的变化是基于地球的公转。地球绕太阳的运动决定了不同季节的日照时长与强度:
- 春分与秋分:太阳直射赤道,北半球与南半球的昼夜时长相等。
- 夏至与冬至:太阳直射北回归线或南回归线,导致两半球的季节交替。
我们这次的观测点是长沙市,其纬度为28°11′49〃N,经度为112°58′42〃E。通过查询近五年长沙的太阳高度角与方位角的变化数据,我们得到了一个平均值。
太阳直射点的这种变化规律导致了地球各地不同季节的交替。例如,当太阳直射点位于北半球时,北半球将经历夏季,而南半球则经历冬季,反之亦然
3. 数据准备
知道了,一年中太阳直射点的运动规律。那就好办了,我们只需要知道要观测的目标点的维度就可以了。
这次我们选择的观测点是长沙
长沙经纬度=28 °11′49〃N,112 °58′42〃
以下数据来自国际天文学联合会,展示了长沙在春分、夏至、秋分和冬至,从早上8点到下午5点的太阳高度角和方位角变化:
// 长沙春分,夏至,秋分,冬至从早上8点到下午5点太阳方位角和太阳高度角变化,数据来源国际天文学联合会 www.timeanddate.com
const CHANGSHA = [
[
[22.86, 30.45, 38.11, 44.94, 49.03, 44.68, 37.68, 30.11, 22.62, 15.71],
[
119.62, 126.43, 143.12, 165.6, 180, 195.63, 218.37, 235.08, 246.64,
253.77,
],
],
[
[61.19, 68.25, 74.15, 77.59, 77.86, 74.77, 68.19, 60.09, 51.47, 43.13],
[
84.51, 98.94, 125.33, 156.77, 178.06, 198.51, 225.16, 239.45, 250.1,
256.54,
],
],
[
[22.89, 30.16, 37.51, 44.26, 48.31, 43.99, 36.85, 29.13, 21.49, 14.38],
[
120.38, 127.25, 144.28, 167.62, 180.0, 192.64, 214.02, 230.44, 241.84,
249.24,
],
],
[
[22.04, 29.08, 35.36, 40.43, 43.03, 40.76, 34.97, 28.38, 21.43, 15.06],
[120.5, 127.8, 144.57, 167.15, 180, 193.71, 216.14, 232.91, 244.67, 252.4],
],
];
export { CHANGSHA };
4. 实现太阳在 Three.js 场景中的动态模拟
使用 Three.js 的 Sky 类
我们可以使用 Three.js 提供的 Sky
类来生成天空背景。关键是通过调整 phi
和 theta
来控制太阳的位置,并将其转换为球坐标系下的三维坐标,更新天空中的太阳位置。
import * as THREE from "three";
import { Sky } from "three/examples/jsm/objects/Sky.js";
export default class Sun {
sky: Sky;
constructor(scene: THREE.Scene, renderer: THREE.WebGLRenderer) {
const sun = new THREE.Vector3();
this.sky = new Sky();
this.sky.scale.setScalar(40000);
scene.add(this.sky);
const effectController = {
turbidity: 20,
rayleigh: 3,
mieCoefficient: 0.005,
mieDirectionalG: 0.7,
elevation: 2,
azimuth: 180,
};
const uniforms = this.sky.material.uniforms;
uniforms["turbidity"].value = effectController.turbidity;
uniforms["rayleigh"].value = effectController.rayleigh;
uniforms["mieCoefficient"].value = effectController.mieCoefficient;
uniforms["mieDirectionalG"].value = effectController.mieDirectionalG;
const phi = THREE.MathUtils.degToRad(90 - effectController.elevation);
const theta = THREE.MathUtils.degToRad(effectController.azimuth);
sun.setFromSphericalCoords(1, phi, theta);
uniforms["sunPosition"].value.copy(sun);
renderer.toneMappingExposure = 1;
}
}
光照效果
为了模拟太阳光照,我们将使用直射光 (DirectionalLight) 和半球光 (HemisphereLight),并在其中加入光晕效果,使场景看起来更加逼真。
import { DirectionalLight, HemisphereLight, PointLight, Color, Vector3, TextureLoader } from "three";
import { Lensflare, LensflareElement } from "three/examples/jsm/objects/Lensflare.js";
// 直射光设置
function createDirLight(dirColor: string | number | Color) {
const dirLight = new DirectionalLight(dirColor);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = Math.pow(2, 13);
dirLight.shadow.mapSize.height = Math.pow(2, 13);
const d = 400;
dirLight.shadow.camera.left = -d;
dirLight.shadow.camera.right = d;
dirLight.shadow.camera.top = d;
dirLight.shadow.camera.bottom = -d;
dirLight.shadow.camera.far = 800;
dirLight.shadow.camera.near = 10;
return dirLight;
}
// 半球光设置
function hlight() {
const hemiLight = new HemisphereLight(0xb1e1ff, "#1e1e1e", 0.4);
hemiLight.color.setHSL(0.6, 1, 0.6);
hemiLight.groundColor.setHSL(0.095, 1, 0.75);
hemiLight.position.set(0, 50, 0);
return hemiLight;
}
// 光晕效果
function addLight(light: PointLight) {
const lensflare = new Lensflare();
const textureLoader = new TextureLoader();
const textureFlare0 = textureLoader.load("textures/lensflare/lensflare0.png");
lensflare.addElement(new LensflareElement(textureFlare0, 250, 0, light.color));
light.add(lensflare);
}
5. 动态时间控制与交互
用户可以通过滑动条动态调整太阳光照的时间,Three.js 提供了 MathUtils.lerp
方法来实现高度角和方位角的线性插值,保证在模拟过程中太阳的运动流畅逼真。
function updateSunPosition() {
const index = time.value - 8;
const curent = Math.floor(index);
const next = Math.ceil(index);
const phi = MathUtils.degToRad(90 - MathUtils.lerp(SphericalCoords[0][curent], SphericalCoords[0][next], index - curent));
const theta = MathUtils.degToRad(180 - MathUtils.lerp(SphericalCoords[1][curent], SphericalCoords[1][next], index - curent));
sunDir.setFromSphericalCoords(1, phi, theta);
renderer.render(scene, camera);
}
里面的场景初始化或者循环播放啥的不是核心就不过多的啰嗦啦,看最终效果
通过上述方法,我们可以在 Three.js 场景中实现基于时间与纬度的太阳光照模拟,为建筑楼盘提供更真实的日照分析效果。