☰
球面展开揭秘:游戏中的全景与反射真相
2026/10/15 5:53:07 网站建设 项目流程

先给一句话结论:

球面展开就是在“球面上的方向”和“一张平面图片上的坐标”之间建立换算关系。因为球面无法不变形地摊平,所以不同的展开方式各有代价,游戏会根据用途选择不同的方案。


一、通俗理解:地球仪和世界地图

地球仪(球面) 世界地图(平面) ___ ┌──────────────────────┐ / 北 \ │▓▓▓▓▓▓ 北极被拉成一整条 ▓▓▓│ │ 极 │ 展开 ─▶ │ │ │ │ │ 赤道附近基本正常 │ \_南__/ │ │ │▓▓▓▓▓▓ 南极被拉成一整条 ▓▓▓│ └──────────────────────┘

常见的世界地图上,格陵兰岛看起来几乎和非洲一样大,实际上非洲大约是它的 14 倍。原因是:

球面是弯曲的,平面是平的 就像橘子皮,怎么压都无法完全平整地铺在桌上,一定会被拉伸或撕开

数学上,这由高斯的“绝妙定理”保证:球面和平面的曲率不同,不存在既不拉伸、又不撕裂的展开方式。

所以所有球面展开方案都是在做取舍:

保角度? 保面积? 接缝少? 计算简单? 采样均匀? → 不可能全部满足,只能根据用途挑选

游戏中需要处理球面的地方很多:天空、环境反射、星球、全景视频、光照探针等,本质都是用一张或几张平面贴图,存储“各个方向”的信息。


二、核心原理:方向 ↔ 球面坐标 ↔ 贴图 UV

1. 用两个角度描述球面上的一个点

球面上任意一点,都可以用“经度 + 纬度”两个角度表示:

Y(上) │ ● 点 P │ /│ │ / │ θ:纬度(离赤道多高,-90°~+90°) │/ │ ─────────●───┼────── X / ╲ │ / ╲ │ Z φ:经度(绕 Y 轴转了多少,-180°~+180°)

在游戏中,球面上的一个点通常等价于从球心出发的一个单位方向向量d = (x, y, z),例如:

天空:从摄像机看向某个方向,能看到什么颜色 反射:反射光线朝某个方向射出,会照到什么

2. 方向 → UV(查贴图时用)

以最常见的**经纬度展开(Equirectangular)**为例,Y 轴朝上:

经度 φ = atan2(z, x) 范围 -π ~ π 纬度 θ = asin(y) 范围 -π/2 ~ π/2 u = φ / (2π) + 0.5 范围 0 ~ 1 v = θ / π + 0.5 范围 0 ~ 1

相当于:经度横向摊开成 u,纬度纵向摊开成 v,得到一张宽高比为 2:1 的图。

3. UV → 方向(生成球体模型、烘焙贴图时用)

φ = (u - 0.5) × 2π θ = (v - 0.5) × π x = cos θ · cos φ y = sin θ z = cos θ · sin φ

两个方向的换算都有了,就能在“球面”和“图片”之间来回转换。

4. Shader 示例:用全景图画天空

Texture2D _PanoramaTex; SamplerState sampler_PanoramaTex; float2 DirToEquirectUV(float3 d) { d = normalize(d); float u = atan2(d.z, d.x) / (2.0 * PI) + 0.5; float v = asin(d.y) / PI + 0.5; return float2(u, v); } float4 frag(float3 viewDir : TEXCOORD0) : SV_Target { float2 uv = DirToEquirectUV(viewDir); return _PanoramaTex.Sample(sampler_PanoramaTex, uv); }

每个像素只需要知道“摄像机朝哪看”,换算成 UV 后查表即可。


三、经纬度展开的三个典型问题

问题 1:两极严重拉伸

赤道一圈周长:2πR 北纬 60° 一圈周长:πR(只有一半) 北极:周长为 0,只是一个点 但在贴图里,每一行的像素数都一样多 → 越靠近两极,越多像素挤在越小的区域里 → 极点处,整整一行像素都对应同一个点

结果是:

- 两极浪费大量像素,赤道附近反而分辨率不足 - 在球体模型上,极点附近的贴图呈放射状扭曲(“菊花纹”)

每个像素覆盖的实际面积,大致和cos(纬度)成正比,越靠近两极越小。

问题 2:左右边缘的接缝

u = 0和u = 1在球面上是同一条经线,但在贴图上分别位于最左和最右:

贴图: │ u=0 …………………………………… u=1 │ └──────── 球面上其实是同一条线 ────────┘

如果贴图左右两边内容没有对齐,会出现一条明显的竖线。

问题 3:接缝处的 Mipmap 错误(Shader 中很常见)

即使贴图左右两边完全对齐,实时渲染时也常看到一条细线。原因是:

GPU 选择 Mipmap 层级时,会比较相邻像素之间 UV 的变化量 正常情况:相邻像素 u 相差 0.001 → 选清晰的层级 接缝处: 左边像素 u = 0.999, 右边像素 u = 0.001 → GPU 以为 u 变化了近 1 → 选最模糊的 Mipmap → 出现一条模糊或异色的细线

常见解决方法:

// 方法 1:同时计算两套 u,选变化量更小的那套来计算 Mipmap float u1 = frac(uv.x); float u2 = frac(uv.x + 0.5) - 0.5; float u = (fwidth(u1) < fwidth(u2)) ? u1 : u2; // 方法 2:不用自动 Mipmap,自己用连续的方向向量计算梯度,再调用 SampleGrad // 方法 3:天空只用 Mip 0 采样(SampleLevel(..., 0)),简单直接

四、常见的球面展开方式对比

方式形状优点缺点典型用途
经纬度(Equirectangular)一张 2:1 的图直观,易编辑,摄影设备直接输出两极拉伸,有接缝HDRI 全景、360 视频、天空素材
立方体贴图(Cube Map)6 张正方形图变形小,GPU 硬件原生支持,无极点6 个面有边界,占用较多天空盒、反射探针
球形贴图(Sphere Map)一张圆形图,像拍镜面球只要一张图依赖视角,背面严重压缩早期环境反射、MatCap
八面体映射(Octahedral)一张正方形图一张图覆盖全方向,分布较均匀,编码简单有折叠接缝光照探针、法线压缩、探针图集
双抛物面(Dual Paraboloid)前、后两张圆形图只需渲染两次两半交界处有误差早期动态反射、点光源阴影
立方体球(Cube Sphere)6 块网格网格分布均匀,无极点UV 分成 6 块星球地形

几种展开方式的形状示意:

经纬度 立方体贴图(十字展开) 八面体 ┌──────────┐ ┌──┐ ┌───────┐ │ │ ┌──┼──┼──┬──┐ │ ╲ ╱ │ │ 2 : 1 │ │ │ │ │ │ │ ╲ ╱ │ │ │ └──┼──┼──┴──┘ │ ╱ ╲ │ └──────────┘ └──┘ │ ╱ ╲ │ └───────┘

五、在游戏中的应用

应用 1:天空盒与 HDRI 全景

美术和摄影师一般提供经纬度全景图,因为相机、渲染软件可以直接输出,Photoshop 也能编辑:

拍摄或渲染 360° 全景 → 得到 2:1 经纬度图(HDR 格式) │ ├── 直接在 Shader 里按方向采样,作为天空(Unity 的 Panoramic Skybox) │ └── 导入引擎时转换成 Cube Map → 运行时采样更快,没有极点问题

经纬度图适合制作、传输和存储,Cube Map 适合运行时采样。

应用 2:环境反射

金属、水面、车漆等材质,需要知道“反射方向上能看到什么”:

反射方向 R = reflect(视线方向, 法线) 颜色 = 环境贴图.Sample(R)

技术演进大致是:

早期(固定管线时代):Sphere Map 一张“拍镜面球”的照片,只在某个视角下正确 之后:Cube Map 反射 视角无关,GPU 原生支持 现代:Reflection Probe(反射探针) 在场景中多处放置探针,各自烘焙一张 Cube Map 物体使用离自己最近的探针,并按粗糙度选择不同的 Mip 层级 (粗糙表面反射模糊,就用更模糊的 Mip)

应用 3:MatCap(材质捕获)

MatCap 是球形贴图在今天最常见的用法:用一张“画好的光照球”代替光照计算。

美术画一张圆形的球体光照图: ┌─────────┐ │ ◜高光◝ │ │ ( 球 ) │ 这张图已经包含了光照、高光、反射、材质质感 │ ◟暗部◞ │ └─────────┘

原理:把视角空间下的法线直接当作 UV:

float3 nView = normalize(mul((float3x3)UNITY_MATRIX_V, worldNormal)); float2 matcapUV = nView.xy * 0.5 + 0.5; float3 color = _MatCapTex.Sample(sampler_MatCapTex, matcapUV).rgb;
法线朝向屏幕正前方 → 采样图片中心 法线朝向左边 → 采样图片左侧 法线朝向上方 → 采样图片上侧

特点:

优点:只需一次采样,极其便宜,效果风格化、可控 适合手游角色、雕刻软件预览(ZBrush)、卡通渲染、UI 中的 3D 物件 缺点:光照跟着摄像机走,物体转动时高光位置不变,无法对应真实光源

应用 4:星球和球体模型贴图

给一个球体模型贴图,就是在做“球面展开”。常见的球体网格有两种:

UV 球(经纬线球) 立方体球(Cube Sphere) ___ ┌──────┐ /│││\ 极点处三角形挤成一团 │╲ ╱│ │─┼┼┼─│ UV 就是经纬度展开 │ ┌──┐ │ 把立方体每个顶点归一化 │─┼┼┼─│ → 贴图在两极扭曲 │ └──┘ │ 推到球面上 \│││/ │╱ ╲│ 网格均匀,没有极点 └──────┘

开放世界中的星球(如太空探索类游戏)通常采用立方体球 + 分块 LOD:

- 6 个面各自是一块四叉树地形,靠近时细分 - 每个面有独立的高度图、贴图,没有极点问题 - 也可以避开 UV,直接用三维坐标采样三维噪声生成地形,完全无接缝

对于岩石、地形等不想处理 UV 的情况,也常用三平面映射(Triplanar Mapping):从 X、Y、Z 三个方向投影贴图,按法线混合,和球面展开思路互补。

应用 5:360° 全景视频与 VR

全景视频通常以经纬度格式编码(视频编码器只认矩形画面) │ 播放时:在一个内表面朝向观众的大球上贴视频,或者在 Shader 中按视线方向采样 │ 优化:两极浪费了大量码率 → 一些平台改用立方体格式或等角立方体格式(EAC) → 同样的码率,画面中更多像素用在了观众真正关心的区域

应用 6:存储“各个方向”的数据(八面体映射)

现代引擎中有大量数据是“每个方向一个值”:

- 光照探针(Light Probe):每个方向的入射光是多少 - 全局光照探针(如 DDGI):每个方向的光照和距离 - G-Buffer 中的法线:把三维单位向量压缩成两个数

八面体映射特别适合这些用途:

1. 把方向投影到八面体 |x| + |y| + |z| = 1 上 2. 上半部分直接压平成菱形 3. 下半部分沿边折到四个角上 4. 得到一张正方形图,覆盖全部方向
float2 OctEncode(float3 n) { n /= (abs(n.x) + abs(n.y) + abs(n.z)); if (n.z < 0) n.xy = (1.0 - abs(n.yx)) * (n.xy >= 0 ? 1.0 : -1.0); return n.xy * 0.5 + 0.5; }

优势:

- 只需一张正方形贴图,方便把成百上千个探针打包到一张大图集里 - 分布比经纬度均匀,没有严重的极点问题 - 法线从 3 个分量压到 2 个分量,节省 G-Buffer 带宽

六、怎么选择

需求推荐方式
美术制作、交换全景素材经纬度
运行时天空盒、反射Cube Map
手游低成本风格化材质MatCap(球形贴图)
大量探针打包、法线压缩八面体映射
星球地形立方体球 + 分块 LOD,或三维噪声
不想处理 UV 的球形物体三平面映射
360 视频经纬度(兼容性好)或立方体格式(画质更好)

七、总结

1. 本质:建立“球面方向 ↔ 平面贴图坐标”的换算关系 方向向量 → 经纬度角 → UV,以及反向换算 2. 根本限制:球面无法无变形地展开 所有方案都在拉伸、接缝、均匀度、计算成本之间取舍 3. 经纬度展开最直观,但有极点拉伸、左右接缝、Mipmap 细线问题 4. 游戏中的主要用途: 天空与全景 → 经纬度素材,运行时多转为 Cube Map 环境反射 → Cube Map / 反射探针 风格化材质 → MatCap 星球 → 立方体球 方向数据 → 八面体映射

一句话:球面展开就是把“四面八方的信息”塞进平面贴图;没有完美的塞法,经纬度方便制作,Cube Map 方便实时采样,MatCap 便宜好看,八面体适合大量存储,游戏开发要做的就是按场景选择最合适的那一种。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询