헤어 제작 XGen 가이드라인

언리얼 엔진에 사용할 그룸용 얼렘빅 파일의 익스포트 방법을 설명합니다.

Windows
MacOS
Linux

이 가이드에서는 Maya의 레거시 XGen 헤어 제작 시스템 에서 언리얼 엔진으로 지원 어트리뷰트를 이용해 그룸을 임포트하고 설정하는 방법을 그룸용 얼렘빅 세부사항 문서를 통해 설명합니다.

이 가이드의 에셋은 Maya 2018.6으로 제작했습니다.

레거시 XGen 설명 변환

가이드를 NURBS 커브로 변환

옮기려는 가이드와 일치하는 커브를 저장하기 위해 다음과 같은 단계를 통해 그룸의 가이드를 커브로 변환합니다.

  1. Maya의 메뉴 세트를 Modeling 으로 설정하면 이용 가능한 메뉴를 정확히 살펴볼 수 있습니다.

    MenuSetSelection.png

  2. 메인 메뉴에서 Generate 드롭다운을 클릭하고 XGen Editor 를 선택합니다.

  3. XGen 창에서 Utilities 탭을 이용하여 Guides to Curves 를 선택합니다.

    XGen_Utilities.png

  4. Create Curves 을 클릭합니다.

완료하면 그룸의 출력은 아래와 같습니다.

GuidesToNurbsCurvesOutput.png

그룸을 XGen 인터랙티브 그룸으로 변환

레거시 XGen 설명을 사용하는 경우, 그룸을 XGen Interactive Groom 으로 변환해야 합니다. 다음과 같이 진행합니다.

  1. XGen 설명 노드를 선택합니다.

    XGenDescriptionNodes.png

  2. Modeling 메뉴 내에서 메인 메뉴를 통해 Generate 드롭다운을 클릭한 후 Convert to Interactive Groom 을 선택합니다.

    XGenInteractiveGroom.png

스플라인 설명을 NURBS 커브로 익스포트

선택한 스플라인 설명을 다음 단계에 따라 얼렘빅 파일로 익스포트합니다. 얼렘빅 파일은 보간된 헤어를 NURBS 커브로 임포트할 수 있습니다.

  1. XGen 스플라인 설명 노드를 선택하고, Modeling 메뉴 내에서 메인 메뉴를 통해 Generate 드롭다운을 클릭합니다. 목록에서 Cache > Export Cache 를 선택합니다.

    SplineDescToNurbsCurve_ExportCache.png

  2. Export Cache 창에서 다음과 같이 설정합니다.

    • Cache Time Frame: Current Frame 으로 설정

    • Multiple Transforms: 비활성화됨

    • Write Final Width: 활성화됨

    SplineDescToNurbsCurve_ExportCacheWindow.png

  3. 파일 이름을 입력하고 파일 유형으로 Alembic 을 선택합니다.

    SplineDescToNurbsCurve_ExportCacheFile.png

  4. Export 를 클릭합니다.

  5. File 메뉴에서 Import 를 선택합니다. Import 창이 열립니다. 이 창을 이용해 얼렘빅('.abc') 파일을 선택하여 씬으로 임포트할 수 있습니다.

    SplineDescToNurbsCurve_ImportAlembic.png

임포트가 완료되면, 다음과 같이 얼렘빅 파일로 익스포트 및 저장된 XGen 스플라인 설명을 임포트하여 NURBS 커브로 보간된 헤어를 가져옵니다.

SplineDescToNurbsCurve_Output.png

어트리뷰트 생성

그룹 ID 어트리뷰트 생성

보간된 헤어는 하나 이상의 그룹으로 익스포트될 수 있습니다. 언리얼 엔진은 이러한 그룹을 고유 머티리얼 할당으로 인식합니다.

그룹 ID 어트리뷰트 생성 시 다음과 같은 스크립트를 사용할 수 있습니다.

from maya import cmds

attr_name = 'groom_group_id'

# 참고: 다음 이름을 변경하여 노드의 씬 반영
groups = ['hair_brows_splineDescription1|SplineGrp0', 'hair_lashes_splineDescription1|SplineGrp0', 'hair_head_splineDescription1|SplineGrp0']

for groom_group_id, group_name in enumerate(groups):

    # xgGroom에서 커브 얻기
    curves = cmds.listRelatives(group_name, ad=True, type='nurbsCurve')

    # 그룹 ID로 태그 그루핑
    cmds.addAttr(group_name, longName=attr_name, attributeType='short', defaultValue=groom_group_id, keyable=True)

    # 어트리뷰트 범위 추가
    # Maya의 얼렘빅이 데이터를 GeometryScope::kConstantScope로 익스포트하도록 강제
    cmds.addAttr(group_name, longName='{}_AbcGeomScope'.format(attr_name), dataType='string', keyable=True)
    cmds.setAttr('{}.{}_AbcGeomScope'.format(group_name, attr_name), 'con', type='string')

가이드 어트리뷰트 생성

언리얼 엔진에서 그룸의 가이드 어트리뷰트 생성 시, 가이드 로 태그된 커브만 시뮬레이션에 사용됩니다. UE4로 임포트하는 과정에서 얼렘빅 파일에 지정된 가이드가 없는 경우, 보간된 헤어의 일정 퍼센티지가 내부적으로 가이드로 태그됩니다.

가이드 없는 그룸을 임포트할 때, 가이드로 태그되는 보간된 헤어의 일정 퍼센티지는 그룸 임포트 옵션 을 사용해 설정할 수 있습니다. 기본적으로 헤어의 10%만이 가이드로 사용됩니다.

가이드 어트리뷰트 생성 시 다음 스크립트를 사용할 수 있습니다.

from maya import cmds

attr_name = 'groom_guide'

# xgGroom에서 커브 얻기
curves = cmds.listRelatives('xgGroom', ad=True, type='nurbsCurve')

# 새 그룹 생성
guides_group = cmds.createNode('transform', name='guides')

# 그룹을 groom_guide로 태그
cmds.addAttr(guides_group, longName=attr_name, attributeType='short', defaultValue=1, keyable=True)

# Maya 얼렘빅이 커브를 하나의 그룹으로 익스포트하도록 강제
cmds.addAttr(guides_group, longName='riCurves', attributeType='bool', defaultValue=1, keyable=True)

# 어트리뷰트 범위 추가
# Maya의 얼렘빅이 데이터를 GeometryScope::kConstantScope로 익스포트하도록 강제
cmds.addAttr(guides_group, longName='{}_AbcGeomScope'.format(attr_name), dataType='string', keyable=True)
cmds.setAttr('{}.{}_AbcGeomScope'.format(guides_group, attr_name), 'con', type='string')

# 가이드 그룹 내에서 커브 인 커브
종속 설정:
    cmds.parent(curve, guides_group, shape=True, relative=True)

Groom_Width 어트리뷰트

Maya의 경우, 너비값은 특별 비헤이비어를 가지고 있습니다. 그룸(Groom)용 얼렘빅 세부사항 에 따라 회수하고 사용하여 그룸을 빌드하는 다른 DCC 애플리케이션과는 다릅니다.

Maya는 너비값을 커브에서 직접 익스포트할 수 있어서 커스텀 groom_width 어트리뷰트를 익스포트할 필요가 없습니다. 임포터는 해당 값을 자동으로 그 어트리뷰트로 변환합니다. 언리얼 엔진으로 임포트 시 groom_wdith 어트리뷰트가 그룸과 함께 존재하는 경우에는 오버라이트되지 않습니다. `groom_wdith`가 지정되지 않았거나 너비값으로 변환할 수 없는 경우, 빌더는 1cm 값으로 전환합니다.

Maya에서 얼렘빅으로 익스포트

  1. Maya에서 익스포트하려는 가이드와 Group_ID 커브를 선택합니다.

    각 노드는 고유한 이름이 있어야 합니다.

  2. 모델링 메뉴 내에서 메인 메뉴를 통해 Cache 드롭다운을 클릭한 후 Alembic Cache > Export Selection to Alembic 를 선택합니다.

    ExportToAlembic_ExportSelection.png

  3. Export Selection 창의 General Options 카테고리에서 Cache time rangeCurrent Frame 으로 설정합니다.

    ExportToAlembic_ExportSelection_CurrentFrame.png

  4. Attributes 카테고리에서 원하는 어트리뷰트 의 이름을 목록에 입력하고 Add 버튼을 누릅니다. 다음과 같은 스키마 어트리뷰트를 추가합니다.

    ExportToAlembic_ExportSelection_AddAttributes.png

    • groom_group_id

    • groom_guide

  5. File name 텍스트박스에 파일 이름을 입력하고, Files of typeAlembic 으로 설정합니다.

    ExportToAlembic_ExportSelection_FileNameType.png

  6. Export Selection 버튼을 클릭합니다.

텍스처를 헤어 UV에 적용

아래 단계별 설명 및 스크립트를 통해 언리얼 엔진으로 익스포트 가능한 XGen 헤어를 설정할 수 있습니다. 적용된 텍스처를 각 헤어 스트랜드에 표시할 수 있습니다.

  1. Maya에서 Modeling 메뉴를 통해 Generate > Create Interactive Groom Splines 를 선택합니다.

    HairUV_XGen.png

  2. 프로젝트에서 가이드를 생성하고 원하는 대로 헤어를 빗을 수 있습니다. 준비가 완료되면 Generate > Cache > Create New Cache 를 선택하여 커브를 Alembic Cache 로 익스포트합니다.

    HairUV_2_XGen.png

  3. XGen 헤어를 숨기거나 삭제하여 제거합니다. 그 다음 익스포트한 헤어 커브를 Maya 씬의 소스 메시로 리임포트합니다.

    HairUV_3_XGen.png

  4. 씬에 따라 최상위 커브 아래 자손으로 수천 개의 스플라인 커브가 존재할 수 있습니다. 이 예시의 경우 최상위 커브는 SplineGrp0 입니다. 다음과 같은 Python 스크립트를 편집하여 아래 값을 프로젝트 값으로 대체합니다.

    • export_directory

    • hair_file

    • curve_top_group

    • uv_mesh

    여기 에서 아래 스크립트를 다운로드할 수 있습니다.

    from maya import cmds
    from maya import OpenMaya
    import os
    
    def create_root_uv_attribute(curves_group, mesh_node, uv_set='map1'):
        '''
        Create "groom_root_uv" attribute on group of curves.
        '''
    
        # check curves group
        if not cmds.objExists(curves_group):
            raise RuntimeError('Group not found: "{}"'.format(curves_group))
    
        # get curves in group
        curve_shapes = cmds.listRelatives(curves_group, shapes=True, noIntermediate=True)
        curve_shapes = cmds.ls(curve_shapes, type='nurbsCurve')
        if not curve_shapes:
            raise RuntimeError('Invalid curves group. No nurbs-curves found in group.')
        else:
            print "found curves"
            print curve_shapes
    
        # get curve roots
        points = list()
        for curve_shape in curve_shapes:
            point = cmds.pointPosition('{}.cv[0]'.format(curve_shape), world=True)
            points.append(point)
    
        # get uvs
        values = list()
        uvs = find_closest_uv_point(points, mesh_node, uv_set=uv_set)
        for u, v in uvs:
            values.append([u, v, 0])
            #print (str(u) + " , " + str(v)  )
    
        # create attribute
        name = 'groom_root_uv'
        cmds.addAttr(curves_group, ln=name, dt='vectorArray')
        cmds.addAttr(curves_group, ln='{}_AbcGeomScope'.format(name), dt='string')
        cmds.addAttr(curves_group, ln='{}_AbcType'.format(name), dt='string')
    
        cmds.setAttr('{}.{}'.format(curves_group, name), len(values), *values, type='vectorArray')
        cmds.setAttr('{}.{}_AbcGeomScope'.format(curves_group, name), 'uni', type='string')
        cmds.setAttr('{}.{}_AbcType'.format(curves_group, name), 'vector2', type='string')
    
        return uvs
    
    def find_closest_uv_point(points, mesh_node, uv_set='map1'):
        '''
        Find mesh UV-coordinates at given points.
        '''
    
        # check mesh
        if not cmds.objExists(mesh_node):
            raise RuntimeError('Node not found: "{}"'.format(mesh_node))
    
        # check uv_set
        uv_sets = cmds.polyUVSet(mesh_node, q=True, allUVSets=True)
        if uv_set not in uv_sets:
            raise RuntimeError('Invalid uv_set provided: "{}"'.format(uv_set))
    
        # get mesh as dag-path
        selection_list = OpenMaya.MSelectionList()
        selection_list.add(mesh_node)
    
        mesh_dagpath = OpenMaya.MDagPath()
        selection_list.getDagPath(0, mesh_dagpath)
        mesh_dagpath.extendToShape()
    
        # get mesh function set
        fn_mesh = OpenMaya.MFnMesh(mesh_dagpath)
    
        uvs = list()
        for i in range(len(points)):
    
            script_util = OpenMaya.MScriptUtil()
            script_util.createFromDouble(0.0, 0.0)
            uv_point = script_util.asFloat2Ptr()
    
            point = OpenMaya.MPoint(*points[i])
            fn_mesh.getUVAtPoint(point, uv_point, OpenMaya.MSpace.kWorld, uv_set)
    
            u = OpenMaya.MScriptUtil.getFloat2ArrayItem(uv_point, 0, 0)
            v = OpenMaya.MScriptUtil.getFloat2ArrayItem(uv_point, 0, 1)
    
            uvs.append((u, v))
    
        return uvs
    
    def abc_export(filepath, node=None, start_frame=1, end_frame=1, data_format='otawa', uv_write=True):
    
        job_command = '-frameRange {} {} '.format(start_frame, end_frame)
        job_command += '-dataFormat {} '.format(data_format)
    
        job_command += '-attr groom_root_uv '
    
        if uv_write:
            job_command += '-uvWrite '
    
        job_command += '-root {} '.format(node)   
    
        job_command += '-file {} '.format(filepath) 
    
        cmds.AbcExport(verbose=True, j=job_command)
    
    def main():
    
        export_directory = 'D:/Dev/Ref'
        hair_file = os.path.join(export_directory, 'hair_export.abc')
        curve_top_group= 'description1|SplineGrp0'
        uv_mesh='pPlane1'
    
        create_root_uv_attribute( curve_top_group , uv_mesh)
        abc_export(hair_file, curve_top_group)
    
    main()
  5. Maya에서 변경된 값으로 스크립트를 실행하면 새 얼렘빅('.abc') 파일을 생성할 수 있습니다. 생성된 파일은 언리얼 엔진으로 임포트할 수 있습니다.

  6. 언리얼 엔진에서 헤어 셰이딩 모델을 사용하여 새 머티리얼을 생성합니다. 머티리얼 그래프에서 헤어 어트리뷰트 표현식을 추가하고 루트 UV 를 텍스처 샘플의 UV 입력에 연결합니다.

    HairUV_4_XGen.png

    groom_root_uv 어트리뷰트는 헤어가 어태치된 기반 메시 UV를 헤어별로 지정합니다. 이 어트리뷰트는 선택사항입니다. 지정되지 않은 경우, 엔진에서 자동으로 구형 매핑을 통해 루트 UV를 생성합니다.

  7. 임포트된 헤어 얼렘빅 파일을 콘텐츠 브라우저에서 레벨로 드래그한 후, 헤어 머티리얼을 할당합니다. 완료하면 다음과 같습니다.

    레벨의 헤어 얼렘빅 파일의 너비는 반드시 0보다 커야 합니다.

    HairUV_5_XGen.png

Select Skin
Light
Dark

Welcome to the new Unreal Engine 4 Documentation site!

We're working on lots of new features including a feedback system so you can tell us how we are doing. It's not quite ready for use in the wild yet, so head over to the Documentation Feedback forum to tell us about this page or call out any issues you are encountering in the meantime.

We'll be sure to let you know when the new system is up and running.

Post Feedback