Toolkit Pipeline 教程

Toolkit Pipeline 教程

此文档提供了分步教程,指导技术总监 (TD) 使用 Toolkit 以高级方式设置简单的端到端工作流。完成本教程的新 Toolkit 用户将了解平台的基本方面,以及如何根据其工作室的特定需求对平台进行自定义。该教程还介绍了现有 Toolkit 应用,以及如何在美工人员工作流中将它们配合使用。
请注意,本文档介绍仅当控制 Toolkit 配置时可用的功能。有关详细信息,请参见 Shotgun 集成用户手册

目录:

概述

            工作流

先决条件

      软件

      Shotgun 站点

      配置的 Shotgun 项目

      Shotgun Desktop

      其他文档:

Toolkit 配置

      了解 Toolkit 配置

      配置内容创作软件

            清除不需要的内容:

            软件路径

      文件系统配置

自定义工作流

      了解挂钩

      自定义发布概述

      发布摄影机

      发布 Alembic 缓存

      发布着色器

      发布渲染图像

      配置加载器

美工人员工作流

      建模

      装备

      贴图

      构图

      动画

      照明

      合成

审核

上游更新

总结

概述

下面的工作流是一个长达 10 周的系列博客《Two Guys and a Toolkit》中创建的一个工作流。阅读该系列博客,您将了解到本教程中未介绍的其他详细信息和见解。

工作流

本教程介绍如何为动画或视觉效果制作打造一个简化但却典型的工作流。按照本教程,您将打造一个全面的工作流,为素材的建模、视觉开发到融入制作场景提供所有必要环节。下面概述了该工作流的工作方式:



工作流概述

为了简单起见,这里使用的数字内容创作 (DCC) 软件将尽可能最少,并且仅限于 Maya 和 Nuke。还是为了简单,工作流各个步骤之间传递的数据仅限 Maya ASCII 文件、Alembic 缓存和渲染的图像序列。

先决条件

软件

本教程假定您有使用 Shotgun 跟踪和管理制作数据的经验,并对 MayaNuke 有基本了解。此外,您还应具有 Python 脚本编写经验,因为教程需要通过 Python 编写的挂钩来修改 Toolkit 默认应用的行为。

教程中将会提到的其他技术包括 GitHubAlembic

Shotgun 站点

如果您没有 Shotgun 站点,可以在此处注册免费试用版。您会收到一封电子邮件,里面包含站点准备就绪后如何登录站点的说明。站点开始正常运行后,您可以通读《Shotgun 快速入门》文档。

配置 Shotgun 项目

有了站点之后,您需要在 Shotgun 中创建一个新项目,并像准备开始制作那样配置该项目。这包括确保所有必要的 Shotgun 实体都已就位并正确关联。在本教程中,素材序列镜头任务实体是必需的,默认情况下新项目中应提供这些实体。您将创建以下对象:

  • 两个素材:“茶壶”角色和“桌子”道具
  • 一个序列
  • 一个链接至您创建的序列的镜头
  • 每个工作流步骤一个任务

这些任务将链接至镜头或素材,具体取决于与它们关联的工作流步骤。

下面是一些屏幕截图,显示了您在 Shotgun 中配置的项目实体:



茶壶和桌子素材



一个镜头(注意序列链接)



配置的任务

Shotgun Desktop

您可以通过以下链接找到包含 Shotgun Desktop 下载和安装说明的教程:


Shotgun Desktop 提供一个界面,它将引导您完成为项目设置 Toolkit 的整个过程。链接的教程将帮助您确定默认的 Toolkit 配置模板中有哪些适合您的需求,以及如何设置本地文件系统存储。

使用链接的文档在计算机上安装 Shotgun Desktop,然后使用新配置的 Shotgun 项目,按照首次运行 Shotgun Desktop 部分概述的步骤操作。


选择配置模板时,务必使用默认配置。




另外,请记下您在 Project Set Up Wizard 中选择的配置位置。在本教程中,您将使用该路径探索项目配置。




如果这是您第一次设置 Toolkit 项目,系统还会提示您为制作数据定义一个存储位置。




完成 Shotgun Desktop 安装教程后,再继续本教程。

其他文档:

如果您是刚刚接触 Shotgun 或 Toolkit,并想了解一些另外的背景信息,下面这些链接可能会对您有所帮助:

Toolkit 配置

了解 Toolkit 配置

构建 Toolkit 工作流的重要一步是了解 Toolkit 工作流配置的组织及工作方式。Toolkit 配置定义了要用在您的工作流中的应用程序集成。这包括确定要使用哪些数字内容创作软件 (DCC),以及美工人员将在这些 DCC 内使用哪些 Toolkit 应用、插件和框架。

您在学习 Shotgun Desktop 安装教程期间指定的配置位置记录在 Shotgun 中,位于项目的 Pipeline Configurations 页面。




在计算机上浏览到该路径,您将看到一个如下所示的目录结构:




构建工作流时要关注的三个主要目录是 coreenvhooks 目录。这些目录中的文件驱动着工作流的工作方式以及它呈现给用户的方式。以下小节将深入介绍如何修改这些目录中的文件来配置项目。

要了解其他信息,您始终可以参考以下文档了解项目配置的更多信息:

配置内容创作软件

因为您在 Shotgun Desktop 中设置项目时使用了默认配置,所以您的项目将配置为使用 Toolkit 集成的所有 DCC。本教程中概述的工作流不需要所有 DCC。此外,我们需要修改要使用的 DCC 的默认路径,使之与您文件系统的路径匹配。下面这些小节将逐步引导您为项目配置内容创作软件。

清除不需要的内容:

首先要从 Shotgun Desktop 中移除您不会使用的 DCC 应用程序。下面的示例显示了 Shotgun Desktop 中显示的默认 DCC:




要从 Shotgun Desktop 中清除 DCC 启动器,需要编辑 tk-desktop 配置文件中的 config/env/project.yml插件设置。

Shotgun Desktop 是一个在项目层面的上下文中运行的 Toolkit 插件。环境文件中的插件和应用配置采取以下形式:

engine:
<engine-name>:
    apps:
    <app-name>:
        <app-setting1>:
        <app-setting2>:
        ...
    <engine-setting1>:
    <engine-setting2>
    ...

默认的 Shotgun Desktop 插件配置如下所示:



注意:@ 用于从随附的文件中导入一批配置。

您可以看到一个启动器应用列表,每个 DCC 各有一个启动器,如 tk-multi-launchnuketk-multi-launchmaya。要从 tk-desktop 插件中删除某个 DCC 启动器,只需从配置文件中删除对应的行即可。




对于本教程而言,除 Maya、Nuke、Photoshop 和 Screening Room 以外的所有启动器行都将被移除。当您返回 Shotgun Desktop 中的项目时,应该可以看到如下所示的界面。




除了从 Shotgun Desktop 应用程序启动 DCC 以外,每个 config/env/shotgun_*.yml 文件还定义了浏览器中为给定上下文提供的启动器。例如,shotgun_asset.yml 中定义的启动器将在 Shotgun 中的“Assets”页面的“Project Actions”菜单上可用。




project.yml 中的 Desktop 插件配置一样,您可以从 shotgun_*.yml 文件中移除 DCC,仅提供美工人员在制作活动中需要的启动器。

软件路径

要确保将要在工作流中使用的 DCC 得到正确引用,您需要更新默认配置中指定的路径。DCC 的路径存储在 config/env/includes/paths.yml 文件中。在该文件中,Maya 的路径配置如下所示:




在此文件中,找到 Maya、Nuke 和 Photoshop 的部分,更新路径以体现这些软件包在您的文件系统中的位置。

文件系统配置

本教程中概述的简单工作流使用默认 Toolkit 配置提供的目录结构。有关配置项目目录结构的其他信息,请参见“文件系统配置参考”文档。


本教程还使用默认 Toolkit 配置中定义的模板。有关模板系统的详细信息,请参见“配置文件系统模板”文档。

自定义工作流

为了构建我们概述的简单工作流,您需要在美工人员(理论上)可以开始工作前,对 Toolkit 的核心应用配置和挂钩进行一些自定义。

了解挂钩

在深入了解工作流自定义之前,您应该对 Toolkit 挂钩的概念、工作方式和位置有一个基本的了解。如果您不熟悉 Toolkit 挂钩,应先阅读下面的链接文档,然后再继续本教程。


下面各节将介绍有关修改或自定义应用挂钩的信息。对于其中每项自定义,您将按以下步骤操作:

  1. 在配置的 目录下找到要自定义的应用install,然后找到 hooks 子目录。下图的示例说明了已安装的应用的结构以及如何找到要自定义的挂钩。
  2. 复制挂钩(必要时重命名)到您的配置的顶层 hooks 目录。
  3. 在配置中为要自定义的应用更新适当的挂钩设置,使其引用新改写的挂钩。本教程中的大多数自定义在 shot_step.yml 文件中进行。但发布着色器部分例外,它需要修改 asset_step.yml 配置。
  4. 更改代码,对您的挂钩副本进行自定义。



找到 发布器应用中的 scan_scene_tk-maya 挂钩,然后在配置的 hooks 目录中创建一个副本。

自定义发布概述

本教程的以下各节将概述创建一个自定义发布工作流所涉及的步骤。每节将要介绍的基本步骤包括:

向适当插件的配置中添加一个新的自定义发布类型
向发布器应用的 scan_scene 挂钩中添加逻辑以确定场景中要发布的项
在发布器的 secondary_pre_publish 挂钩中添加针对待发布项的验证
向发布器的 secondary_publish 挂钩中添加导出和注册逻辑

上面的彩色项目符号将显示在下面各小节,帮助您识别每个步骤。

发布摄影机

我们要添加一项可为工作流提供很大灵活性的简单自定义,那就是发布独立摄影机的功能。通过此功能,您可以生成一次摄影机(通常在构图中),然后让工作流的所有其他步骤(如动画、照明和合成)直接使用它。

在继续操作前,请通读发布器应用的文档,概括了解该应用背后的概念和如何配置它。


要让摄影机发布可以正常工作,您需要向适当环境的发布器配置中添加一个新的辅助发布类型。在项目的 shot_step.yml 环境中,向 tk-maya 插件配置中发布器secondary_outputs 字段添加以下条目:

        - description: 'Publish a camera'
          display_group: Cameras
          display_name: Camera
          icon: 'icons/camera.png'
          name: camera
          publish_template: maya_shot_camera
          required: false
          scene_item_type: camera
          selected: false
          publish_group: false
          group_name: default
          tank_type: 'Camera'

有关 YAML 的小提示:在 YAML 语法中,空列表以 [] 表示,但是非空列表并没有方括号,每个列表项另起一行,并以短横线开头。在本例中,secondary_outputs 是一个词典列表,而上面的片段只是一个列表项。就缩进而言,列表项开头的短横线应与列表名称齐平。因此,如果上面是 secondary_outputs 中唯一的列表项,看起来将如下所示:

        secondary_outputs:
        - description: 'Publish a camera'
          display_group: Cameras
          display_name: Camera
          icon: 'icons/camera.png'
          name: camera
          publish_template: maya_shot_camera
          required: false
          scene_item_type: camera
          selected: false
          publish_group: false
          group_name: default
          tank_type: 'Camera'

这将告诉发布器应用,我们另外有一个 camera 类型的辅助发布需要发布器识别。

记下新的辅助输出设置中指定的 maya_shot_camera 模板。此模板指示发布挂钩应将摄影机写入磁盘的哪个位置。您需要确保 config/core/templates.yml 文件中有对应的模板。示例如下:

    maya_shot_camera:
        definition: '@shot_root/publish/maya/camera/{name}.v{version}.ma'
        root_name: 'primary'


接下来,需要告诉发布器如何识别文件中类型为 camera 的项。这通过修改发布器scan_scene 挂钩来实现。下面是需要向挂钩的 execute 方法中添加的代码片段:

        # look for cameras to publish
        for camera in cmds.listCameras(perspective=True):
            items.append({"type": "camera", "name": camera})

此代码会向会话中每个透视摄影机的辅助发布项列表中添加一个词典。在实际的制作环境中,代码可能会更加复杂,并且只会添加与某个命名约定相符或标记为可渲染的摄影机。


添加自定义发布类型要做的下一步是修改发布器辅助发布前挂钩。通过此挂钩,可验证和核实确定的辅助发布项。

您需要在 execute 方法中为摄影机类型另外添加一个条件。在该条件中,您将为提供的摄影机项调用一个验证方法。

elif output["name"] == "camera":
    errors.extend(self.__validate_item_for_camera(item))

下面是一个示例片段,显示了您添加的验证方法的样子:

    def __validate_item_for_camera(self, item):
        """
        Validate that the item is valid to be exported to a camera

        :param item:    The item to validate
        :returns:       A list of any errors found during validation

        """

        # add error checking here!
        errors = []
        return errors


最后,您需要实际处理摄影机项的发布。这将在发布器辅助发布挂钩的 execute 方法中处理。与发布前挂钩一样,您将为摄影机类型添加一个条件,以调用导出、注册和发布摄影机的方法。需要添加的条件应如下所示:

         elif output["name"] == "camera":
             try:
                 self.__publish_camera(item, output, work_template,
                                       primary_publish_path, sg_task, comment,
                                       thumbnail_path, progress_cb)
             except Exception, e:
                 errors.append("Publish failed - %s" % e)

另外,还需要添加实际的发布逻辑。下面是应向挂钩中添加的用来处理摄影机发布的方法。

单击此处展开发布摄影机的方法...

    import re    # add this to the top of your file.

    def __publish_camera(self, item, output, work_template,
        primary_publish_path, sg_task, comment, thumbnail_path, progress_cb):
        """
        Publish a shot camera and register with Shotgun.

        :param item:           The item to publish
        :param output:         The output definition to publish with
        :param work_template:  The work template for the current scene
        :param primary_publish_path: The path to the primary published file
        :param sg_task:        The Shotgun task we are publishing for
        :param comment:        The publish comment/description
        :param thumbnail_path: The path to the publish thumbnail
        :param progress_cb:    A callback that can be used to report progress
        """

        # determine the publish info to use
        #
        progress_cb(10, "Determining publish details")

        # get the current scene path and extract fields from it
        # using the work template:
        scene_path = os.path.abspath(cmds.file(query=True, sn=True))
        fields = work_template.get_fields(scene_path)
        publish_version = fields["version"]
        tank_type = output["tank_type"]
        cam_name = item['name']
        fields['obj_name'] = cam_name
        fields['name'] = re.sub(r'[\W_]+', '', cam_name)

        # create the publish path by applying the fields
        # with the publish template:
        publish_template = output["publish_template"]
        publish_path = publish_template.apply_fields(fields)

        # ensure the publish folder exists:
        publish_folder = os.path.dirname(publish_path)
        self.parent.ensure_folder_exists(publish_folder)

        # determine the publish name
        publish_name = fields.get("obj_name")
        if not publish_name:
            publish_name = os.path.basename(publish_path)

        # Find additional info from the scene:
        #
        progress_cb(10, "Analysing scene")

        cmds.select(cam_name, replace=True)

        # write a .ma file to the publish path with the camera definitions
        progress_cb(25, "Exporting the camera.")
        cmds.file(publish_path, type='mayaAscii', exportSelected=True,
            options="v=0", prompt=False, force=True)

        # register the publish:
        progress_cb(75, "Registering the publish")
        args = {
            "tk": self.parent.tank,
            "context": self.parent.context,
            "comment": comment,
            "path": publish_path,
            "name": publish_name,
            "version_number": publish_version,
            "thumbnail_path": thumbnail_path,
            "task": sg_task,
            "dependency_paths": [primary_publish_path],
            "published_file_type":tank_type
        }
        tank.util.register_publish(**args)

此工作流中导出和发布的摄影机文件是 Maya 文件,只包含摄影机本身。此时便可通过加载器应用在下游引用它们。

发布 Alembic 缓存

默认配置附带的 Alembic 缓存发布支持功能针对的是素材发布。 此功能非常适合建模师向使用 Mari 这类应用程序的纹理绘图师传递 Alembic 缓存,但不适合在镜头上下文中从 Maya 导出缓存的几何体供镜头工作流下游的其他步骤使用。对于这类工作流,我们已准备了非常详实的教程。按照下面链接的教程,可以设置如何在镜头内发布 Alembic 缓存。

发布着色器

构建工作流时,着色器的管理可能会是一项耗时并且复杂的任务。就此示例工作流而言,我们将对发布器应用进行自定义,在贴图这一步将 Maya 着色器网络导出为辅助发布。此外,我们将设计一个快速但不完善的解决方案,让着色器在被下游引用时可重新连接至 Alembic 几何体缓存。

需要承认的是,这是一个非常简单而又不稳固的系统。更保险的解决方案可能需要将已贴图角色的其他表现形式以及使用外部图像作为纹理所带来的素材管理任务考虑在内。希望这个示例能为您构建实际的解决方案带来灵感。


此项自定义与摄影机发布一样,第一步是向环境配置中另外添加一个辅助发布类型。由于着色器是处理素材时在上游创建的,因此请将以下内容添加到 tk-maya section 中的 asset_step.yml 文件。

        - description: Publish shader network for all geometry in the scene
          display_group: Shader Network
          display_name: Maya Shader Network
          icon: 'icons/maya_shader_network_publish.png'
          name: maya_shader_network
          publish_template: maya_asset_shader_network
          required: false
          scene_item_type: shader_network
          selected: false
          publish_group: false
          group_name: default
          tank_type: 'Maya Shader Network'

与 Alembic 导出一样,各种挂钩会使用配置的这个部分,针对定义的类型(在本例中为 shader_network 类型)触发要运行的附加代码。此处的其他字段定义了附加的导出类型如何显示在发布器应用中、它是否是必需的类型、写入磁盘时使用什么模板等。

将以下模板定义添加到您项目的 config/core/templates.yml 配置文件。

    # The location where maya shader networks are stored on disk
    maya_asset_shader_network:
        definition: '@asset_root/publish/maya/surfacing/{name}.v{version}.ma'
        root_name: 'primary'


现在,我们已指示发布器查找这个新类型,接下来需要修改 scan_scene 挂钩,以确定会话中的着色器网络。将以下代码添加到 scan_scene 挂钩:

        # export shader networks for any root level meshes.
        for grp in cmds.ls(assemblies=True):
            if cmds.ls(grp, dag=True, type="mesh"):
                items.append({"type":"shader_network", "name":grp})

上面的代码会针对所有根一级的网格向 items 列表中附加一个词典。发布挂钩将断开这些项,以便为发布器用户界面中选定的网格导出着色器。


如前面的小节中所见,接下来 secondary_pre_publish 挂钩将运行,用来验证选定的项。在该挂钩的 execute 方法中,向现有的条件中添加以下内容,调用特定于着色器网络的验证代码。

    elif output["name"] == "maya_shader_network":
        errors.extend(self.__validate_item_for_maya_shader_network_publish(item))

下面是验证方法的一个示例片段:

    def __validate_item_for_maya_shader_network_publish(self, item):

        """
        Validate that the item is valid to be exported to a maya shader network

        :param item: The item to validate
        :returns: A list of any errors found during validation that should be reported
                  to the artist
        """

        # add error checking here!
        errors = []
        return errors


当用户在用户界面中单击 Publish 按钮时,将执行实际的发布例程。我们从前面的自定义中了解到,该例程位于 secondary_publish 挂钩中。接下来,向现有条件中插入一个调用,以发布着色器网络。

            elif output["name"] == "maya_shader_network":
                try:
                    self.__publish_maya_shader_network(item, output,
                        work_template, primary_publish_path, sg_task, comment,
                        thumbnail_path, progress_cb)
                except Exception, e:
                    errors.append("Publish failed - %s" % e)

然后,添加发布着色器网络的方法。

单击此处展开发布着色器的方法...

    def __publish_maya_shader_network(self, item, output, work_template,
        primary_publish_path, sg_task, comment, thumbnail_path, progress_cb):
        """
        Publish shader networks for the asset and register with Shotgun.

        :param item:                    The item to publish
        :param output:                  The output definition to publish with
        :param work_template:           The work template for the current scene
        :param primary_publish_path:    The path to the primary published file
        :param sg_task:                 The Shotgun task we are publishing for
        :param comment:                 The publish comment/description
        :param thumbnail_path:          The path to the publish thumbnail
        :param progress_cb:             A callback that can be used to report progress

        """

        # determine the publish info to use
        #
        progress_cb(10, "Determining publish details")

        # get the current scene path and extract fields from it
        # using the work template:
        scene_path = os.path.abspath(cmds.file(query=True, sn=True))
        fields = work_template.get_fields(scene_path)
        publish_version = fields["version"]
        tank_type = output["tank_type"]
        obj_name = item['name']
        fields['obj_name'] = obj_name
        fields['name'] = re.sub(r'[\W_]+', '', obj_name)

        # create the publish path by applying the fields
        # with the publish template:
        publish_template = output["publish_template"]
        publish_path = publish_template.apply_fields(fields)

        # ensure the publish folder exists:
        publish_folder = os.path.dirname(publish_path)
        self.parent.ensure_folder_exists(publish_folder)

        # determine the publish name:
        publish_name = fields.get("obj_name")
        if not publish_name:
            publish_name = os.path.basename(publish_path)

        # Find additional info from the scene:
        #
        progress_cb(10, "Analysing scene")

        # clean up any hookup nodes that existed before
        _clean_shader_hookup_script_nodes()

        # there's probably a better way to do this. i am jon snow (i know
        # nothing)
        shading_groups = set()
        shad_group_to_obj = {}
        if cmds.ls(obj_name, dag=True, type="mesh"):
            faces = cmds.polyListComponentConversion(obj_name, toFace=True)
            for shading_group in cmds.listSets(type=1, object=faces[0]):
                shading_groups.add(shading_group)
                shad_group_to_obj[shading_group] = obj_name

        shaders = set()
        script_nodes = []
        for shading_group in list(shading_groups):
            connections = cmds.listConnections(
                shading_group, source=True, destination=False)
            for shader in cmds.ls(connections, materials=True):
                shaders.add(shader)
                obj_name = shad_group_to_obj[shading_group]

                # can't seem to store arbitrary data in maya in any
                # reasonable way. would love to know a better way to
                # do this. for now, just create a script node that
                # we can easily find and deduce an object name and
                # shader name. Yes, this is hacky.
                script_node = cmds.scriptNode(
                    name="SHADER_HOOKUP_" + obj_name,
                    scriptType=0, # execute on demand.
                    beforeScript=shader,
                )

                script_nodes.append(script_node)

        if not shaders:
            progress_cb(100, "No shader networks to export.")
            return

        select_nodes = list(shaders)
        select_nodes.extend(script_nodes)

        cmds.select(select_nodes, replace=True)

        # write a .ma file to the publish path with the shader network definitions
        progress_cb(25, "Exporting the shader network.")
        cmds.file(
            publish_path,
            type='mayaAscii',
            exportSelected=True,
            options="v=0",
            prompt=False,
            force=True
        )

        # clean up shader hookup nodes. they should exist in the publish file
        # only.
        _clean_shader_hookup_script_nodes()

        # register the publish:
        progress_cb(75, "Registering the publish")
        args = {
            "tk": self.parent.tank,
            "context": self.parent.context,
            "comment": comment,
            "path": publish_path,
            "name": publish_name,
            "version_number": publish_version,
            "thumbnail_path": thumbnail_path,
            "task": sg_task,
            "dependency_paths": [primary_publish_path],
            "published_file_type":tank_type
        }

        tank.util.register_publish(**args)

    def _clean_shader_hookup_script_nodes():

        # clean up any existing shader hookup nodes
        hookup_prefix = "SHADER_HOOKUP_"
        shader_hookups = {}
        for node in cmds.ls(type="script"):
            if node.startswith(hookup_prefix):
                cmds.delete(node)

有了导出逻辑和发布逻辑后,应该就可以开始创建着色器网络发布了。为了在下游使用它们,您需要对加载器应用稍做修改。


此示例中发布的着色器只是 Maya 文件,因此不必更改现有逻辑就能被加载器应用引用。唯一要做的更改是添加一个新逻辑,在着色器被引用到文件中之后将它们重新连接至适当的网格。

您需要向加载器应用中的自定义 action_hook 添加以下逻辑。

def _hookup_shaders(reference_node):

    hookup_prefix = "SHADER_HOOKUP_"
    shader_hookups = {}
    for node in cmds.ls(type="script"):
        if not node.startswith(hookup_prefix):
            continue
        obj_pattern = node.replace(hookup_prefix, "") + "\d*"
        obj_pattern = "^" + obj_pattern + "$"
        shader = cmds.scriptNode(node, query=True, beforeScript=True)
        shader_hookups[obj_pattern] = shader

    for node in (cmds.referenceQuery(reference_node, nodes=True) or []):
        for (obj_pattern, shader) in shader_hookups.iteritems():
            if re.match(obj_pattern, node, re.IGNORECASE):
                # assign the shader to the object
                cmds.file(unloadReference=reference_node, force=True)
                cmds.setAttr(reference_node + ".locked", False)
                cmds.file(loadReference=reference_node)
                cmds.select(node, replace=True)
                cmds.hyperShade(assign=shader)
                cmds.file(unloadReference=reference_node)
                cmds.setAttr(reference_node + ".locked", True)
                cmds.file(loadReference=reference_node)
            else:
                print "NODE: " + node + " doesn't match " + obj_pattern

不要忘记在 _create_reference() 方法结尾处添加对 _hookup_shaders() 的调用。

这些自定义的完整实现位于 tk-framework-simple 库中定义的挂钩以及 tk-config-simple 库中的 asset_step 环境中。

发布渲染图像

对此简单工作流的另一项自定义是使其能够将渲染图像从 Maya 发布到 Nuke,以进行从照明到合成的任务发包。为了让发包过程正常进行,您首先要创建一个模板来表示渲染图像在磁盘上的路径。将以下模板添加到项目的 config/core/templates.yml 配置文件。

maya_shot_render:
  definition: '@shot_root/work/images/{maya.camera_name}/{maya.layer_name}/v{version}/{name}.{SEQ}.exr'
  root_name: 'primary'

您会发现这里使用了 maya.layer_namemaya.camera_name 模板字符串。将这些字符串添加到同一文件的键部分充当占位符,自定义的发布挂钩会在搜索磁盘上的渲染图像时填充这些占位符。

    # reserve a template key for the maya camera name and render layer name
    maya.camera_name:
        type: str

    maya.layer_name:
        type: str


有了模板之后,与 Alembic 缓存和着色器网络一样,您需要在适当环境的发布器配置中为渲染图像添加一个新的辅助输出类型。将以下内容添加到 shot_step.yml

        - description: 'Publish a rendered image sequence'
          display_group: Renders
          display_name: Rendered
          icon: 'icons/publish_nuke_writenode.png'
          name: rendered_image
          publish_template: maya_shot_render
          required: false
          scene_item_type: rendered_image
          selected: false
          publish_group: false
          group_name: default
          tank_type: 'Rendered Image'

“渲染图像”类型的辅助输出定义用于帮助控制渲染的帧在发布器中的显示方式。这里的 publish_template 字段稍微有些误导,因为等到发布器可见时,帧已经渲染完毕。这与其他 Maya 辅助发布不同,后者使用模板来控制发布挂钩执行时将这些附加文件写入何处。


与前面一样,要让这个自定义可以使用,第一个要修改的挂钩还是发布器scan_scene 挂钩。但是与其他自定义在当前 Maya 会话中搜索内容项的做法不同,此代码是在磁盘上查找渲染帧。将以下代码添加到 scan_scene 挂钩:

        # we'll use the engine to get the templates
        engine = tank.platform.current_engine()

        # get the current app
        app = self.parent

        # look up the template for the work file in the configuration
        # will get the proper template based on context (Asset, Shot, etc)
        work_template = app.get_template("template_work")
        work_template_fields = work_template.get_fields(scene_name)
        version = work_template_fields["version"]

        # get all the secondary output render templates and match them against
        # what is on disk
        secondary_outputs = app.get_setting("secondary_outputs")
        render_outputs = [out for out in secondary_outputs if out["tank_type"] == "Rendered Image"]
        for render_output in render_outputs:

            render_template = app.get_template_by_name(render_output["publish_template"])

            # now look for rendered images. note that the cameras returned from 
            # listCameras will include full DAG path. You may need to account 
            # for this in your, more robust solution, if you want the camera name
            # to be part of the publish path. For my simple test, the cameras 
            # are not parented, so there is no hierarchy.

            # iterate over all cameras and layers
            for camera in cmds.listCameras():
                for layer in cmds.ls(type="renderLayer"):

                    # apparently maya has 2 names for the default layer. I'm
                    # guessing it actually renders out as 'masterLayer'.
                    layer = layer.replace("defaultRenderLayer", "masterLayer")

                    # these are the fields to populate into the template to match
                    # against
                    fields = {
                        'maya.camera_name': camera,
                        'maya.layer_name': layer,
                        'name': layer,
                        'version': version,
                    }

                    # match existing paths against the render template
                    paths = engine.tank.abstract_paths_from_template(
                        render_template, fields)

                    # if there's a match, add an item to the render 
                    if paths:
                        items.append({
                            "type": "rendered_image", 
                            "name": layer,

                            # since we already know the path, pass it along for
                            # publish hook to use
                            "other_params": {
                                # just adding first path here. may want to do some
                                # additional validation if there are multiple.
                                'path': paths[0],
                            }
                        })

这些更改的目的是为了找出磁盘上是否有任何与 maya_shot_render 模板匹配的渲染帧。背后的逻辑是对当前 Maya 会话中的摄影机和渲染层进行轮询,然后使用这些名称评估渲染模板。如果磁盘上有与评估的路径匹配的帧,则表示有内容要发布。该代码还会记录找到的渲染图像,让发布挂钩不必做同样的工作。


现在,渲染图像将被添加到待发布项的列表中,您需要修改 secondary_pre_publish 挂钩,以确保渲染帧有效且正常。看过了前面的自定义,下面的代码应该很眼熟。

        elif output["name"] == "rendered_image":
            errors.extend(self.__validate_item_for_rendered_image_publish(item))

下面是验证方法的一个示例片段:

    # validate rendered images...
    def __validate_item_for_rendered_image_publish(self, item):

        """
        Validate that the item is valid to be exported as a rendered image

        :param item:    The item to validate
        :returns:       A list of any errors found during validation
        """

        # add error checking here. here you can validate the rendered images in
        # whatever way you need to. right number of frames, no missing frames,
        # able to generate a thumbnail, all expected layers present, whatever
        # else you need.
        errors = []
        return errors

在实际制作中,可以添加下面这些可能的验证步骤:

  • 所有帧都存在
  • 文件大小一致
  • 权限正确
  • 辅助文件存在(例如缩略图或 QuickTime 影片)


最后要做的更改是修改 secondary_publish 挂钩,以处理新的类型。在该挂钩中,添加条件语句,以调用发布渲染图像的方法。

            elif output["name"] == "rendered_image":
                try:
                   self.__publish_rendered_images(item, output,
                       work_template, primary_publish_path, sg_task, comment,
                       thumbnail_path, progress_cb)
                except Exception, e:
                   errors.append("Publish failed - %s" % e)

然后,添加用来执行注册和发布渲染图像路径的方法。

单击此处展开发布渲染图像的方法...

    def __publish_rendered_images(self, item, output, work_template,
        primary_publish_path, sg_task, comment, thumbnail_path, progress_cb):
        """
        Publish rendered images and register with Shotgun.

        :param item:                    The item to publish
        :param output:                  The output definition to publish with
        :param work_template:           The work template for the current scene
        :param primary_publish_path:    The path to the primary published file
        :param sg_task:                 The Shotgun task we are publishing for
        :param comment:                 The publish comment/description
        :param thumbnail_path:          The path to the publish thumbnail
        :param progress_cb:             A callback that can be used to report progress
        """

        # determine the publish info to use
        #
        progress_cb(10, "Determining publish details")

        # get the current scene path and extract fields from it
        # using the work template:
        scene_path = os.path.abspath(cmds.file(query=True, sn=True))
        fields = work_template.get_fields(scene_path)
        publish_version = fields["version"]
        tank_type = output["tank_type"]

        # this is pretty straight forward since the publish file(s) have
        # already been created (rendered). We're really just populating the
        # arguments to send to the sg publish file registration below.
        publish_name = item["name"]

        # we already determined the path in the scan_scene code. so just
        # pull it from that dictionary.
        other_params = item["other_params"]
        publish_path = other_params["path"]

        # register the publish:
        progress_cb(75, "Registering the publish")
        args = {
            "tk": self.parent.tank,
            "context": self.parent.context,
            "comment": comment,
            "path": publish_path,
            "name": publish_name,
            "version_number": publish_version,
            "thumbnail_path": thumbnail_path,
            "task": sg_task,
            "dependency_paths": [primary_publish_path],
            "published_file_type": tank_type
        }

        tank.util.register_publish(**args)

在这段代码中,您可以看到发布路径来自 other_params 词典。前面提到,渲染图像路径由 scan_scene hook 确定,因此不用再搜索它们。在实际的制作工作流中,您还可以在这里做一些额外的处理,比如设置文件权限、创建符号链接等。

完成这一切后,发布的渲染图像应向 Shotgun 注册,并且无需再做更改便可供 Nuke 中的加载器使用。



渲染图像的辅助发布



Nuke 加载器中的渲染图像

配置加载器

现在,您已添加了几个新的辅助发布类型,接下来需要确保应用可以识别它们,以便在工作流的每一步创建新的上游引用。引用发布的文件让场景细分应用可以在上游美工人员迭代创作和发布工作时快速扫描新的版本。场景细分应用开箱即可支持更新 Maya 引用。


要让加载器应用可以识别您添加的新辅助发布类型,请在配置的 shot_step.yml 中更新 action_mappings 部分。

tk-multi-loader2:
  action_mappings:
    Alembic Cache: [reference, import]
    Camera: [reference, import]
    Maya Shader Network: [reference, import]
    Rendered Image: [texture_node]
    Maya Scene: [reference, import]

添加完新的动作映射后,加载器将显示可供引用的自定义辅助发布类型。

美工人员工作流

现在所有的自定义都已就绪,下面我们来看看如何将它们组合在一起。下面各节将逐步为您讲解从建模到合成的整个工作流。

建模

简单工作流的第一步是建模。在标题为配置的 Shotgun 项目的先决条件小节中,您应该已在 Shotgun 中创建了茶壶角色素材。如果您还没有创建,请先回去创建该素材,然后再继续。

从 Shotgun Desktop 启动 Maya。接下来,创建一个茶壶模型或下载并导入我们提供的茶壶。




得到满意的茶壶外观后,单击 Maya 界面顶部的 Shotgun > Shotgun File Manager... 菜单中的 File Save... 按钮。在文件管理器界面中,浏览到茶壶素材的建模 (Model) 任务,并为文件命名。




接下来,通过 Shotgun > Publish... 菜单启动发布器




单击摄影机图标为茶壶创建一个屏幕截图,并输入注释。然后,单击“Publish”并等待过程完成。




此时您便成功将模型发布为可供下游用户使用的 Maya 文件。

重复本节的过程,创建(或下载)一个桌子道具模型并发布它。完成后,茶壶和桌子素材应该各有一个发布为 Maya 文件的模型。

装备

发布模型后,下一步是构建并发布一个装备。

从 Shotgun Desktop 启动 Maya。应用程序打开后,单击 Maya 界面顶部的 Shotgun > Shotgun File Manager... 菜单中的 File Save... 按钮。在文件管理器界面中,浏览到茶壶素材的装备 (Rig) 任务,并为文件命名。




现在,使用 Shotgun > Load... 菜单动作启动加载器应用。加载器应用打开后,您可以选择之前发布的茶壶模型,将它引用到场景中。




装备已加载到场景中的茶壶模型。




装备做好发布准备后,通过 Shotgun > Publish... 菜单启动发布器。按照与发布模型相同的方法发布装备好的素材。在本例中,没必要为装备发布 Alembic 缓存,但即使发布也没任何坏处。




重复本节的过程,装备并发布桌子道具。完成后,茶壶和桌子素材应该各有一个发布为 Maya 文件的装备。

贴图

工作流中关于素材部分的最后一步是为发布的模型贴图,然后发布它们的着色器网络。

从 Shotgun Desktop 启动 Maya。应用程序打开后,单击 Maya 界面顶部的 Shotgun > Shotgun File Manager... 菜单中的 File Save... 按钮。在文件管理器界面中,浏览到茶壶素材的纹理 (Texturing) 和着色 (Shading) 任务,并为文件命名。




与前面的装备步骤一样,使用 Shotgun > Load... 菜单动作启动加载器应用。加载器应用打开后,您可以选择之前发布的茶壶模型,将它引用到场景中。

现在已经有了模型,我们来为茶壶贴图。




完成操作后,通过 Shotgun > Publish... 菜单启动发布器应用,发布着色器网络。按照与发布模型相同的方法发布完成贴图的素材。执行此操作时,务必确保“Maya 着色器网络”辅助项已启用。




重复本节的过程,为桌子道具贴图并进行发布。完成后,茶壶和桌子素材应该各有一个发布为 Maya 文件的着色器。

布局

下面我们进入工作流中的镜头制作部分。第一步是搭建一个基本的镜头布局,包括镜头摄影机的动画,以及茶壶和桌子素材在画面中的摆放位置。

从 Shotgun Desktop 启动 Maya。应用程序打开后,单击 Maya 界面顶部的 Shotgun > Shotgun File Manager... 菜单中的 File Save... 按钮。在文件管理器界面中,浏览到镜头的摄影机 (Camera) 任务,并为文件命名。




现在,使用 Shotgun > Load... 菜单动作启动加载器应用。加载器应用打开后,您可以选择之前发布的装备,将它们引用到场景中。




现在茶壶和桌子的装备已加载到场景中,我们根据需要放置它们。为镜头创建一个摄影机并添加动画效果。




完成操作后,通过 Shotgun > Publish... 菜单启动发布器应用,发布镜头布局的所有组成部分。这里务必为两个素材发布 Alembic 缓存,并为摄影机发布辅助输出。




动画

镜头的布局发布之后,接下来要向镜头中的素材添加附加动画。

从 Shotgun Desktop 启动 Maya。应用程序打开后,单击 Maya 界面顶部的 Shotgun > Shotgun File Manager... 菜单中的 File Save... 按钮。在文件管理器界面中,浏览到镜头的动画 (Animation) 任务,并为文件命名。




现在,使用 Shotgun > Load... 菜单动作启动加载器应用。加载器应用打开后,您可以选择摄影机任务中发布的 Maya 场景文件,并将它引用到场景中。引用素材之后,还要使用加载器将发布的摄影机加载进来。




现在布局已在场景中,我们根据需要为装备添加动画效果。




完成操作后,通过 Shotgun > Publish... 菜单启动发布器应用,发布镜头布局的所有组成部分。这里务必为两个素材发布 Alembic 缓存。在这一步,可能没必要再次发布摄影机,因为对摄影机的更改更多时候是在摄影机任务中进行的。




照明

在照明环节,您需要将之前为素材和镜头创建的所有已发布文件合并在一起。

从 Shotgun Desktop 启动 Maya。应用程序打开后,单击 Maya 界面顶部的 Shotgun > Shotgun File Manager... 菜单中的 File Save... 按钮。在文件管理器界面中,浏览到镜头的照明 (Lighting) 任务,并为文件命名。




现在,使用 Shotgun > Load... 菜单动作启动加载器应用。加载器应用打开后,您可以浏览查看以下已发布项,为它们创建引用:

  • 布局步骤发布的摄影机
  • 动画步骤发布的桌子和茶壶的 Alembic 缓存
  • 桌子和茶壶的着色器网络




最终的结果应该是上游生成的所有内容的组合。




下一步是根据自己的喜好在场景中放置灯光。




然后,您需要设置渲染层并渲染到磁盘。您需要确保渲染层中的输出路径与发布渲染图像部分中定义的模板一致。

完成操作后,通过 Shotgun > Publish... 菜单启动发布器应用,发布渲染图像层。




合成

镜头工作流的最后一步是合成。在这一步,我们可以加载照明步骤发布的渲染元素,以及布局步骤发布的镜头摄影机和动画步骤发布的 Alembic 缓存。将所有必要的数据加载到 Nuke 中以后,即可渲染最终的镜头。

从 Shotgun Desktop 启动 Nuke。应用程序打开后,单击 Nuke 界面顶部的 Shotgun > Shotgun File Manager... 菜单中的 File Save... 按钮。在文件管理器界面中,浏览到镜头的镜头收尾 (Shot Finaling) 任务,并为文件命名。




现在,使用 Shotgun > Load... 菜单动作启动加载器应用。加载器应用打开后,您可以选择照明任务中发布的渲染元素,并将它们读取到场景中。




加载器应用将在 Nuke 中为导入的每个项创建读取节点。




现在渲染元素已读取到场景中,我们来完成镜头的收尾工作。完成此操作后,您可以创建一个 Shotgun Quick Daily 节点,通过它可在 Shotgun 中根据合成的输出创建版本。




除了 Shotgun Quick Daily 节点,还可以使用 Shotgun 写入节点在 Shotgun Toolkit 中管理合成的输出。




有了 Shotgun 写入节点,可以使用从 Shotgun > Publish... 菜单启动的发布器应用来发布合成的输出。




审核

在制作过程中,有时候我们需要审核镜头中已完成的工作。如上面合成小节中有关 ShotgunQuickDaily 节点的部分所述,我们可以将 DCC 应用程序生成的媒体提交到 Shotgun。


要浏览已向 Shotgun 注册的媒体,可以从 Shotgun Desktop 启动审片室应用。




审片室打开后,可以按链接的实体来过滤可用媒体,无论是镜头还是素材。




双击要查看的媒体,然后使用 RV 中的播放控制选项播放它。

上游更新

在制作过程中,工作流各个步骤的美工人员会迭代创作并发布工作的新版本。Toolkit 提供场景细分应用,它会显示引用的发布文件有哪些版本已过期。


在美工人员的工作流中,有一项工作是检查更新,确保他们的文件是最新的,包含上游的最新工作成果。这是在工作流所有步骤都会做的工作,方法是从 Maya 的 Shotgun 菜单通过 Shotgun > Scene Breakdown... 启动场景细分应用。

此应用将显示文件中识别的所有发布文件引用。过期的引用将显示一个内部带有“x”的红色圆点。最新的引用将显示一个内部带有对勾的绿色圆点。



场景细分应用。左侧显示文件的引用在更新前的状态。
在右侧,摄影机已从版本 6 更新为版本 11。

要更新过期的引用,只需选择过期引用所在的行,然后单击 Update Selected 按钮。上图显示了更新前后应用的状态。

如果所有项都显示绿色,表示文件已使用上游最新发布的文件进行了更新。

总结

我们希望本教程能为您使用 Toolkit 打造自己的自定义工作流奠定良好的基础。学完本教程,您应该能够了解如何扩展默认的 Toolkit 应用来满足自己工作室的特定需求。

如果您认为默认的 Toolkit 应用未涵盖某些功能或工作流,可以随时编写自己的应用。这里是一份详实的文档,可帮助您快速入门编写您的第一个 Toolkit 应用。

与以往一样,如果您对本教程或者 Shotgun 或 Toolkit 的一般使用有其他疑问,请随时提交故障单

关注

0 评论

登录写评论。