[杂项] AR -- 前后景处理
前言
本文着重梳理两年来博主在 AR 直播领域(渲染、推流、数据处理)的技术积累. 由下文的预备知识可知, 其中的每项都可以作为单独的专题展开讲解. 碍于全职工作, 近期不可能对所有的内容展开详尽讨论. 为避免遗忘这些宝贵的经验和方法, 才有了这篇文章.
预备知识
GPU 流水线
- 顶点数据: 模型通常由三角面 (或四边面) 组成. 在 GPU 渲染流程中,三角面片的顶点数据则是顶点着色器的输入,包括:位置, 法线, 切线, N 个纹理坐标以及颜色
- 坐标系: 空间位置都是相对的.
日常技术沟通时,我们常说:A 坐标系在 B 坐标系下的表示; 点在某个坐标系下的表示;- A 类: 左手系, 右手系
- B 类:模型坐标系, 相机坐标系, 世界坐标系
- 坐标系: 空间位置都是相对的.
- 集合阶段:
- 顶点着色器: 顶点/片元着色器的第一个函数
- 必要动作:完成顶点坐标从模型空间转换到齐次裁剪空间(
mul(UNITY_MVP, v.position)
) - 应用: 顶点动画, 低质量光照计算
- 必要动作:完成顶点坐标从模型空间转换到齐次裁剪空间(
- 曲面细分着色器: 可忽略哈, 99.9% 的技美不会接触到这里
- 集合着色器: 同上可忽略, 如果感兴趣 google 一下
- 裁剪: 至关重要, 跟投影矩阵直接相关(正交投影, 透视投影)
- 屏幕映射: 至关重要, 3D –> 2D
- 顶点着色器: 顶点/片元着色器的第一个函数
- 光栅化阶段
- 三角形设置: 固定函数阶段
- 三角形遍历: 固定函数阶段
- 片元着色器: 进行逐片元的着色操作. 顶点/片元着色器的第二个函数
- 逐片元操作: 主要完成颜色, 深度缓冲, 混合等操作. 非可编程阶段, 开发人员 不可直接介入
- 屏幕图像
Shader
- Shading Language: 列出几种常见的高级语言—可视为 中间语言 (Intermediate Language, IL). 这里的高级是相对于汇编语言来讲的, 并不是像 C# 相对于 C 的高级那种.
- HLSL: DirectX 专用
- GLSL: OpenGL 专用
- CG: NVIDA 专用, 也即
C for Graphic
. 在单个Pass
通道, C 风格的代码通常被包围在CGPROGRAM
和ENDCG
之间。
- Unity Shader: Unity 开发中, 有三种形式来编写 Unity Shader
- 表面着色器 (Surface Shader): 噢噢噢噢,Unity 的宠儿. 使用这种着色器, 开发人员无需过多关注光照的处理细节
- 顶点/片元着色器 (Vertex/Fragment Shader): 更加复杂, 灵活度更高. 博主全权负责开发的 AR 直播项目 100% 采用这种形式
- 固定函数着色器 (Fixed Function Shader): 已经被抛弃, 也可以稍稍了解下
3D 空间变换: 投影,坐标变换
- 坐标投影: 也即 3D —> 2D, 从世界坐标系 —> 投影平面的变换
- 坐标变换: 依赖相机外参进行世界坐标到相机坐标的变换; 依赖内参进行进一步到投影平面的操作
- 相机外参: 世界坐标系到相机坐标系的变换 (必须)
- 相机内参: 进一步变换到投影屏幕 (可选) (物理相机的畸变在大多数情况下可忽略, 但直播画面必须要进行处理)
- 3D 点投影: 即消除系数
Z
的作用 (透视投影下, 平行线会在无穷远处相交.Z
可理解为深度信息或距离)- 齐次坐标系:
pos_2d = world2Proj * pos_3d
- 笛卡尔坐标系:
pos_2d = pos_2d / pos_2d.z
- 齐次坐标系:
- 坐标变换: 依赖相机外参进行世界坐标到相机坐标的变换; 依赖内参进行进一步到投影平面的操作
3D 模型:点云处理, 模型拼接
AR 字面义为 增强现实. 说白了, AR 也就是在现实世界的基础上增加些 点缀. 点缀容易, 但要点缀地恰到好处:
- 视觉上: 让用户感到 恰到好处, 也即视野范围内的径向位置合适, 径向深度合适
- 听觉上:让用户感受到那种 由远及近的鸣笛声
本文重点讲解视觉上的效果处理 (后续有机会的话, 再对声音的多普勒效应模拟展开介绍). AR 增强现实要做到上述的效果, 离不开对现实世界的 建模 和 标定. 对于不同的 AR 应用, 建模的方式有多种. 拿 LPL 直播效果, 盲猜他们会选择如下几种方式 (或者完全不需要, LPL 不需要对整个直播场地毫无遗漏的高精度要求)
- 点云扫描:扫描精度不够, 尤其是边角处. 需要专业人员对从点云的导出模型进行修模 (如: 减面), 否侧很容易到达数百万顶点的模型文件
- 设计模型:过于理想, 现场搭建的场地往往是组装起来的. 而组装的精度跟施工人员专业程度, 场地大小强相关
- 设计模型拼接:相对比较节省人力, 节省时间的方式. 它的精度足够满足 AR 呈现的需求
- 设计模型分块:按照实际场地的组装形式,对整体的设计模型进行拆分.
- 场地标定:对搭建后的场地, 按需进行坐标点标定
- 模型拼接:需要专用的模型拼接软件进行拼接. 作为当年的预研项, 博主当时耗时三周完成了基本功能的开发
- 模型动态导入: 插件可用, 很好用
- 模型缩放问题:可选缩放系数
- 模型材质问题:需要遍历所有的
material
, 有些模型会存在 submesh 的情况
- 模型拼接并导出: Unity 官方插件可用, 依赖于 Editor 模式
- 模型缩放问题:100:1 的问题
- 模型格式问题:导入和导出的格式会涉及 fbx, obj, 因为我们是需要给模型贴图的. 某些情况下会生成光秃秃的模型哈哈哈哈
- 模型点拾取: 实际上拾取的是三角面, 然后计算出距离拾取点最近的模型顶点. 注: 会存在多个三角面共点的情况
- 刚体变换: 奇异值求解 T44 坐标变换矩阵; 然后对模型施加相应分量的旋转、平移、缩放. 不难哈哈哈哈
- 模型动态导入: 插件可用, 很好用
相机内外参
内外参完全描述了物理相机的所有信息, 包括:相对于世界坐标系的变换, 相机的物理参数
- 外参: 描述了相机的 位姿 信息, 包括:旋转参数, 平移参数. 旋转通常由 Euler 角描述, 可表示为 (qx, qy, qz, qw)
- 内参: 描述了相机的 畸变 和相机参数信息, 包括: 内参矩阵, 畸变系数
- 内参系数: fx, fy, cx, cy
- 畸变系数: 径向畸变, 切向畸变
- 径向畸变: 来自透镜形状不规则及建模方式, 导致镜头不同区域焦距不同. 光线远离透镜中心的地方偏折更大 (枕型畸变) 或更小 (桶形畸变). 通常由 k1, k2, k3 表示
- 切向畸变:来自整个相机的组装过程. 由于透镜制造上的缺陷, 使得透镜本身与图像平面不平行而产生的 通常由 p1, p2 表示
简介
AR 呈现 (或者是 AR 直播) 本质上是多张纹理 (或者图片) 的叠加 (Blend
指令, Blit
函数, lerp
插值函数). 这些纹理可分为: 底图 (UYVY422), UI 层 (BGRA), 特效层1, 特效层2, 特效层3 等
- 最上层 (UI 层): 常驻元素不随机位变化, 包括:2D UI, 2D 动画等
- 中间层 (特效层): 各种类型的特效, 这里的特效层划分不是随意定的哦. 前文所述的恰到好处的 点缀 侧重在深度上的体现, 而不同类型的特效在深度上会受到不同类型的 遮挡. 按遮挡需求, 很容易对不同类型的特效进行划分. 就拿 某某 赛事直播来说, 可进行如下划分
- UI 层: 是的, 就其呈现形式来讲, 有些 UI (依赖于相机视角, 所属物体的状态) 是要归类为特效
- 移动拖尾效果: 根据实时位置, 对粒子进行持续移动即可形成连续的轨迹. 实现方式有:ParticleSystem, TrailRender
- 粒子效果: 本质上是 2D 精灵, 借助于插件将其转换为具有 3D 效果的粒子特效
- 最底层 (物理相机输出的底图): 物理相机无法避免会存在各种前述畸变.
- 对上述特效层添加畸变效果, 以使特效跟底图完全吻合 (此时存在畸变)
- 对叠加后的畸变输出, 进行裁剪 (通常表现为四周存在黑边, 因此需要先裁剪后缩放至原始尺寸)
对于本文要介绍的前后景关, AR 呈现效果只是最后的表现. 而背后却需要硬件, 算法, 系统框架, 设计, 程序的支撑. 接下来将以 某某 直播为例, 简要概括前后景处理中涉及到的关键技术点 (注: 不涉及通用解决方案, 仅关注突破点)
- 场地(官方场景): 针对场地, AR 要做的事情就是要进行场地对齐: 现实世界与虚拟世界的完全对齐. 然后, 对齐 (标定) 并不是本文的重点. 现在我们更关注如何提高虚拟世界的 精度.
- 道具(官方设备): 道具没有场地的固定位置, 但相对于玩家来说, 道具会在很长一段时间内具有固定的位置 (参数). 这就是对道具的定义, 针对不同的应用场景完全可以换个名词. 现在我们就关注世界坐标系到屏幕图像的 映射 及相关处理.
- 玩家(赛队机器人):在 AR 直播领域, 玩家的深度处理相关比较复杂 (游戏的纯虚拟世界无法与其相提并论). 现在我们关注玩家与特效的前后景 互动
准备阶段
本节开门见山, 直接针对简介中提出的三点作出相应的描述
- 场地精度: 由 预备知识 中对于 3D 模型 重点描述可知, 模型拼接 可以相对提高对 AR 建模的精度
- 道具映射: 涉及到 预备知识 中的 3D 空间变换 和 相机内外参 【前面的长篇陈述, 没有一句话是多余的】
- 玩家互动: 涉及到玩家 Mask 不规则区域, 玩家的 3D 位置
处理方法
包括模型拼接,坐标映射,以及玩家互动。
模型拼接
有很多前置条件不在本文讲解范围,后续有机会再分享。
- 模型分割 :即对整体的设计模型, 按照施工人员的组装方式进行分割。 这部分工作由相应的设计人员来完成, 但需要我们提出几点技术要求:
- 坐标系: 统一为 AR 系统中采用的 左手系 或者 __右手系__。 预备知识 中对 GPU 流水线 的描述中有提及
- 坐标零点: 尽量保持处于模型的集合中心, 这样方便用户交互 (基于坐标零点)
- 模型缩放: 根据最终使用模型的引擎工具, 要对模型进行相应的缩放。比如: Unity 中导入的模型会存在 100:1 的关系
- 模型贴图:这点对于具有对称性的 AR 场地比较重要
- 模型减面: 比如, 可以在 Blender 中对模型添加
Decimate Modifier
, 对不可见细节进行减面处理
- 模型动态导入 :Unity 开发中, 可以借助付费插件 Trilib 来完成
- 模型点拾取 :涉及到 3渲2 或 OSR ,即:用户看到的永远是 2D 图片, 用户的交互会被投影到不可见的 3D 场景
- 模型定位 :即根据现实世界中测量的模型位置 (至少有 3 个不共面的模型顶点), 求解拾取点所需要的变换操作
- 刚体变换求解:
- 平移:消除偏移对拾取点的影响。如果模型本身的原点处于集合中心,则无需本步操作
- 旋转:通过奇异值分解的方式,计算 模型坐标系 下拾取点到 世界坐标系 下的标定点的旋转矩阵(3X3)
- 变换矩阵:通过前两步参数,得到 T44 变换矩阵
- 施加变换:对于 Unity 开发,也就1个接口的调用而已
- 刚体变换求解:
- 模型拼接并导出 :依赖于 Unity 官方插件 以及 Editor 模式。注意导出模型存在
SubMesh
的情况
坐标映射
官方设备外形固定且位姿参数相对稳定。针对这种情况,通常将它们的物理属性以配置参数的形式介入前端渲染流程:通过 3D 投影 可在二维成像平面的 裁剪 区域形成 BoundingBox ,再根据径向深度就可对 BoundingBox 内的像素点进行剔除 (Clip
)。 从而,产生视觉上的视觉差:特效被遮挡了
- BoundingBox 意义: 矩形框。由 3D 空间中的测量点 (标定点),根据当前相机的内外参进行成像平面的实时投影。 内外参包括相机的
Focus
,Zoom
,Eular Angle
, 而这几个参数跟相机动态变化的姿态和视野范围强相关。因此,需要进行实时投影。 - 变换步骤:世界坐标系 –> 相机坐标系 –> 2D投影
- 生成包围体:可选六棱柱,可根据需要测量点(没必要)
- 投影:世界坐标系到 2D 投影
- 数据准备:
- 外参矩阵(View2World): 相机外参,即旋转,平移。 Unity 接口为 Matrix4x4::SetTRS()
- 内参矩阵(View2Proj): 相机内参,即 fx, fy, cx, cy
- 实施变换:有两种方式供选:可实现算法接口,也可直接调用 Opencv 接口
- 自定义接口 :
- 求解 world2Proj 矩阵: world2Proj = view2Proj * world2View
- 进行 3D 点投影:
- 齐次坐标系:pos_2d = worldProj * pos_3d
- 笛卡尔坐标系:pos_2d = pos_2d / pos2d.z
- 计算 BoundingBox: 从六棱柱对应的 12 个投影后的 2D 坐标中,选出最小包围盒
- Opencv 接口 :
- 接口介绍:
- Calib3D.Rogdrigues: 依赖 外参矩阵 ,计算 旋转向量
- Calib3D.projectPoints: 依赖 旋转向量 ,平移向量 ,内参矩阵 ,__畸变系数__(可选),计算投影点
- Imgproc.boundingRect: 计算包围盒
- 步骤:
- 平移向量:取自 T44 矩阵的最后一列,即平移参数
- 旋转向量:通过 Rodrigues 计算出旋转向量
- 3D 投影:通过 projectPoints 直接投影
- BoundingBox:通过 Imgproc.boundingRect 计算出投影点的最小包围盒
- 接口介绍:
- 自定义接口 :
- 数据准备:
玩家互动:
指真实世界中的 玩家 跟虚拟世界中特效的相互干涉。这里的互动涉及两个层面:屏幕投影,前后关系
- 不规则 Mask :玩家不规则投影在相机可见范围的联通区。这些知识需要大量数据,来对模型训练已提供识别准确性。机器学习不在我的能力范围,故这里仅作简要介绍。
- 数据传输:可以将场地上所有玩家的联通区,以 alpha 通道的形式跟底图一起传递给引擎
- 应用:根据不同玩家的联通区在 1080p 图片的位置,可以实时生成 中间层特效(UI 层)用于描述当前玩家的状态,如:血量,Buff, 等级等
- 径向深度 : 借助于 Mask 联通区,可以深度信息实时判断当前像素点与对应玩家的前后关系。若玩家深度大小当前特效像素点的深度,则保留特效;若玩家深度小于当前像素点深度,则特效被剔除(Clip)。
附录
本文仅对 AR 直播中深度处理及相关技术作简要介绍,暂未涉及 Coding 相关的技术和性能要求。后续大致会对本文进行拆解,并增加以下专题:
- 直播推流:硬件及方案选型,包括:BMD硬件采集卡,NDI 设备,本地局域网内传输方案等。 这里提及的推流追求最小延时和最少丢帧,不涉及通常所说的编解码工作。
- AR 前端渲染:以往经历使用的是 Unity 引擎。 为向行业内的解决方案靠拢,后续会使用 Unreal 实现所有的技术点【手头没有 1 行代码】
- AR 后端系统:后续会使用 Qt 再现最小直播系统:包括视频采集,数据对齐,NDI输出等功能【依赖天价设备和算法支撑,只能模拟最小系统】