LoginSignup
23
12

More than 5 years have passed since last update.

アトラステクスチャから軽量なポリゴンを生成するツールの作成

Last updated at Posted at 2018-11-30

アトラステクスチャからカードポリゴン生成、描画に優しい形状へ変換、そしてHoudini 17で追加されたPython Viewer Statesを使用したPivot設定に関して解説します。

まずはどんなツールなのかを録画しましたのでご確認ください。
https://youtu.be/gcBinxr6f1I

アトラステクスチャからポリゴンへの流れ

1.テクスチャを読み込む
2.テクスチャの境界を調整
3.テクスチャをトレースしてポリゴンへ変換
4.生成されたポリゴンを矩形化
5.UVを生成
6.Pivotを使用して位置を調整

テクスチャを読み込み、テクスチャの境界を調整

テクスチャの読み込みはCOPを使用します。テクスチャの種類はOpacityだけでいいのですが、表示用にAlbedoも使用しています。ここでポイントになるのは DilateErodeノードを使用した輪郭の調整です。コントラスト等でもある程度調整可能ですが、例えば枯れた木の葉などでは小さな穴が開いている事もありますので、DilateErodeノードであれば小さな穴を埋める事ができます。
TEX2CARD_TEX.png

テクスチャをトレースしてポリゴンへ変換

テクスチャからポリゴンへの変換はTrace SOPで行います。またCOPから画像を参照するには op:パス名 を使用します。
op:`opfullpath("../image/OUT_OPAC")`

またTrace SOPで生成されたポリゴンの頂点数、頂点間隔の調整はカーブの調整でお馴染みのResample SOPを使用することでエッジの頂点を調整することができます。
TEX2CARD_TRACE.png

生成されたポリゴンを矩形化

テクスチャからポリゴン化したものをさらに矩形へ変換します。矩形への変換はforeachでPrimitive分ループして、矩形(Grid)をmatchsizeで各Primitiveの大きさにマッチさせていきます。これで矩形化は完了です。
TEX2CARD_CARD.png
ここからもうひと手間加えて、描画に優しい形状へ変換していきたいと思います。ここで言う描画に優しいとは?透明部分をできるだけ排除した形状を指します。当初Python SOPを使用して形状を変換していましたが、convexhullを2D化すればいいのでは?と思い、検証した結果、Shrinkwrap SOP, traiangulate2d SOPで実現できました。さらにPolyreduceとTrace後にエッジのresampleの組み合わせで軽量なデータへ変換できます。
TEX2CARD_OD.JPG

UVを生成

UVの生成は頂点位置がそのままUVに対応しているので、Wrangleで v@uv = @P; で完成です。
TEX2CARD_UV.png

Pivotを使用して位置を調整

Pivotは各矩形の中心を求めてPivotとし、Pivotが[0,0,0]になるように頂点を移動しています。またHoudini 17で搭載された新機能のPython Viewer Statesを使用して形状上の任意の位置にPivotを設定できる機能を実装しました。

Python Viewer States

Python Viewer Statesはビューポートのインタラクションを制御できる機能です。例えば回転ツールはViewer Statesの一つで、このような機能をPythonを使用して独自に実装できるのがPython Viewer Statesという事のようです。
※現在Python Viewer StatesはHDAからしか利用できません。
https://www.sidefx.com/docs/houdini/hom/python_states.html

Python Viewer Statesを使用してPivotツールを実装

Pivotツールの実装はHELPに記載されています、DrawPointsをほぼそのまま使用しています。
実装の概要は、
- Viewer Statesの動作を実装した DrawPointsクラス
- 登録に必要なViewerStateTemplateの生成
で構成されています。
これらの実装はHDAのScripts/Python Moduleへ設定しています。
TEX2CARD_SCRIPT.png

DrawPointsクラス

onMouseEventが発生すると、生成したポリゴンへ例を飛ばして、ヒットしたらその位置をAdd SOPへ追加する

from __future__ import print_function

import stateutils

class DrawPoints(object):    
    def __init__(self, state_name, scene_viewer):
        self.state_name = state_name
        self.scene_viewer = scene_viewer

        self._node = None
        self._pressed = False
        self._index = 0    

    def _point_count(self):
        multiparm = self._node.parm("points")
        # This is how you get the number of instances in a multiparm
        return multiparm.evalAsInt()

    def _insert_point(self):
        index = self._point_count()
        multiparm = self._node.parm("points")
        multiparm.insertMultiParmInstance(index)
        return index

    def _set(self, index, position):
        self._node.parm("usept%d" % index).set(1)
        self._node.parmTuple("pt%d" % index).set(position)

    def _start(self):
        if not self._pressed:
            self.scene_viewer.beginStateUndo("Add point")
            self._index = self._insert_point()
        self._pressed = True

    def _finish(self):
        if self._pressed:
            self.scene_viewer.endStateUndo()
        self._pressed = False

    def onMouseEvent(self, kwargs):
        node = self._node = kwargs["node"]
        ui_event = kwargs["ui_event"]
        device = ui_event.device()
        origin, direction = ui_event.ray()

        # Find intersection with geometry or ground
        intersected = -1
        cards = node.node("OUT")
        # Only try intersecting geometry if this node has input
        if cards:
            geometry = cards.geometry()
            intersected, position, _, _ = stateutils.sopGeometryIntersection(geometry, origin, direction)
        #if intersected < 0:
        #   position = stateutils.cplaneIntersection(self.scene_viewer, origin, direction)

        # Create/move point if LMB is down
        if device.isLeftButton():
            self._start()
            self._set(self._index, position)
        else:
            self._finish()

    def onInterrupt(self, kwargs):
        self._finish()

ViewerStateTemplate

ViwerStateTemplateは独自のViewerStatesを作成するためのテンプレートになるオブジェクトです。


# Grab a reference to the asset's node type
nodetype = kwargs['type']
# Use the node's name and label as the state name and label
state_name = nodetype.name() + ".pystate"
label = nodetype.description()
category = nodetype.category()
# Instantiate ViewerStateTemplate with the state name, label,
# and node type category
template = hou.ViewerStateTemplate(state_name, label, category)

# Mandatory binding
template.bindFactory(DrawPoints)
# Other optional bindings would go here... 1

Viewer Statesの登録と解放

ViewerStatesの登録、解放はPythonで実装する必要があります。それぞれ実装先は
登録はOnInstall
TEX2CARD_ONINSTALL.png

module = kwargs['type'].hdaModule()
hou.ui.registerViewerState(module.template)

解放はOnUninstall
TEX2CARD_ONUNINSTALL.png

state_name = kwargs['type'].name() + ".pystate"
hou.ui.unregisterViewerState(state_name)

おまけ

ここからHDAがDownloadできます。
※Houdini 17用ですので、17以前のバージョンでは動作しません。

23
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
12