资讯详情

资讯详情

OpenExec 计算值入门:基于 OpenUSD 的 ExecUsdSystem 数据流求值实战指南

OpenExec 计算值入门基于 OpenUSD 的 ExecUsdSystem 数据流求值实战指南【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD本教程是 OpenUSD 中 OpenExec 子系统pxr/exec的第一篇入门实战指南围绕官方示例 computingValues 展开演示如何通过ExecUsdSystem这一套面向性能敏感场景的低层 API从UsdStage中请求并计算UsdGeomXformableprim 的computeLocalToWorldTransform局部坐标到世界坐标的 4x4 变换矩阵。阅读完本文你将掌握「创建系统 → 构建请求 → 准备请求 → 执行求值 → 提取结果」的完整调用链并理解其背后的数据流网络编译、调度与缓存机制。一、教程背景OpenExec 如何计算值OpenExec 的目标是高效求值 USD 场景中编码的计算行为。其核心思想是Schema 发布计算computations——计算是计算行为的最小原子单元从其他计算的输出以及场景中 author 的值读取输入并产生输出值。OpenExec 将一个UsdStage连同已注册的计算定义消化为场景内已实例化计算的内部表示一张数据流网络客户端随后可以请求任意一组计算输出。除了产出请求的值之外OpenExec 还支持计算值缓存以及在场景变化时对缓存进行失效处理。计算必须挂接在某个场景对象上该对象称为计算提供者provider可以是UsdPrim或UsdAttribute它把计算锚定在 USD 场景中并为计算提供取值的参考点。与 Schema 关联的计算称为插件计算plugin computations——首次请求使用某 Schema 的 prim 时OpenExec 会按需加载定义该 Schema 计算的插件而 OpenExec 自身提供固定的一组内建计算builtin computations例如每个 attribute 都提供computeValue内建计算产出当前求值时刻的 attribute 值。这些概念在 OpenExec Overview 中有系统阐述。本教程选用的computeLocalToWorldTransform就是由UsdGeomXformableSchema 发布的插件计算对给定的 Xform其结果是将其局部坐标中的点变换到世界坐标的 4x4 矩阵该结果依赖两个值该 Xform 上transform属性的 author 值在父级 Xform 上递归调用computeLocalToWorldTransform的计算结果。注意本教程展示的 API 是 OpenExec 的最低层 API专为成像系统这类对性能要求极高的客户端设计。OpenExec 未来会在其上叠加更便捷的 API服务于不追求极致性能、或符合特定模式从而在保持最大性能的同时获得更便利用法的场景。教程对应的完整示例代码位于USD/extras/exec/examples/computingValues/即仓库中的 extras/exec/examples/computingValues/ 目录。二、准备场景创建包含 Xform prim 的 UsdStage第一步是创建一个UsdStage其场景中包含UsdGeomXformableprim。教程使用如下 usda 场景根节点Root平移 (1, 0, 0)两个子节点A1平移 (0, 2, 0)、A2平移 (0, 0, 3)。该文件正是示例测试环境中的 xformPrims.usda#usda 1.0 def Xform Root ( kind component ) { uniform token[] xformOpOrder [ xformOp:transform ] matrix4d xformOp:transform ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (1, 0, 0, 1) ) def Xform A1 { uniform token[] xformOpOrder [ xformOp:transform ] matrix4d xformOp:transform ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 2, 0, 1) ) } def Xform A2 { uniform token[] xformOpOrder [ xformOp:transform ] matrix4d xformOp:transform ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 3, 1) ) } }关于该场景的几点说明uniform token[] xformOpOrder声明 Xform 上变换操作xformOp的执行顺序这里只有一个xformOp:transform操作。matrix4d xformOp:transform矩阵按行写入每个括号是一行最后一行前三个分量是平移向量。因此Root的平移是 (1, 0, 0)A1是 (0, 2, 0)A2是 (0, 0, 3)。假设该 layer 存放在名为xformPrims.usda的文件中可以用一行代码将其打开到UsdStage上UsdStageRefPtr stage UsdStage::Open(xformPrims.usda);三、创建 ExecUsdSystem持有一张共享的数据流网络要从UsdStage计算值需要基于该 stage 创建一个ExecUsdSystem对象ExecUsdSystem execSystem(stage);从源码看ExecUsdSystem继承自基类ExecSystem其构造函数通过EsfUsdSceneAdapter::AdaptStage(stage)将 stage 适配为内部求值场景并注册一个_NoticeListener订阅 stage 上的场景变更通知见 system.cpp。ExecUsdSystem维护计算给定UsdStage上的值所需的全部内部状态尤其是持有编译好的数据流网络network用于求值。也就是说我们可以用同一个 system 对象对源自同一 stage 的不同值集合分别发起请求多次请求之间共享编译结果等状态从而把成本包括编译网络的成本摊薄到不同的值请求上实现摊销amortizing。另外ExecUsdSystem会延长构造时传入的UsdStage的生命周期实际使用中ExecUsdSystem通常与UsdStage相伴而生见 system.h。ExecUsdSystem还提供ChangeTime(UsdTimeCode time)方法来切换求值时间它会按新时间重新解析场景中依赖时间的输入判定哪些输入真正在旧/新时间之间发生了变化进而使依赖这些输入的计算值失效并通知相关请求见 system.h。四、构建 ExecUsdRequest用 ValueKey 声明求值目标需要计算的一组值通过构建一个ExecUsdRequest来指定。每个被请求的值由一个ExecUsdValueKey标识它包含两部分provider一个提供计算的UsdObjectUsdPrim或UsdAttributeTfToken所请求计算的名称。从 valueKey.h 可以看出ExecUsdValueKey内部是一个std::variant支持三种形态已过期的键ExecUsd_ExpiredValueKey、attribute 计算键ExecUsd_AttributeComputationValueKey、prim 计算键ExecUsd_PrimComputationValueKey并对应三类构造函数仅传UsdAttribute计算该 attribute 的computeValue内建计算UsdAttributeTfTokenattribute 计算UsdPrimTfTokenprim 计算本教程使用此形态。要构建包含一组 value key 的请求调用ExecUsdSystem::BuildRequeststd::vectorExecUsdValueKey valueKeys { {stage-GetPrimAtPath(SdfPath(/Root/A1)), ExecGeomXformableTokens-computeLocalToWorldTransform}, {stage-GetPrimAtPath(SdfPath(/Root/A2)), ExecGeomXformableTokens-computeLocalToWorldTransform}, }; const ExecUsdRequest request execSystem.BuildRequest(std::move(valueKeys));ExecGeomXformableTokens-computeLocalToWorldTransform是定义于 tokens.h 的公开 token即UsdGeomXformable上该计算的名称。BuildRequest还支持两个可选回调参数ExecRequestComputedValueInvalidationCallback计算值因 author 值变更或场景结构失效而失效时被调用与ExecRequestTimeChangeInvalidationCallback时间变化导致计算值失效时被调用失效键可能被批量合并到一次回调中见 system.h。注意在这两个回调内部客户端不得再调用求值相关接口包括Compute或值提取。一个 request 维护高效计算其所代表的那组特定值所需的状态尤其是持有一份调度表schedule通过在同一 request 上进行多轮计算来摊销创建调度表的成本。ExecUsdRequest是仅可移动move-only的批量值容器ExecUsdRequest的语义细节见 request.h一次同时编译、调度、求值多个值远比逐个值分别执行这三步高效。五、准备请求编译网络 创建调度调用ExecUsdSystem::PrepareRequest准备请求它做两件事确保 system 持有的网络已为该请求完成编译即保证计算请求值所需的全部数据流节点、以及节点间传输数据的连接都已存在于网络中且其结构与UsdStage当前的 author 状态保持最新确保请求的调度表是最新的必要时含首次重新调度。execSystem.PrepareRequest(request);源码实现印证了这两步PrepareRequest依次调用requestImpl.Compile()与requestImpl.Schedule()见 system.cpp。值得注意的是显式准备请求是可选的如果客户端不调用PrepareRequest而直接调用ComputeCompute内部也会隐式完成编译与调度Compute同样先执行Compile()和Schedule()见 system.cpp。但显式调用通常更可取因为客户端能精确控制准备发生的时机编译compilation和调度scheduling往往比求值本身昂贵得多因此合理的做法是先把它们提前完成再进行多轮计算。六、执行求值Compute 返回 CacheView要计算请求的那组值直接调用ExecUsdSystem::ComputeExecUsdCacheView cache execSystem.Compute(request);计算完成后结果即可通过返回的ExecUsdCacheView对象访问。ExecUsdCacheView是对Compute产出的计算值的只读视图见 cacheView.h并有一个重要生命周期约束缓存视图不能比产生它的ExecUsdSystem或ExecUsdRequest存活得更久。对于需要临时覆盖某些计算值的场景ExecUsdSystem还提供ComputeWithOverrides(request, valueOverrides)如果请求中的某个值依赖 override 列表中的计算值则用 override 提供的结果替代override 仅对这一次调用生效不影响后续调用且 override 值的类型必须与被覆盖的计算值类型一致否则会报 coding error见 system.h。提取计算值通过ExecUsdCacheView::Get提取计算值索引与构建请求时 value key 向量中的顺序一一对应VtValue value; value cache.Get(0); const GfMatrix4d a1LocalToWorld value.GetGfMatrix4d(); value cache.Get(1); const GfMatrix4d a2LocalToWorld value.GetGfMatrix4d();Get(int index)返回VtValue若传入的索引未求值会发出错误并返回空值见 cacheView.h。七、完整示例把一切串起来下面把全部步骤合并为一段完整可运行的示例代码即仓库中的 computingValues.cpp含全部头文件与断言#include pxr/base/gf/matrix4d.h #include pxr/base/tf/diagnosticLite.h #include pxr/base/vt/value.h #include pxr/exec/execGeom/tokens.h #include pxr/exec/execUsd/cacheView.h #include pxr/exec/execUsd/request.h #include pxr/exec/execUsd/system.h #include pxr/exec/execUsd/valueKey.h #include pxr/usd/sdf/path.h #include pxr/usd/usd/stage.h #include utility #include vector PXR_NAMESPACE_USING_DIRECTIVE; void Example() { // Open the layer that contains our scene on a UsdStage. const UsdStageRefPtr stage UsdStage::Open(xformPrims.usda); // Create an ExecUsdSystem, which we will use to evaluate computations on // the stage. ExecUsdSystem execSystem(stage); // Create a vector of value keys that indicate which computed values we are // requesting for evaluation. std::vectorExecUsdValueKey valueKeys { {stage-GetPrimAtPath(SdfPath(/Root/A1)), ExecGeomXformableTokens-computeLocalToWorldTransform}, {stage-GetPrimAtPath(SdfPath(/Root/A2)), ExecGeomXformableTokens-computeLocalToWorldTransform}, }; // Build the request. const ExecUsdRequest request execSystem.BuildRequest(std::move(valueKeys)); // Prepare the request, ensuring the data flow graph is compiled and the // schedule is created. execSystem.PrepareRequest(request); // Evaluate the data flow graph according to the schedule, to yield the // requested computed values. ExecUsdCacheView cache execSystem.Compute(request); // Extract the values. VtValue value; value cache.Get(0); const GfMatrix4d a1LocalToWorld value.GetGfMatrix4d(); value cache.Get(1); const GfMatrix4d a2LocalToWorld value.GetGfMatrix4d(); // The resulting matrices are the concatenation of the transforms // authored on A1 and Root and A2 and Root, respectively. Here, we // extract the translations from the resulting matrices, demonstrating // that we end up with the expected net translations necessary to // translate points in each local space into world space. TF_AXIOM(GfIsClose( a1LocalToWorld.ExtractTranslation(), GfVec3d(1, 2, 0), 1e-6)); TF_AXIOM(GfIsClose( a2LocalToWorld.ExtractTranslation(), GfVec3d(1, 0, 3), 1e-6)); }其中的结果验证逻辑非常直观局部到世界的矩阵是A1/A2上 author 的变换与其父级Root变换的级联。A1的净平移 A1平移 (0, 2, 0) 与Root平移 (1, 0, 0) 的合成 (1, 2, 0)A2的净平移 (0, 0, 3) 与 (1, 0, 0) 的合成 (1, 0, 3)。两个TF_AXIOM断言容差 1e-6恰好验证了这两个期望值。示例还自带main()入口直接调用Example()编译运行后以退出码 0 通过即代表断言全部成立。八、源码纵深computeLocalToWorldTransform 是如何定义的教程请求的computeLocalToWorldTransform计算定义在 xformable.cpp 中其注册代码完整展示了 OpenExec 计算定义语言CDL的形态static GfMatrix4d _ComputeLocalToWorldTransform(const VdfContext ctx) { const GfMatrix4d *const localToParent ctx.GetInputValuePtrGfMatrix4d( _tokens-xformOpTransform); const GfMatrix4d *const parentToWorld ctx.GetInputValuePtrGfMatrix4d( ExecGeomXformableTokens-computeLocalToWorldTransform); // The computed local-to-world matrix is the local matrix multiplied with // the parent local-to-world, if we have both. If we are missing either // value, then the matrix we have provides the local-to-world transform. And // if we have neither, we fall back to the identity matrix. if (parentToWorld) { return localToParent ? (*localToParent) * (*parentToWorld) : (*parentToWorld); } else { return localToParent ? (*localToParent) : GfMatrix4d(1.0); } } EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(UsdGeomXformable) { self.PrimComputation(ExecGeomXformableTokens-computeLocalToWorldTransform) .CallbackGfMatrix4d(_ComputeLocalToWorldTransform) .Inputs( AttributeValueGfMatrix4d( _tokens-xformOpTransform), NamespaceAncestorGfMatrix4d( ExecGeomXformableTokens-computeLocalToWorldTransform) ); }这里揭示了教程所述「结果依赖两个值」的底层实现AttributeValueGfMatrix4d(xformOp:transform)声明该计算的第一个输入是当前 prim 上xformOp:transform属性的 author 值NamespaceAncestorGfMatrix4d(computeLocalToWorldTransform)声明第二个输入来自命名空间祖先父级 Xform上的同名计算正是它构成了递归调用链——每个 Xform 的局部到世界矩阵 自身局部到父级的矩阵 × 父级局部到世界的矩阵直至到达没有该输入的根节点此时回退为单位矩阵。这也解释了为何教程的 usda 场景无需额外代码即可让A1、A2自动“感知”到父级Root的平移求值网络在编译阶段就依据NamespaceAncestor声明把父子链上的计算节点连接起来了。当需要为其他 schema 自定义类似计算时可参考系列教程第二篇 Tutorial 2: Defining Schema Computations。九、构建与测试示例的 CMake 集成示例在仓库中同时作为测试注册其 CMakeLists.txt 展示了编译本示例所需链接的库set(PXR_PACKAGE exec) pxr_build_test(computingValues LIBRARIES gf execGeom execUsd sdf tf usd vt CPPFILES computingValues.cpp ) pxr_register_test(testComputingValuesExample COMMAND ${CMAKE_INSTALL_PREFIX}/tests/computingValues TESTENV testenv/testComputingValuesExample EXPECTED_RETURN_CODE 0 )要点链接库覆盖了示例用到的全部模块gfGfMatrix4d/GfVec3d、vtVtValue、tfTF_AXIOM/token、sdfSdfPath、usdUsdStage/UsdPrim以及 OpenExec 侧的execGeomtoken 与计算注册与execUsdExecUsdSystem等核心 APIpxr_register_test将编译产物注册为testComputingValuesExample测试运行环境指向testenv/testComputingValuesExample其中的 xformPrims.usda 会被视为工作目录下的xformPrims.usda供UsdStage::Open打开并以退出码 0 作为通过标准。在你的构建环境中该测试会被自动执行以验证本教程的全部 API 调用链单独复现时只需把computingValues.cpp与xformPrims.usda置于同一目录按上述链接关系编译运行即可。更广泛的ExecUsdSystem用法多时间片求值、失效回调、override 等还可以在 testExecUsdAttributeComputations.cpp 等测试文件中看到更多真实调用模式。十、性能要点与最佳实践小结回顾整个流程可以归纳出几条对性能敏感客户端尤为重要的实践准则用同一个ExecUsdSystem服务同一 stage 的多次请求网络编译、节点连接等重成本在多个请求间共享实现摊销显式调用PrepareRequest以前置编译与调度成本编译和调度比求值昂贵将其与多轮Compute分离可以避免每轮求值都付出隐式准备的代价用同一个ExecUsdRequest做多轮求值调度表在请求内被复用多轮计算共享批量请求多个值ExecUsdRequest的设计初衷就是一次编译、调度、求值一批值而非逐值执行多时间求值时按时间分组在同一时间点先算完所有请求再切换到下一时间能让依赖时间的中间结果保持在缓存中被跨请求复用见 system.h 中对ChangeTime的说明遵守缓存视图与回调的生命周期约束ExecUsdCacheView不得比产生它的 system/request 存活更久BuildRequest的失效回调内禁止再次进入求值。这套「System → Request → Prepare → Compute → CacheView」的五步流程是后续深入 OpenExec 缓存失效、执行阶段架构等高级主题见 overview.md 的 Advanced Topics 预告的基础也是将 OpenExec 接入成像、仿真等高性能管线的起点。延伸阅读OpenExec Overview计算、provider、插件计算与内建计算的概念总览Tutorial 2: Defining Schema Computations如何为自定义 schema 注册自己的计算定义computingValues 示例本教程对应的完整可运行代码与测试环境execGeom/xformable.cppcomputeLocalToWorldTransform的计算定义与注册源码。【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →