太阳三维坐标用sin/cos计算:Y=radius×sin(πt),X/Z=radius×cos/sin(2πt)×cos(elevationAngle);光照强度用pow(h,1.5),色温用smoothstep加mix插值;防精度漂移需拆分dayCounter与timeInDay;着色器通过全局SunSystem单例同步更新。

太阳位置怎么用 sin 和 cos 算出三维坐标
昼夜交替本质是太阳绕场景做周期性运动,不是真模拟天体力学,而是用简化的球面轨迹。关键参数只有两个:当前时间(归一化到 [0, 1))、地平线高度角(控制日出日落位置)和轨道半径。
常见错误是直接用线性插值算高度,导致太阳在地平线附近移动过快、正午悬停感强。正确做法是把一天映射到半圆弧:用 sin(π * time) 控制 Y(高度),用 cos(2π * time) 和 sin(2π * time) 控制 X/Z 平面投影。
-
float t = fmod(currentGameTime / dayDuration, 1.0f);—— 先归一化时间 - Y 坐标 =
radius * sin(M_PI * t)—— 半周期正弦,保证日出/日落平滑过渡 - X 坐标 =
radius * cos(M_PI * 2.0f * t) * cos(elevationAngle); - Z 坐标 =
radius * sin(M_PI * 2.0f * t) * cos(elevationAngle);——elevationAngle是地轴倾角模拟,通常取0.2f~0.4f弧度
光照强度和色温怎么随太阳高度实时变化
人眼对光的感知不是线性的,简单用 Y 坐标做亮度权重会显得正午刺眼、黄昏过暗。更合理的是分段拟合:地平线下用环境光兜底,地平线上用带幂次衰减的插值。
典型实现中,sunIntensity 和 sunColor 都依赖同一个高度因子 h = max(0.0f, sunY),但处理方式不同:
- 强度:用
pow(h, 1.5f)比线性更接近真实大气透射衰减 - 色温:日出/日落时
sunColor = vec3(1.0f, 0.7f, 0.3f),正午趋近vec3(1.0f, 0.95f, 0.85f),用mix()插值但权重用smoothstep(0.05f, 0.3f, h)避免突变 - 阴影软硬:低角度时增大
shadowBias或降低级联阴影分辨率,否则地表噪点明显
怎么避免 fmod 精度漂移导致昼夜跳变
长时间运行后,currentGameTime 累积到 float 精度不足(约 16777216 秒后整数部分丢失),fmod(x, 1.0f) 结果可能突变,造成太阳瞬移或卡顿。
根本解法不是换 double(渲染管线通常不接受 double 向量),而是把时间拆解:
- 维护一个
uint32_t dayCounter记录完整天数 - 只用
float timeInDay(范围严格保持在[0, 1))参与三角计算 - 每过一天,重置
timeInDay = 0.0f,同时dayCounter++用于天气/事件系统 - 更新逻辑写成:
timeInDay += deltaTime / dayDuration; if (timeInDay >= 1.0f) { timeInDay -= 1.0f; dayCounter++; }
Unity/C++ 项目里怎么把太阳位置传给着色器
不要在每帧 C++ 侧反复计算并 SetVector——开销大且易错。推荐统一管理为一个全局 SunSystem 单例,内部缓存上一帧结果,仅当 timeInDay 变化超过阈值(如 1e-4f)才重新计算并更新 GPU 缓冲。
着色器端接收方式取决于渲染管线:
- URP:用
ShaderProperty注册_SunPosition和_SunColor,在RenderObjectsFeature中注入 - 自研管线:把太阳数据打包进常量缓冲区(CBUFFER),结构体对齐按
16-byte边界,字段顺序为float4 position; float4 color; - 切记禁用
UNITY_NO_DXT5nm等宏影响法线贴图时,_SunPosition的 Z 分量别被意外复用
最易被忽略的是天空盒同步——如果用了基于物理的天空(如 Preetham 模型),它的太阳方向必须和动态光源完全一致,否则出现“太阳在天上,阴影却朝反方向”的穿帮。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











