大部分知识都是相通的,Maya和USD在设计上有很多相似之处,USD的Schema粗看很难理解,但实际上与Maya的MFn有着异曲同工之处。这篇文章会简单介绍一下这两个知识点,做个对比,了解下它们在各自环境下的使用套路,长长姿势,帮助看官们融会贯通,闻一知十。
废话不多说,正文开始。
首先我们来看看USD中的Schema:
USD defines a schema as an object whose purpose is to author and retrieve structured data from some UsdObject. Most schemas found in the core are "prim schemas", which are further refined into IsA Schemas and API Schemas, for which the USD distribution provides tools for code generation to create your own schemas. Schemas are lightweight objects we create to wrap a UsdObject, as and when needed, to robustly interrogate and author scene description.
USD定义Schema的目的是编写、检索一些UsdObject的结构化数据。USD内核中大部分能找到的Schema大多数都是Prim类型的Schema,这些Schema以委托的方式封装了UsdObject,并提供了相应的函数来访问UsdObject。这些Schema可以进一步细分为IsASchema和API Schema,对此,USD的软件发行版也提供了生成代码的工具来创建自己的Schema。
Schema是轻量级的对象,我们创建它来封装UsdObject,并在我们需要的时候,能够稳定的询问和编写场景描述。
Schema的简易继承关系如图:
UsdGeomSphere就是一种Schema,提供了一套与内含数据相关的函数集,比如GetRadiusAttr()可以获取球体半径。由于继承Usdtyped,它是可以定义自身身份类型的。Schema不仅提供了操作内部数据的API,还提供了与Stage交互的功能,记录其自身在Stage中的层级和合成方式,以实现资产与资产之间的嵌套组合,实际上Schema就是层的概念,它包含了原始几何体数据,在场景层级中以层的方式组成有向循环图。
An API schema is a prim Schema that does not represent an object's type-identity, but simply serves as an interface or API for authoring and extracting a set of related data.
Choose to create an API Schema when you have a group of related properties, metadata, and possibly associated behaviors that may need to be applied to multiple different types of prims. For example, if your pipeline has a set of three attributes that get authored onto every gprim, and you want to have a robust schema for authoring and extracting those attributes, you could create an API schema for them.
Why not instead subclass the typed UsdGeomGprim schema and add the attributes there? Because you would then need to redefine all of the schema classes that derive from Gprim, thus preventing you from taking advantage of built-in DCC support for the UsdGeom GPrim-derived classes. API schemas provide for "mix in" data organization.
API schemas can be generated using the USD schema generation tools, but they can also be created manually.
API Schema是Prim类型的Schema,但无法表示对象的类型身份,只能用作编写和提取一组相关数据的接口或API。举个例子,UsdModelAPI就是一个API Schema,它有一套函数来操作模型,比如GetAssetName()可以获取模型的名称,GetAssetVersion()可以获取模型的版本名称。而这些函数是与被访问对象的具体类型无关的,无论它是sphere还是cube,只要它是模型,就支持UsdModelAPI的访问。
如果你有一组相关属性、元数据、可能相互联系的行为,需要添加到多个不同类型Prim对象上,请选择创建API Schema。比如,如果在你的流程中,每个UsdGeomGprim实例上的都创建了一套三个属性构成的组,你想有一个稳定的方式来编写或提取这组属性,你就可以为他们创建一个API Schema。
为什么不创建UsdGeomGprim的子类,把这套属性添加到子类里呢?因为如果这样,你就需要重新定义所有Gprim的子类的API Schema,看下图:
如果你只是创建了UsdGeomGprim的子类,那只有这个子类会得到相应的API支持,UsdGeomGprim下游的其他子类是不受支持的。
显然这会阻碍你从DCC对UsdGeom的原生支持中受益。
你可以使用USD schema generation tools来生成新的API Schema,也可以手动创建它们。
API Schema的简易继承关系如图:
UsdModelAPI就是一种API Schema,跳过Usdtyped,直接继承自UsdSchemaBase,这就意味着UsdModelAPI是无法表达自身类型的。
我们再来看看MFn:
Any class with this prefix is a function set used to operate on MObjects of a particular type.
Objects and function sets are always used together. They are separate which easily establishes ownership—objects are always owned by Maya, and function sets are always owned by you.
任意以MFn做前缀的类都是用来操作特定类型MObject的函数集。
MObject和函数集(MFn)总是一起使用的,它们的拥有权是不同的,MObject一直被Maya持有,函数集(MFn)则一直被用户持用。
对Schema、API Schema及MFn稍加对比不难发现,这三者的设计目的都是用来操纵软件内部数据对象。
下面再对Schema、API Schema及MFn的异同从这几个方面做一个总结:
-
实现方式:Schema、API Schema、MFn都是通过delegate(委托)的方式实现,通过将数据对象实例传递给这三个工厂类,来达到对象组合的作用,这种复用方式往往也称为黑箱复用(black-box reuse)。这种方式由于只要工厂类和数据类遵守接口约定,运行时刻数据类也能动态替换,因此,Schema、API Schema、MFn三个类在各自环境里对其它对象的依赖更少,具备更高的灵活性。这也是大型软件开发中的常见套路。
-
拥有权:Schema、API Schema对象的拥有权归属Stage所有,由Stage分配和释放,举个例子:
sphere = stage.DefinePrim('/hello/world', 'Sphere')
这是一个典型的委托形式,新创建的sphere对象由stage对象持有,而stage是可以被用户访问编辑的,所以Schema、API Schema、MFn三者的拥有权都归属用户。
-
作用对象:Schema、API Schema的作用对象是UsdPrim,UsdPrim是USD中的基本元素。内部结构如图:
显然UsdPrim是USD内部对象的一个handle,Schema、API Schema操作的实际上就是USD内部的数据对象。而MFn操作的MObject当然就是Maya中的数据对象啦,在作用对象上Schema、API Schema、MFn也是异曲同工。
- 调用方式:我们来看一个Schema的使用例子:
spherePrim = UsdGeom.Sphere.Define(stage, '/hello/world')
这行代码执行后会在stage的'/hello/world'路径上创建一个球体对象,该球体对象由spherePrim持有,相当于将sphere委托给spherePrim。
我们再来看一个MFn的使用例子:
MFnNurbsCurve curveFn; MObject curve = curveFn.create( ... ); MFnNurbsSurface surface( curve );
第一行定义一个名为curveFn的MFn实例,第二行通过curveFn的create方法创建一个名为curve的MObject,显然这是一个用MFn创建数据对象的做法。而第三行用构造函数的方式创建一个新的surface对象,同时把curve传递给surface,这就是一个标准的委托方式。
可见在调用方式上三者也非常近似。
- 自定义:自定义指的是开发新类,扩展功能。Schema的开发方式与MFn还是有很大不同的,USD提供了代码生成工具,你只需要按照如下例子编写一个schema.usda文件:
class "SimplePrim" ( doc = """An example of an untyped schema prim. Note that it does not specify a typeName""" inherits = </Typed> customData = { string className = "Simple" } ) { int intAttr = 0 ( doc = "An integer attribute with fallback value of 0." ) rel target ( doc = """A relationship called target that could point to another prim or a property""" ) }
然后再执行
usdGenSchema schema.usda
即可运行USD的代码生成工具usdGenSchema生成SimplePrim类的源码了,SimplePrim就是一个自定义的Schema类。
API Schema的自定义方式和Schema几乎相同,在这里就跳过了。
我们再来看看MFn的自定义方式。要写一个MFn就没那么容易了,Maya的底层非常灵活且复杂,还没有提供代码生成器,你也不能直接编写MFn类,必须先写一个MPxCommand,再通过MFnPlugin把这个命令注册到Maya中,开发者需要对Maya底层架构有足够的了解才能写MFn,这是非常繁琐专业的工作。具体例子可以参考:helloCmd
由此可见,虽然在开发灵活性上MFn要优于USD,要比开发效率和容易程度,还是USD的Schema胜出。
精力有限, Schema、API Schema及MFn的对比就到这里吧,希望这篇文章能帮助到对Maya开发、USD开发有兴趣的小伙伴们。