今天分享的是Pixar在Siggraph2019上关于USD的分享,整个系列包含五篇文章,这是第一篇,PPT地址见参考文献[1]。
其他几篇文章的链接给出如下:
第二篇 【Siggraph2019】USD Composition
第三篇 【Siggraph2019】USD Authoring and Advanced Features


Pixar对外开源了众多的内容,其中最核心的三项是OpenSubdiv、Hydra以及USD,下面将对Hydra跟USD做重点介绍。

上面是Pixar影视管线中所需要用到的众多软件的简易流程图,其中Hydra标注的地方也都是OpenSubdiv会需要用到的地方,而图中的蓝色带箭头的线条表示的各个软件之间的数据传输,这一块是通过USD完成的。
那么什么是USD,USD是如何作用在数据传输中的呢?下面将会用一些简单的几何体来进行介绍。
#usda 1.0
def Cube “box” {
double size = 1.0
}
USD是Universal Scene Description的缩写,指的是一种用于表示场景中各类资源的统一格式描述方法(不只是一种文件格式,还包括了描述语言在内的众多内容,这个后面会说到),上面给出的一段代码就是USD中的一个usda文件的内容(USD中描述资源有三种后缀的文件,分别是.usd、.usdc以及.usda,其中.usda指的是人类可读的文件描述方式,.usdc是二进制格式,而.usd指的则是上述两种中的一种),描述了一个尺寸为1的cube,这个cube的名字叫“box”。
第一行指的是usda文件的版本号,第二行中的def表示的是我们需要定义一个prim(primitive的缩写,USD中的数据是按照层级方式来组织的,一层的基本数据单位就是prim,每个prim可以下面挂多个child prim),这里的def是USD中的一个描述符(specifier),除了def之外,还有其他的描述符比如over、class等。
“Cube”指的是当前定义的prim的类型(带类型的描述语言),这个类型是通过schema来定义描述的,后面会对schema做更多的介绍。
“box”则是我们定义的这个prim的名字,紧接着一个花括号,其中存放的是这个prim的properties,properties包含attributes跟relationships两类,前者指一些数据属性,后者指的则是prim之间的引用关系。上面这个例子中的size就是box的一个attribute。
有人会问了,那么cube的具体信息到底是什么样的呢,我们很清楚,一个cube就是一个立方体,那么其对应的vertex/index数据都是可以自定义的,不需要赘述,那么一个其他的非规则几何体呢,应该用什么样的方式进行描述呢?
#usda 1.0
def Mesh “box” {
float3f[] extent = [(-1.0, -1.0, -1.0), (1.0, 1.0, 1.0)]
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4,
4, 5, 7, 6, 6, 7, 1, 0,
1, 7, 5, 3, 6, 0, 2, 4]
point3f[] points = [(-1.0, -1.0, -1.0), (1.0, -1.0, -1.0),
(-1.0, -1.0, 1.0), (1.0, -1.0, 1.0),
(-1.0, 1.0, 1.0), (1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0), (1.0, 1.0, -1.0)]
color3f[] primvars:displayColor = [(0.5, 0.5, 0.5)] (
interpolation = “constant”
)
}
上面给出的例子则可以推广到任意的mesh类型,因为里面指定了顶点、颜色等更为通用的属性数据。
跟前面一样,这样一个描述文件其名称可以定义为box.usda,这样的一个文件在USD中的一个更为专业的称谓是一个layer,这种就像是photoshop中的一个layer,而这种叫法会贯穿整篇文章。
#usda 1.0
def “world”
{
def “box1” (references = @box.usda@) {
}
def “box2” (references = @box.usda@) {
}
}
前面定义的是一个box的描述文件,但是我们的一个场景中通常会有众多的物件,其中部分物件使用的是相同的mesh数据,这个时候如果将所有物件都按照之前的方式来进行组织,无疑会使得文件可读性变差,且存在大量的冗余。
为了解决这种问题,USD引入了composition arcs(如上面文件写法中小括号括起来的部分),上面使用composition arcs用于指定当前prim的一个reference数据,这里reference的是mesh描述文件。
这里描述的两个box由于没有指定translation数据,因此在空间上是完全重叠的。
#usda 1.0
def “world”
{
def “box1” (references = @box.usda@) {
uniform token[] xformOpOrder = ["xformOp:translate"]
float3d xformOp:translate = (-1, 0, 0)
}
def “box2” (references = @box.usda@) {
uniform token[] xformOpOrder = ["xformOp:translate"]
float3d xformOp:translate = (1, 0, 0)
}
}
为了能在空间上错开,这里增加了一些额外的attributes数据,即translate。
为了能在效果上进行区分,还可以在这里对每个box的color属性进行覆盖:
#usda 1.0
def “world”
{
def “box1” (references = @box.usda@) {
uniform token[] xformOpOrder = ["xformOp:translate"]
float3d xformOp:translate = (-1, 0, 0)
}
def “box2” (references = @box.usda@) {
uniform token[] xformOpOrder = ["xformOp:translate"]
float3d xformOp:translate = (1, 0, 0)
color3f[] primvars:displayColor = [(0.0, 0.0, 1.0)] {
interpolation = “constant”
}
}
}

当我们将所有的references、composition arcs以及覆盖的attributes都放到一起,最终组合起来的一个整体,我们称之为一个stage。
再回到我们最开始的问题,什么是USD?
- USD是用于描述场景组织关系并对场景中相关asset的数值进行解析的一个runtime engine
- 这个engine是用来在多个应用之间实现数据传输的,同时在部分应用中也被用作3D场景数据格式标准使用
- USD可以用于构建一个任意复杂度的场景,同时还支持多人对这个场景进行协同编辑
那么为什么一定要使用USD呢,就没有其他的更好的解决方案了吗?
- USD是Pixar经历多年迭代而成的一个结果,其中积累了众多的经验教训,已经是一个十分稳定可用的版本
- 在USD的基础上,衍生了众多的高质量工具,包括Presto中的composition engine、Pixar自研的动画、riggin以及simulation系统等,这些工具都已经经过了多年的迭代与验证,已经足以说明USD的完整与可用
- 当然我们后面会发现USD中包含了很多比较复杂的概念,但是这些概念不是为了显得高深而刻意设计的,而是确确实实因为项目需要才存在的
- USD最开始为了实现电影品质的内容制作而设计的,因此在复杂度上面是完全能够满足当下游戏与电影制作的一切需求的。
下面来介绍下USD的重要组成部分。
1. Prim
首先是Prims,前面已经解释过,我们def后面跟的就是prim,那么更为一般的定义是什么呢?
首先,每个prim都有一个类型,其次,prim都有一些相关的properties。
2. Properties
前面说过,properties包含attributes与relationships两大类,attributes可以理解为通常意义上的物件的一些数值属性;而relationships则是用于实现prim跟properties的关联的(这个比较抽象,看看后面是不是可以补上相关的案例介绍)。
3. Metadata
Metadata是作用于layer、prim、properties上面的一些额外的数据信息,如下面代码中的constant就指定了primvars属性的插值规则。
#usda 1.0
def Cube “box” {
double size = 1.0
color3f[] primvars:displayColor =
[(0.5, 0.5, 0.5)] (
interpolation = “constant”
)
}
4. Layers
Layer可以认为是按照层级组织的Prim & Properties的一个容器,每个Layer都是一个可以被USD Composition Engine引用的一个Object。
5. Composition Arcs
前面给出了Composition Arcs的示例,其通俗的定义是USD提供的一套用于方便实现场景组装的操作符,后面会给出更多的使用案例。
6. Stage
Stage是将从Root Layer开始的所有Layers组合起来的一个结果,通常一个Stage就对应于场景中用于遍历查询的一个物件。这个Stage给出了prims、prims在命名空间中的位置信息以及相关attributes的数值等的完整的数据信息。

说到命名空间,这里需要提一下,prims跟properties都是命名空间中的对象数据,而在USD中,他们是通过SdfPath(这是USD跟Hydra API中一个非常常用的组件)来追踪的,上图给了两个例子,第一个给出了xformOrder attribute的path,第二个给出了box2 prim的path。
Schema

前面对Schema的介绍不多,但是这是一个很重要的概念,总的来说,Schema指的是用于为prim赋予具体含义的一个properties集合。
- USD的core是不知道prim以及prim中的properties具体是代表什么的,只有在添加了Schema之后才能找到对应的具体含义。比如说,如果一个prim表示的是一个mesh,或者之前示例中的一个cube,就需要一个schema来规范这个prim应该要包含哪些properties。
- USD Schema有很多的案例可以参考,这个后面再介绍
- 有一些内容没有定义对应的schema,比如rigging,这是因为Schema最开始是为了数据interchange而设计的,而rigging数据要想interchange却十分困难(不过这里需要解释的是,USD没有设计对应的rigging编码Schema不是因为技术障碍)
下面一起来看下到底都有哪些Schema呢?
- Geometry -> UsdGeom
- Volumes -> UsdVol
- Shading -> UsdShade
- Lighting -> UsdLux
- Skeletal Animation -> UsdSkel
- Rendering* -> UsdRender*
这里是目前USD中支持的Schema(每行最前面的名字,箭头右侧则是对应的libraries,后面偶尔会使用libraries名字来指代Schema),并且时不时还会增添新的(Rendering就是目前正在增加的一个),同时对于已有的Schema,也会时不时的在功能上进行扩展(最近为Skeletal Animation Schema添加了Blend Shape功能)。
接下来我们会看到USD的生态系统是可扩展的,在USD的library stack中提供了众多添加插件的点。
1. File format plugins
一个文件格式插件指的是用于指定USD对于不同的文件格式的读写方法的插件,这里要提一下,USD还引入了一种叫做动态文件格式的机制,后面会有一些介绍。
2. Asset resolution plugins
USD还提供了一种允许用户对asset的路径进行截取并重新解读的插件方式。
这个插件的主要功能就是将制作管线中所编码的某个asset的引用转换成用户asset系统所能够识别与打开的路径,如下面这个示例所展示的一样:
@my-asset-id@ -> /usr/show1/myasset/v3/myasset.usd
3. custom schemas
前面介绍了USD原生支持的一些Schema,除此之外,USD还支持用户自定义Schema,比如你想要实现一个Hair Schema,这个内容在Matt关于Pipeline部分中会有详细介绍。
4. scene delegate
接下来讲一点Hydra的内容,Hydra的架构可以允许用户编写插件以实现原生场景的转换。
5. render delegate
这个插件可以允许用户实现自定义的渲染逻辑
6. prim adapters
这里还可以通过实现USD imaging插件来对之前实现的自定义Schema进行可视化处理。
总而言之,USD跟Hydra在灵活性与可扩展性上面是能够满足一切需求的。
接下来会介绍一下USD开源库的组织结构,后续如果有阅读源码需求,也可以快速理清脉络。

首先,这里提供了几条直接跟Pixar联系的方式。

当你clone好开源仓库后,你会看到如上图所示的四个顶层文件夹,这几个文件夹代表的含义给出如下:

- base
整个系统的最底层部分就放在base文件夹中,在这里可以找到用于处理基础框架能力的相关库,之前提到的插件系统,Vt(实现了基础的数值类型)等
- usd
接下来,usd文件夹中存储的是usd core部分,在这个文件夹中可以找到prims & properties实现的相关代码,此外composition engine以及大部分的core schema也在这里面。
如果要实现File Format Plugin或者自定义Schema,那么就应该存放到这个文件夹中。
- imaging
Imaging中存放的是Hydra Core部分,如果要实现Scene Delegate跟Render Delegate,就应该放在这里,同时,USD的渲染相关代码就放在这个文件夹中(比如Storm,也就是USD的快速交互视口类型的渲染器就放在这里)
- usdImaging
这个文件夹主要包含Hydra的USD Scene Delegate的相关实现,也是所有内容组合到一起的地方,在这个文件夹中可以找到usdview以及usd viewer的相关代码。

接下来让我们来点干货,比如一个displayColor primvar是如何在整个library stack中进行传递的,整个系统实际上是基于pull(拉取?)实现的,因此我们这里可以假象,imaging system尝试请求这个primvar。
- 假设primvar是存储在USD的二进制文件中,因此首先会需要通过usdc文件格式插件来对这个数值进行读取
- 之后将之打包成一个VtValue —— 需要注意,usdc执行效率很高,这里基本上是零拷贝的 ——数组传输在这里整个stack中都是copy-on-write方式的
- 之后,sdf是用于对layer、prim以及properties进行访问的library
- sdf中layer是通过composition engine中的pcp(这里是什么含义?)完成组合的,在这个地方最终需要解析的数值就已经处于可用状态了
- usd则是客户端查询流程结束时的entry-level的API,提供了UsdStge,prim以及properties等众多的API。
- usdGeom则是用于为properties赋予意义的schema library
- usdGeom会由Hydra的USD Scene Delegate,也就是usdImaging发起查询,而usdImaging则是由Hydra的hd进行查询
- hdSt表示的是render delegate,具体这里对应的是GL backend,
- 这就是一个红色像素数据查询所对应的全过程
- 整个流程看起来很复杂,我们通常只关心性能表现,包括GPU存储空间,总的来说,在整个过程中数据只有一份拷贝,而各个环节的职责与聚焦点都做到了很好的解耦。
网友评论