노드로 시뮬레이션의 한계를 넘다: 옷 시뮬레이션 가볍게 하기
고민
프로젝트를 진행하면서 우리는 종종 옷 시뮬레이션의 문턱에 서곤한다.
이 과정에서 나타나는 고민들은 다양한데, 나는 그 중에서도
-
‘시물레이션 세팅의 값은 무엇인가?’,
-
‘어떻게 하면 가볍게 작업 할 수 있을까?’
두 가지의 주된 고민이었다.
오늘은 특히 가벼운 작업의 방법에 대해 작성해보려 한다.
당연히 이 방법이 모든 상황에 정답이 될 수는 없겠지만,
나만의 해결 방식을 기록해 보고자 한다.
Cloth Simulation
옷 시뮬레이션은 그 자체로 꽤나 무거운 작업이다.
이를 조금이라도 가벼워지게 하기 위해,
나는 low mesh에서 시물레이션을 하고,
high mesh는 warp 기능을 이용해 따라오게 하는 방식을 선호한다.
이 접근법은 high mesh에 직접 시물레이션을 적용하는 것보다 더 쾌적한 작업이 된다.
문제점
하지만 이 방법을 사용하면서도 한 가지 문제에 부딪힌다.
바로 warp 기능이 high mesh가 low mesh를 너무 정직하게 따라간다는 점이다.
딱딱한 high mesh
해결방안
이 문제를 해결하기 위해, 나는 warp이 적용된 high mesh에 직접 만든 smooth 디폼을 적용시켜봤다.
이 디폼은 메시의 모든 버텍스를 계산하기 때문에, 수축이나 디테일의 감소 같은 몇 가지 단점이 있다.
아래와 같은 기능을 조절해 단점을 극복하려 해봤다.
1. iterater를 이용한 Smooth 강도 조절
순차적으로 iterater 값을 3까지 올렸다.
2. Paint를 이용한 스무스 강도 조절
paint를 통해 smooth 노드의 전 값과 후 값을 0과 1 사이로 제어 할 수 있다.
모서리 수축을 제어 가능하다.
Deform 핵심 코드
def deform(self, data_block, geo_iter, matrix, multi_index):
envelope = data_block.inputValue(self.envelope).asFloat()
iterator = data_block.inputValue(self.iterator).asInt()
if envelope == 0 or iterator == 0:
return
input_handle = data_block.outputArrayValue(self.input)
input_handle.jumpToElement(multi_index)
input_element_handle = input_handle.outputValue()
input_geom = input_element_handle.child(self.inputGeom).asMesh()
mesh_fn = om.MFnMesh(input_geom)
prevIndex = om.MScriptUtil()
prevIndex.createFromInt(0)
vertices = om.MItMeshVertex(input_geom)
new_pt_local = []
geo_iter.reset()
while not geo_iter.isDone():
index = geo_iter.index()
vertices.setIndex(index, prevIndex.asIntPtr())
connectedVertices = om.MIntArray()
vertices.getConnectedVertices(connectedVertices)
new_pt_local.append(self.get_connected_vertices_position_avarge(mesh_fn, connectedVertices))
geo_iter.next()
new_pos = []
sub_pos = []
count = 0
while count < iterator:
# print(count)
geo_iter.reset()
while not geo_iter.isDone():
index = geo_iter.index()
if count == 0:
source_pt = geo_iter.position()
target_pt = new_pt_local[index]
else:
source_pt = new_pos[index]
vertices.setIndex(index, prevIndex.asIntPtr())
connectedVertices = om.MIntArray()
vertices.getConnectedVertices(connectedVertices)
length = connectedVertices.length()
sum_point = om.MPoint(0, 0, 0, 0)
for i in range(length):
point = new_pos[connectedVertices[i]]
sum_point.x += point.x
sum_point.y += point.y
sum_point.z += point.z
target_pt = om.MPoint(sum_point[0] / length, sum_point[1] / length, sum_point[2] / length)
source_weight = self.weightValue(data_block, multi_index, geo_iter.index())
final_pt = source_pt + ((target_pt - source_pt) * envelope * source_weight)
geo_iter.setPosition(final_pt)
geo_iter.next()
if count == 0:
new_pos.append(final_pt)
sub_pos.append(final_pt)
else:
sub_pos[index] = final_pt
for i in range(len(sub_pos)):
new_pos[i] = sub_pos[i]
count += 1
def get_connected_vertices_position_avarge(self, meshFn, connectedVertices):
length = connectedVertices.length()
sum_point = om.MPoint(0, 0, 0, 0)
connectedVerticesPosition = []
for i in range(length):
point = om.MPoint()
meshFn.getPoint(connectedVertices[i], point, om.MSpace.kObject)
connectedVerticesPosition.append(point)
sum_point.x += point.x
sum_point.y += point.y
sum_point.z += point.z
result = om.MPoint( sum_point[0] / length, sum_point[1] / length, sum_point[2] / length)
return result
cmds와 pymel 위주로 스크립트만 만들어 사용하다가, 처음으로 openMaya를 이용해 제작한 플러그인이다.
그래픽스를 공부해보기 시작하며, 강의의 맛보기 부분에서 배울 수 있었던 블러효과가 있었다.
단순히 왼쪽 오른쪽, 위 아래 픽셀의 색상의 평균을 구하는 박스 블러를 마야의 포인트들에 적용 시켜봤으며, 원하지 않는 부분을 제어 할 수 있는 Paint 기능과 함께 Deformer를 구성하게 되었다.
지금의 형태는 작동 하는데에 의미를 가지고 있다. 추후 코드 최적화 및 GPU가속까지 적용해 볼 계획이다.
이러한 노드를 활용함으로써, warp을 사용할 때 이 방법이 향후에도 유용하게 사용될 수 있으리라 기대한다.
비록 복잡하고 새로운 시도였지만, 이 경험이 앞으로 생각 하는 방식을 유연하게 해줄 것 같다.
댓글남기기