MENU

• 2025 年 11 月 08 日 • 已有 139 只咪围观过 • -学海拾贝-

准备概览

  • 目标:在 Maya 中用 Pencil+ 4 做测试静态线稿渲染(不涉骨骼动画),快速得到“外轮廓 + 结构线”的干净结果。
  • 测试模型:使用《绝区零》官方发布模型(来源见下)。
  • 模型格式:原始为 MMD(.pmx),为静帧演示转换为 OBJ
  • 渲染思路:以 Line Set 分类出线(外轮廓 / 对象轮廓 / 交叉 / 材质边 / 硬边等),按需给不同类线指定独立笔刷,并用遮罩与焊接确保线条干净不重影
  • 进阶:测试插件主要功能,排查风险

下载与安装


素材与格式转换(MMD→OBJ)

适合只做静态渲染预览;若要绑定/动画,建议保留骨骼信息并在 DCC 内做更规范的导入流程。
  1. 来源

  2. 转换(PMX → OBJ)

    • 在线:AnyConv
    • 导出建议:

      • 仅静帧 → OBJ 即可(无骨骼、无动画)。
  3. 导入 Maya 后的整理

    • Modify > Freeze TransformsCenter Pivot
    • Mesh Display > Set to FaceSoften/Harden Edges(按结构设置硬/软边)
    • 合并或分离网格(按线条策略决定是否需要单体多对象
    • 统一材质(可先赋一个纯白 Lambert 以便观察线)

Pencil+ 4 基础上手

名称以 Pencil+ 4 for Maya 常见面板为参考,菜单/UI 可能随版本略有差异。
  1. 加载插件

    • Windows > Settings/Preferences > Plug-in Manager
    • 勾选 Pencil+4 相关插件(如 Line / Material 等)。
  2. 创建 Line Set(线集)

    • 新建一个 Line Set(可按「角色」「道具」「背景」分层管理)。
    • 将要出线的对象拖入或用过滤规则指定。

[PIC1]

  1. 勾选出线类别(核心)

    • 见下「Line Set 详解」。先只开 Outline(轮廓),确认外轮廓正确。
  2. 为不同线类指定独立笔刷

    • 各类别旁的 Specific Brush Settings 打勾后,会出现一个可指派笔刷资源的槽(例如 PencilBrushSettings_VisibleX)。
    • Brush/Stroke 预设拖入,使该类线有独立的粗细 / 纹理 / 虚线 / 端点等。
  3. 渲染预览

    • 切到目标相机,设置输出分辨率。
    • 渲染(需要用pencil自己的渲染器)。
    • 如需前后景压线逻辑,使用多 Line Set + 遮罩(见下)。
这里有一个比较通俗易懂的视频:

Line Set 详解与推荐搭配

一些功能解释和理解

1) Outline(轮廓)

  • Open Edge(开放边):把模型边界/洞口也当作轮廓线(衣服开口、眼洞等)。
  • Merge Groups(合并组):同组多个物体合并计算外轮廓内遮挡不重复出线
  • Specific Brush Settings:给外轮廓单独用更粗/更黑的笔刷。
搭配:角色主体(身体+衣物+饰品)常勾 Merge Groups,得到一圈干净大外轮廓;需要服装开口强调时再开 Open Edge

2) Object(对象轮廓)

  • Open Edge:与 Outline 同义,但按每个对象独立计算
  • Specific Brush Settings:仅覆盖对象轮廓的笔刷。
用途:同一组内的零件各保一圈外轮廓的画风(更“零件化”)。

3) Intersection(交叉线)

  • Self-Intersection(自相交):自身表面互相穿插/折叠处出线(衣褶、厚面回折)。
  • Specific Brush Settings:仅覆盖交叉线。
用途:强调接触/压痕(袖口压在手臂、鞋底接地)。技术插画常用。

4) Smoothing Boundary(光滑组/硬边边界)

  • Specific Brush Settings
用途:在硬边/法线断开处出线。非常适合硬表面倒角线/板块转折

5) Material ID Boundary(材质 ID 边界)

  • Specific Brush Settings
用途:同网格内不同材质 ID的分界出线 → 配色块分隔、面板缝无需额外切线

6) Selected Edges(选定边)

  • Specific Brush Settings
用途:仅对你手工选择的边出线,最适合“艺术指导式定点勾线”。
提示:用 Create > Sets > Quick Select Set组件选择集,便于复用与版本管理。

7) Normal Angle(法线夹角)

  • Min / Max:只在法线夹角落在该范围的边上出线。
  • Specific Brush Settings
用途:用角度阈值自动抓锐利/转折边
建议:硬表面把 Min 提高只抓很锐的折边;想更细腻就放宽范围。

8) Wireframe(线框)

  • Specific Brush Settings
用途:直接按拓扑线框出线。适合蓝图/教学/拓扑展示,或与其他线型叠加做质感。

9) 辅助管理

  • Weld Edges Between Objects(跨对象焊接边):将紧贴的不同物体在边界上视作连在一起,避免接缝双线/断裂
  • Mask Hidden Lines of Other Line Sets(遮罩其它线集的隐藏线):当前线集会遮蔽被其它线集物体挡住的线。

    典型:人物线集压住背景线集,防止背景线穿出人物。

笔刷 / 线型预设速览(可直接套用,官方内置的)

这些是常见命名的示意与适用性总结。不同版本可能略有差异,请以你的安装包为准;你也可在此基础上做二次定制。

image (8).png

Brush(笔刷纹理类)

  • P_Brush_Bubble(泡泡)柔软圆斑,可爱/软糯风外轮廓或填充式线条。
  • P_Brush_Circle(圆头):线宽均匀干净,卡通/技术图的基础款。
  • P_Brush_Default(默认):通用起点,便于继续自定义。
  • P_Brush_Flat(扁平/排刷):椭圆笔尖,转向有“压扁/起收锋”感,带一点书法味。
  • P_Brush_Flower(花瓣):转角有装饰纹理,适合特效/装饰线。
  • P_Brush_Rough(粗糙):颗粒断续,模拟蜡笔/干粉,有旧纸质感
  • P_Brush_Soft(软边):边缘羽化,适合次级结构线/弱化存在感

Stroke(线型类)

  • P_Stroke_DashedLine1/2/3(虚线):不同间隔/端形;用于隐线/分区线/运动轨迹
  • P_Stroke_Default(默认线型):平滑连续,标准。
  • P_Stroke_Fuzzy1/2/3/4(毛边/抖动):噪声递增;用于手绘颤动/远景弱化/粗糙材质
  • P_Stroke_Pen / Pen2 / Pen3(记号笔系):硬/刷/压感各异;漫画分镜/产品线稿常用。
  • P_Stroke_Spray(喷点):粒子感强;喷涂/尘土/速度尾迹衰减末端
  • P_Stroke_Wave_Large / Wave_Small(波浪线)震动/热气/水波/毛绒边等卡通化强调。
📌 在 Specific Brush Settings 的灰色槽中指派上述笔刷/线型资源,即可让该类线独立控制粗细/纹理/噪声/端头等。
fuzzy真的很像手绘,可以做很多有意思的效果
image (9).png
image (10).png

渲染输出与合成建议

  • 分层管理

    • 人物(粗外轮廓)人物(结构线)道具 / 背景 分成多个 Line Set,利于遮罩与独立调线。
    • 人物线集放在最上层,勾选 Mask Hidden Lines of Other Line Sets
  • 厚薄与缩放

    • 线宽通常与场景尺度/相机距离相关;建立相机-线宽标尺

      • 近景外轮廓:2.0~4.0(示意)
      • 中景外轮廓:1.2~2.2
      • 远景外轮廓:0.6~1.2(或用 Fuzzy 线弱化)
  • 输出与合成

    • 建议输出带透明通道(如 PNG/TIF)便于后期与底色/纹理合成。
    • 可分多通道:外轮廓(粗)结构线(细)隐线/虚线;在 PS/AE/Nuke 中按线型分组做二次调色。

常见问题排错清单

  • 接缝处双线/破碎

    • 勾选 Weld Edges Between Objects;检查是否存在重叠面/重合壳
  • 开口/洞口没出线

    • Outline/Object 内勾 Open Edge
  • 结构线过多、画面脏

    • 只开外轮廓,逐一加开 Intersection / Normal Angle / Material ID
    • 控制 Normal Angle 的 Min/Max,收窄阈值。
  • 硬面折线抓不住

    • 使用 Smoothing Boundary 或提升 Normal Angle Min
  • 材质分块不清

    • 检查模型是否有清晰的材质 ID;必要时在 Maya 中分配多材质
  • 笔刷不生效

    • 确认勾选了 Specific Brush Settings正确指派资源。
  • 线宽随镜头飘

    • 统一场景单位与镜头距离;必要时按镜头远近做多版本线宽或合成端做指数衰减
  • 法线异常导致漏线

    • Mesh Display > Conform / Set to Face 后再 Soften/Harden;清理反法线非流形

*渲染设置(补丁)

理论上基本和正常的render是一致的,这里可以改渲染器

image (11).png

但是,我调取不到batch render官方说明中的-pl:port <int>是不可用的..

所以修修补补写了个临时的batch插件(仅供参考,学习交流)(2022以上):


from __future__ import annotations
import traceback
from maya import cmds, mel


FORCE_OUTPUT_DIR = ""


def _ensure_plugin_loaded(plugin_name):
    try:
        if not cmds.pluginInfo(plugin_name, q=True, loaded=True):
            cmds.loadPlugin(plugin_name)
        return True
    except Exception:
        return False

def _get_current_renderer():
    try:
        return cmds.getAttr("defaultRenderGlobals.currentRenderer")
    except Exception:
        return "unknown"

def _get_frame_range_from_settings():
    s = int(cmds.getAttr("defaultRenderGlobals.startFrame"))
    e = int(cmds.getAttr("defaultRenderGlobals.endFrame"))
    by = int(round(cmds.getAttr("defaultRenderGlobals.byFrameStep")))
    return s, e, max(1, by)

def _set_frame_range(s, e, by):
    cmds.setAttr("defaultRenderGlobals.startFrame", s)
    cmds.setAttr("defaultRenderGlobals.endFrame", e)
    cmds.setAttr("defaultRenderGlobals.byFrameStep", by)

def _ensure_animation_on():
    try:
        if cmds.getAttr("defaultRenderGlobals.animation") != 1:
            cmds.setAttr("defaultRenderGlobals.animation", 1)
    except Exception:
        pass

def _list_render_layers():
    try:
        from maya.app.renderSetup.model import renderSetup
        rs = renderSetup.instance()
        layers = rs.getRenderLayers()
        active = [l for l in layers if getattr(l, "isRenderable", None) and l.isRenderable()] \
                 or [l for l in layers if getattr(l, "isVisible", None) and l.isVisible()] \
                 or list(layers)
        return active
    except Exception:
        return []

def _switch_to_layer(layer_obj):
    try:
        layer_obj.switchTo()
        return True
    except Exception:
        return False

def _list_renderable_cameras():
    cams = []
    for cam_shape in cmds.ls(type="camera", long=True) or []:
        try:
            if cmds.getAttr(cam_shape + ".renderable"):
                tr = cmds.listRelatives(cam_shape, p=True, f=True) or []
                if tr:
                    cams.append(tr[0])  
        except Exception:
            pass

    seen, out = set(), []
    for c in cams:
        if c not in seen:
            out.append(c); seen.add(c)
    return out

def _maybe_override_output_dir(dir_path):
    if not dir_path:
        return
    try:
        if not cmds.objExists("defaultRenderGlobals.imageFilePrefix"):
            return
        prefix = cmds.getAttr("defaultRenderGlobals.imageFilePrefix") or "<Scene>/<RenderLayer>/<Camera>"
        if dir_path.endswith("/") or dir_path.endswith("\\"):
            dir_path = dir_path[:-1]
        new_prefix = dir_path.replace("\\", "/") + "/" + prefix
        cmds.setAttr("defaultRenderGlobals.imageFilePrefix", new_prefix, type="string")
        print("[BatchRender] Override output dir ->", new_prefix)
    except Exception:
        print("[BatchRender] Failed to override output dir")
        traceback.print_exc()

def _camera_arg_for_mel(camera_transform):

    return camera_transform.replace('"', '\\"')


def _renderer_entry(renderer_name):
    r = (renderer_name or "").lower()
    if r in ("arnold", "mtoa"):
        return "arnold"
    return "generic"

def _render_sequence_with_arnold(camera, start, end, by):
    if not _ensure_plugin_loaded("mtoa"):
        raise RuntimeError("无法加载 Arnold 插件(mtoa)。")
    shapes = cmds.listRelatives(camera, s=True, ni=True, f=True) or []
    cam_shape = (shapes[0] if shapes else camera).replace('"','\\"')
    mel.eval(
        'arnoldRender -seq -cam "{cam}" -start {s} -end {e} -by {by};'.format(
            cam=cam_shape, s=start, e=end, by=by
        )
    )

def _render_sequence_generic(camera, start, end, by):

    cam_arg = _camera_arg_for_mel(camera)
    for f in range(start, end + 1, by):
        print("[BatchRender] Rendering frame {f} @ {cam}".format(f=f, cam=camera))
        cmds.currentTime(f, e=True)
        mel.eval('render "{cam}";'.format(cam=cam_arg))


def run_batch(cameras=None, layers=None, start=None, end=None, by=None, override_output_dir=FORCE_OUTPUT_DIR):

    try:
        cur_renderer = _get_current_renderer()
        entry = _renderer_entry(cur_renderer)
        print("[BatchRender] Current Renderer:", cur_renderer, "->", entry)


        s0, e0, by0 = _get_frame_range_from_settings()
        s = int(start) if start is not None else s0
        e = int(end) if end is not None else e0
        step = int(by) if by is not None else by0
        _set_frame_range(s, e, step)
        _ensure_animation_on()

        _maybe_override_output_dir(override_output_dir)


        layer_objs = layers if layers is not None else _list_render_layers()
        if not layer_objs:
            print("[BatchRender] 未找到 Render Setup 层,可能场景未使用 Render Setup。将在当前层渲染。")


        cams = cameras if cameras is not None else _list_renderable_cameras()
        if not cams:
            raise RuntimeError("未发现可渲染相机(CameraShape.renderable==True)。请在需要的相机上勾选 Renderable。")

        def _do_render_for_current_layer():
            for cam in cams:
                if entry == "arnold":
                    _render_sequence_with_arnold(cam, s, e, step)
                else:
                    _render_sequence_generic(cam, s, e, step)

        if layer_objs:
            for lyr in layer_objs:
                lname = getattr(lyr, "name", lambda: str(lyr))()
                print("\n[BatchRender] ===== Render Layer:", lname, "=====")
                _switch_to_layer(lyr)
                _do_render_for_current_layer()
        else:
            _do_render_for_current_layer()

        print("\n[BatchRender] ✅ 完成。请查看输出目录。")

    except Exception as ex:
        print("\n[BatchRender] ❌ 失败:", ex)
        traceback.print_exc()


def _build_ui():
    if cmds.window("BatchSeqRenderUI", exists=True):
        cmds.deleteUI("BatchSeqRenderUI")
    win = cmds.window("BatchSeqRenderUI", title="Batch Sequence Render (Maya 2024)", sizeable=False)
    col = cmds.columnLayout(adj=True, rs=6, cw=440)


    s0, e0, by0 = _get_frame_range_from_settings()
    cmds.text(l="Frame Range(读取自 Render Settings)")
    fr = cmds.rowLayout(nc=6, cw6=[70,90,50,90,50,90], adj=2)
    cmds.text(l="Start")
    f_s = cmds.intField(v=s0)
    cmds.text(l="End")
    f_e = cmds.intField(v=e0)
    cmds.text(l="Step")
    f_by = cmds.intField(v=by0)
    cmds.setParent("..")

    cmds.text(l="Override Output Directory(可选,留空沿用 Render Settings)")
    f_dir = cmds.textField(tx=FORCE_OUTPUT_DIR, cc=lambda *_: None)

    cams = _list_renderable_cameras()
    cmds.text(l="Renderable Cameras(如不选择则使用全部)")
    f_cam = cmds.textScrollList(ams=True, h=120)
    for c in cams:
        cmds.textScrollList(f_cam, e=True, a=c)

    lyr_names = []
    try:
        from maya.app.renderSetup.model import renderSetup
        rs = renderSetup.instance()
        for l in rs.getRenderLayers():
            lyr_names.append(l.name())
    except Exception:
        pass
    cmds.text(l="Render Setup Layers(如不选择则按可渲染/可见层自动)")
    f_layer = cmds.textScrollList(ams=True, h=120)
    for n in lyr_names:
        cmds.textScrollList(f_layer, e=True, a=n)

    def _on_run(*_):
        s = cmds.intField(f_s, q=True, v=True)
        e = cmds.intField(f_e, q=True, v=True)
        by = cmds.intField(f_by, q=True, v=True)
        out_dir = cmds.textField(f_dir, q=True, tx=True).strip()

        sel_cams = cmds.textScrollList(f_cam, q=True, sii=True) or []
        use_cams = [cams[i-1] for i in sel_cams] if sel_cams else cams

        sel_layers = cmds.textScrollList(f_layer, q=True, sii=True) or []
        use_layers = None
        if sel_layers:
            from maya.app.renderSetup.model import renderSetup
            rs = renderSetup.instance()
            name_to_obj = {l.name(): l for l in rs.getRenderLayers()}
            use_layers = [name_to_obj[lyr_names[i-1]] for i in sel_layers]

        run_batch(cameras=use_cams, layers=use_layers, start=s, end=e, by=by, override_output_dir=out_dir)

    cmds.separator(h=5, st="none")
    cmds.button(l="Run", h=34, c=_on_run)
    cmds.separator(h=6, st="none")
    cmds.button(l="Close", c=lambda *_: cmds.deleteUI(win))
    cmds.showWindow(win)

# 执行即弹 UI;也可在 Script Editor 里手动调用 run_batch(...)
try:
    _build_ui()
except Exception:
    traceback.print_exc()
    print("提示:你也可以在 Script Editor 里手动调用: run_batch()")
最后编辑于: 2025 年 11 月 13 日
返回文章列表 打赏
本页链接的二维码
打赏二维码