Introduction

Rigging 과정에서 Skin Weights 작업을 할 때, 위와 같이 복잡한 Model 의 Skin Weights 를 설정하는 일은 굉장히 까다롭다.
이럴 때 단순한 Mesh 를 만들어서 Weights 작업을 하고, Skin Copy 를 해 주면 비교적 쉽게 해결을 할 수 있다.
Bifrost 와 Retpology 노드를 이용해서 Skin Cage Mesh 만드는 방법을 정리해 놓고자 한다.
Environments
- OS: Windows10
- Maya Version: 2024.2
- Bifrost Version 2.7.1.1
Mesh to Volume Mesh


Bifrost Graph 를 만들고, mesh_to_volume 노드를 만든 후 mesh port 를 input port 에 연결하고, 위 이미지와 같이 Parameters 를 설정한다.

volume_to_mesh 노드를 만들고 위 이미지와 같이 연결한다.

mesh 노드와 bifrostGraph 노드를 Node Editor 에 불러와서 위 이미지와 같이 연결한다.
worldMesh 가 아니라 outMesh 를 bifrostGraph.mesh 에 연결하면, mesh 노드의 transform 에 값이 들어가 있는 경우 원치 않는 곳에 Volume Mesh 가 생성된다.

위 이미지와 같이 Volume Mesh 가 생성 되는데, 막혀 있는 Mesh 가 아니라서 오른쪽 이미지와 같이 얇은 두께가 있는 Mesh 가 생성된다.
어떤 버전부터 인지는 모르겠지만, Bifrost 의 mesh_to_volume 노드에 이 문제를 해결할 수 있는 Parameter 가 생겼다.

min_hole_radius 값을 올리면 구멍이 메워지는 것을 볼 수 있다.
30 이상으로 값을 올리면 계산하는데 시간이 너무 오래 걸리기 때문에 30 이하의 값을 사용할 것을 권장한다.
Retopology Volume Mesh

bifrostGeoToMaya, polyRetopo, mesh 노드를 만들고 위 이미지와 같이 연결해 준다.

polyRetopo 노드의 설정을 바꿔가며 원하는 결과를 만든다.
Conclusion
손, 가랑이 등의 좀 더 Detail 이 필요한 곳은 따로 분리해서 위와 같은 방식으로 진행하면 될 것이다.
Old Content
Bifrost
Maya Mesh to Bifrost Mesh
Bifrost 에서는 복잡한 Mesh 를 하나의 단순한 Mesh 로 만드는 과정을 진행한다.

Combine 을 해서 하나의 Mesh 로 만든다.

Windows > Bifrost Graph Editor 메뉴를 선택해 Editor 를 연다.
Bifrost Graph Editor 메뉴가 보이지 않는다면?

Windosw > Settings/Preferences > Plug-in Manager 메뉴를 눌러서 Plug-in Manager 를 열고,
bifrost 를 검색해 bifrostGraph.mll 플러그인을 Load 한다.

Create Graph 버튼을 누른다.

MMB Drag 로 Mesh 를 Graph Editor 에 끌어다 놓고, input 노드를 삭제한다.

Tab 키를 누르고 tovol 검색어를 입력해 mesh_to_volume 노드를 만든다.

input 의 mesh 플러그를 mesh_to_volume 노드에 연결해 준다.
Volume Mode 를 Shell 로 설정!
Store Level Set 체크!
Store Fog Density 체크 해제!
Detail Size 는 0.01 정도로 입력한다. 수치가 작을 수록 Detail 해 진다.

tomesh 검색어를 입력해서 volume_to_mesh 노드를 만든다.

위와 같이 노드를 연결한다.

단순한 mesh 가 생성 되었다.
Bifrost Mesh to Maya Mesh

Node Editor 에서 bifrostGeoToMaya 노드와 mesh 노드를 만들어서 연결한다.

Separate 를 해서 불필요한 mesh 들을 삭제하고, obj 로 Export 한다.
Instant Meshes

https://github.com/wjakob/instant-meshes#pre-compiled-binaries
위 페이지로 가서, Windows 용 프로그램을 다운 받고 실행한다.

Open Mesh 버튼을 눌러서 Obj 파일을 불러 온다.

Target vertex count 를 1K 정도로 설정하고 Orientation field 의 Solve 버튼을 누른다.

빗 모양의 아이콘을 누르면 Flow 를 그려 줄 수 있다.
이 모드에서는 Viewport 를 움직일 수 없다.

Position field 의 Solve 버튼을 누르고,
Export mesh – Extract mesh 버튼을 누르면 Mesh 가 생성된다.
Save… 버튼을 눌러서 obj 로 Export 한다.

Maya 로 가져와서 좌우 대칭을 만들어 주는 등의 추가 작업을 하고 마무리 한다.
Retopologize

Mesh > Reopologize Optionbox 를 선택한다.
위와 같이 옵션을 설정한 후 Retopologize 버튼을 누른다.

Edge Flow 를 컨트롤 할 수 없기 때문에 대칭이 중요하지 않은 경우에 유용하다. (Maya 2024에 Symmetry 기능이 추가되었다.)
아래는 선택한 Mesh 를 Bifrost 를 이용해 Volume Mesh 로 바꾼 후 Retopologize 를 해 주는 코드이다.
convertToCageMesh 함수의 detailSize 와 faceCount 파라메터로 디테일 정도를 조절할 수 있다.
import pymel.core as pm
if not pm.pluginInfo('bifrostGraph', q=True, loaded=True):
pm.loadPlugin('bifrostGraph')
def convertToCageMesh(mesh=None, detailSize=0.025, faceCount=1000):
# It takes long time and produce not good result when below 0.01 value for the detail size
detailSize = max(detailSize, 0.01)
# Build bifrost graph node
bfGraph = pm.createNode('bifrostGraphShape')
pm.vnnNode(bfGraph, '/input', createOutputPort=('inMesh', 'Amino::Object'))
pm.vnnNode(bfGraph, '/output', createInputPort=('outMeshes', 'array<Amino::Object>'))
pm.vnnCompound(bfGraph, '/', addNode='BifrostGraph,Geometry::Converters,mesh_to_volume')
pm.vnnNode(bfGraph, '/mesh_to_volume', setPortDefaultValues=('volume_mode', '1'))
pm.vnnNode(bfGraph, '/mesh_to_volume', setPortDefaultValues=('store_level_set', '1'))
pm.vnnNode(bfGraph, '/mesh_to_volume', setPortDefaultValues=('store_fog_density', '0'))
pm.vnnNode(bfGraph, '/mesh_to_volume', setPortDefaultValues=('detail_size', str(detailSize)))
pm.vnnCompound(bfGraph, '/', addNode='BifrostGraph,Geometry::Converters,volume_to_mesh')
pm.vnnNode(bfGraph, '/volume_to_mesh', createInputPort=('volumes.volume', 'auto'))
pm.vnnConnect(bfGraph, '/input.inMesh', '/mesh_to_volume.mesh')
pm.vnnConnect(bfGraph, '/mesh_to_volume.volume', '/volume_to_mesh.volumes.volume')
pm.vnnConnect(bfGraph, '/volume_to_mesh.meshes', '/output.outMeshes')
# Convert to cage mesh
skinCageName = '{}_cage'.format(mesh)
mesh = pm.duplicate(mesh)[0]
bfGeoToMaya = pm.createNode('bifrostGeoToMaya')
cageMesh = pm.createNode('mesh')
cageMesh.getParent().rename(skinCageName)
mesh.outMesh >> bfGraph.inMesh
bfGraph.outMeshes >> bfGeoToMaya.bifrostGeo
bfGeoToMaya.mayaMesh[0] >> cageMesh.inMesh
pm.delete(cageMesh, ch=True)
pm.delete(bfGraph.getParent())
pm.delete(mesh)
# Clean up cage mesh
largestArea = 0.0
try:
meshes = pm.polySeparate(cageMesh, ch=False)
# Find cage mesh in separated meshes
for mesh in meshes:
bb = meshes[0].boundingBox()
volumeArea = bb.width() * bb.height() * bb.depth()
if volumeArea > largestArea:
cageMesh = mesh
largestArea = volumeArea
pm.parent(cageMesh, w=True)
pm.delete(skinCageName)
cageMesh.rename(skinCageName)
except:
pass
pm.hyperShade(cageMesh, assign='lambert1')
# Retopologize
pm.polyRetopo(
cageMesh,
ch=True,
replaceOriginal=True,
preserveHardEdges=True,
topologyRegularity=1.0,
faceUniformity=0.0,
anisotropy=0.75,
targetFaceCountTolerance=10,
targetFaceCount=faceCount
)
convertToCageMesh(pm.selected()[0])