HDT-SMPの揺れの設定を詳しく調べてみた

体型・装備

ヒラヒラ動くスカートの揺れ方を調整してみます。

今回はSimple Skirt (HDT-SMP PHYSICS)を使います。

設定ファイルの場所

設定ファイルは以下になります。

SKSE\Plugins\hdtSkinnedMeshConfigs\OK_Skirt.xml

今回はこのファイルがひとつだけなのでわかりやすいですが、たくさんある場合はメッシュ(nifファイル)に書かれている設定ファイルのパスを確認するといいです。

XMLファイルの編集について

XML形式とは、データの羅列を人間が読みやすいようにしたものです。とはいえ、そのまま読むのは少々辛いので、ツールを使いこなしましょう。

編集ツールはたくさんあると思いますが、Sublime Textのような色付けしてくれるテキストエディタを使うといいと思います。折り畳みで見やすくすることもできます。

機械的に整理すると読みやすくなります。無料で整理してくれるサイトもありますので活用します。

コメントアウトを使うと、設定を一時的に変更して元に戻すのが楽になります。しかもSublime Textなら色が付いてとてもわかりやすいです。

こういった便利なツールを使うようにして、メモ帳を使うのはやめましょう。

ゲームでの確認の仕方

設定ファイルを編集したら、Skyrimでコンソールを開いて以下のコマンドを実行すると反映されます。Skyrimを再起動する必要はありません。

smp reset

これを知っているだけで調整にかかる時間を大幅に短縮できます。

HDT SMP Windを使っている場合、まずは室内の無風状態で調整しましょう。できればHDT-SMPに切り替えるか、HDT SMP Windの設定ファイルでwind機能を無効にしましょう。

設定ファイルの基本

私が整理したSimple Skirt用設定ファイルです。以下の解説のひな型になります。

Simple SkirtのHDT-SMP設定ファイル (xml)

<?xml version="1.0" encoding="UTF-8"?>
<system xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="description.xsd">

    <!-- 以下のボーンは設定なし -->

    <!-- 身体のボーン -->
    <bone name="R Breast01"/>
    <bone name="L Breast01"/>
    <bone name="R Breast02"/>
    <bone name="L Breast02"/>
    <bone name="R Breast03"/>
    <bone name="L Breast03"/>
    <bone name="NPC L Breast"/>
    <bone name="NPC R Breast"/>
    <bone name="NPC L Breast01"/>
    <bone name="NPC R Breast01"/>
    <bone name="NPC L Clavicle [LClv]"/>
    <bone name="NPC R Clavicle [RClv]"/>
    <bone name="NPC L Forearm [LLar]"/>
    <bone name="NPC R Forearm [RLar]"/>
    <bone name="NPC L ForearmTwist1 [LLt1]"/>
    <bone name="NPC R ForearmTwist1 [RLt1]"/>
    <bone name="NPC L ForearmTwist2 [LLt2]"/>
    <bone name="NPC R ForearmTwist2 [RLt2]"/>
    <bone name="NPC L UpperArm [LUar]"/>
    <bone name="NPC R UpperArm [RUar]"/>
    <bone name="NPC L UpperarmTwist1 [LUt1]"/>
    <bone name="NPC R UpperarmTwist1 [RUt1]"/>
    <bone name="NPC L UpperarmTwist2 [LUt2]"/>
    <bone name="NPC R UpperarmTwist2 [RUt2]"/>
    <bone name="NPC L Thigh [LThg]"/>
    <bone name="NPC R Thigh [RThg]"/>
    <bone name="NPC L Calf [LClf]"/>
    <bone name="NPC R Calf [RClf]"/>
    <bone name="NPC R RearCalf [RrClf]"/>
    <bone name="NPC L RearCalf [LrClf]"/>
    <bone name="NPC L FrontThigh"/>
    <bone name="NPC R FrontThigh"/>
    <bone name="NPC L RearThigh"/>
    <bone name="NPC R RearThigh"/>
    <bone name="NPC L Butt"/>
    <bone name="NPC R Butt"/>
    <bone name="NPC L Pussy02"/>
    <bone name="NPC R Pussy02"/>
    <bone name="NPC L Hand [LHnd]"/>
    <bone name="NPC R Hand [RHnd]"/>
    <bone name="NPC RT Anus2"/>
    <bone name="NPC LT Anus2"/>
    <bone name="NPC LB Anus2"/>
    <bone name="NPC RB Anus2"/>
    <bone name="NPC Head [Head]"/>
    <bone name="NPC Pelvis [Pelv]"/>
    <bone name="NPC Spine [Spn0]"/>
    <bone name="NPC Spine2 [Spn2]"/>
    <bone name="NPC Spine1 [Spn1]"/>
    <bone name="NPC Belly"/>
    <bone name="Clitoral1"/>
    <bone name="VaginaDeep1"/>

    <!-- スカートのボーン(一番上の腰の部分) -->
    <bone name="Dress Left F 1" />
    <bone name="Dress Left B 1" />
    <bone name="Dress Right F 1" />
    <bone name="Dress Right B 1" />

    <!-- 以下のボーンの設定 -->
    <bone-default>
        <mass>2</mass>
        <inertia x="25" y="25" z="25" />
        <centerOfMassTransform>
            <basis x="0" y="0" z="0" w="1" />
            <origin x="0" y="0" z="0" />
        </centerOfMassTransform>
        <linearDamping>0.9</linearDamping>
        <angularDamping>0.9</angularDamping>
        <friction>0.1</friction>
        <rollingFriction>0.5</rollingFriction>
        <restitution>0.5</restitution>
        <margin-multiplier>1</margin-multiplier>
    </bone-default>

    <!-- スカートのボーン(2番目以降、裾にむかって2~5) -->
    <bone name="Dress Left F 2" />
    <bone name="Dress Left F 3" />
    <bone name="Dress Left F 4" />
    <bone name="Dress Left F 5" />
    <bone name="Dress Left B 2" />
    <bone name="Dress Left B 3" />
    <bone name="Dress Left B 4" />
    <bone name="Dress Left B 5" />
    <bone name="Dress Right F 2" />
    <bone name="Dress Right F 3" />
    <bone name="Dress Right F 4" />
    <bone name="Dress Right F 5" />
    <bone name="Dress Right B 2" />
    <bone name="Dress Right B 3" />
    <bone name="Dress Right B 4" />
    <bone name="Dress Right B 5" />

    <!-- 仮想地面のshape -->
    <per-triangle-shape name="VirtualGround">
        <margin>0.5</margin>
        <prenetration>10</prenetration>
        <tag>ground</tag>
        <no-collide-with-tag>ground</no-collide-with-tag>
    </per-triangle-shape>

    <!-- 太もも衝突判定用のshape -->
    <per-triangle-shape name="CollisionLegs">
        <margin>0.1</margin>
        <prenetration>0.5</prenetration>
        <tag>legs</tag>
        <no-collide-with-tag>legs</no-collide-with-tag>
    </per-triangle-shape>

    <!-- スカートのshape -->
    <per-triangle-shape name="skirt">
        <margin>0.1</margin>
        <prenetration>0</prenetration>
        <tag>skirt</tag>
        <no-collide-with-tag>skirt</no-collide-with-tag>
    </per-triangle-shape>

    <!-- 各ボーンの動きの設定 -->
    <generic-constraint-default>
        <frameInB>
            <basis x="0" y="0" z="0" w="1" />
            <origin x="0" y="0" z="0" />
        </frameInB>
        <useLinearReferenceFrameA>false</useLinearReferenceFrameA>
        <linearLowerLimit x="0" y="0" z="0" />
        <linearUpperLimit x="0" y="0" z="0" />

        <!-- 内向きの可動範囲 -->
        <angularLowerLimit x="-0.04" y="-0.04" z="-0.08" />
        <!-- 外向きの可動範囲 -->
        <angularUpperLimit x="0.04" y="0.26" z="0.08" />

        <linearStiffness x="0" y="0" z="0" />
        <angularStiffness x="0" y="0" z="0" />
        <linearDamping x="0" y="0" z="0" />
        <angularDamping x="0" y="0" z="0" />
        <linearEquilibrium x="0" y="0" z="0" />
        <angularEquilibrium x="0" y="0" z="0" />
    </generic-constraint-default>

    <constraint-group>
        <generic-constraint bodyA="Dress Left B 2" bodyB="Dress Left B 1" />
        <generic-constraint bodyA="Dress Left B 3" bodyB="Dress Left B 2" />
        <generic-constraint bodyA="Dress Left B 4" bodyB="Dress Left B 3" />
        <generic-constraint bodyA="Dress Left B 5" bodyB="Dress Left B 4" />
    </constraint-group>

    <constraint-group>
        <generic-constraint bodyA="Dress Right B 2" bodyB="Dress Right B 1" />
        <generic-constraint bodyA="Dress Right B 3" bodyB="Dress Right B 2" />
        <generic-constraint bodyA="Dress Right B 4" bodyB="Dress Right B 3" />
        <generic-constraint bodyA="Dress Right B 5" bodyB="Dress Right B 4" />
    </constraint-group>

    <constraint-group>
        <generic-constraint bodyA="Dress Left F 2" bodyB="Dress Left F 1" />
        <generic-constraint bodyA="Dress Left F 3" bodyB="Dress Left F 2" />
        <generic-constraint bodyA="Dress Left F 4" bodyB="Dress Left F 3" />
        <generic-constraint bodyA="Dress Left F 5" bodyB="Dress Left F 4" />
    </constraint-group>

    <constraint-group>
        <generic-constraint bodyA="Dress Right F 2" bodyB="Dress Right F 1" />
        <generic-constraint bodyA="Dress Right F 3" bodyB="Dress Right F 2" />
        <generic-constraint bodyA="Dress Right F 4" bodyB="Dress Right F 3" />
        <generic-constraint bodyA="Dress Right F 5" bodyB="Dress Right F 4" />
    </constraint-group>

</system>

bone要素とbone-default要素には順番があります。

  • bone-default要素を定義するとそれ以降に置いたbone要素に反映されます。
  • bone-default要素を定義する前はbone-default要素の個々の値は0になっているようです。
  • bone-default要素を定義する前に置いたbone要素は、個々の値が0に設定されるようです。
  • 定義しなかったbone要素は内容不定(HDT-SMP初期値?)となり、挙動がおかしくなります。

装備のメッシュが持っているbone要素に関してはbone要素を必ず定義しておかないとならないということです。装備のメッシュが持っているbone要素というのは、要するにWeightを塗ったboneということです。例えばCollisionLegsというshapeにCBBE 3BBBの太もものweightを塗って動きを反映させようとした場合、太もものboneの定義を追加する必要があるということです。

使っていないbone要素の定義は単に無視されるだけのようですので、CBBEとCBBE 3BBBのboneを全て列挙しておけばいいと思います。

個々の要素が0のboneを何と呼ぶのかわからないので、仮にall zeroed boneと呼ぶとします。何かしらの値を設定したboneをweighted boneと呼ぶとします。すると、HDT-SMPにおけるboneは大きく分けて3つになります。

  • 動かないbone (all zeroed bone)
  • 動くbone (weighted bone)
  • よくわからないbone (未定義のbone)

動かないboneはHDT-SMPでは動かないので、HDT-SMP以外で制御されます。

動くboneはHDT-SMPによる物理法則が適用され、重力や慣性が働くようになります。つまり、地面に向かって落ちていくわけです。リンゴを動くboneにしたら枝と連結しないと地面に落ちてしまうわけです。この連結がgeneric-constraintになります。

generic-constraint要素とgeneric-constraint-default要素にも順番があります。

よくある不具合として、地面に向かってビローンと落ちていく現象があります。以下の点を確認します。

  • bone要素を定義していない。(未定義のbone)
  • 動くboneとして定義したのにgeneric-constraint要素を定義していない。
  • bone要素を定義する位置を間違えている。(動かないboneとして定義したつもりが結果的に動くboneになってしまっている)

重さをかえる

重さはboneのMassになります。丸い物体なら、Massが小さいと風船のようになり、大きいとボーリング玉のようになります。重さが動き方にそのまま反映されますので、理想の動きをする値に設定しましょう。

スカートのMassは初期値が2になっていました。

<bone-default>
    <mass>2</mass>

スカートが動くタイミングは2つあります。プレイヤーが動いた時と風が吹いたとき(HDT SMP Wind)です。

0.1まで下げると、風が吹いているだけでめくれっぱなしになります。

大きくするとあまり揺れなくなります。重くしすぎるとコリジョンと衝突した時に跳ね返らなくなるので、しゃがんだ時にスカートが太ももにめり込みます。風の影響も小さくなりますので、風で揺らめてほしいなら1.0~2.0の間で調整すればいいのかなと思います。

bone-default要素にMassを書いたので、bone-default要素の後に置いたbone要素に適用されます。個々のbone要素でMassを個別に定義すればボーン毎にMassを設定できると思いますが、そこまでやるのは面倒だったのでやりませんでした。

動く範囲をかえる

動き方はgeneric-constraintで決めます。2つあります。移動(linear)と回転(angular)です。Lowerが下限でUpperが上限です。

Lowerがマイナス方向で、Upperがプラス方向です。逆にすると正常に動作しませんので注意してください。

デフォルトはこんな感じです。

<generic-constraint-default>
    <linearLowerLimit x="0" y="0" z="0" />
    <linearUpperLimit x="0" y="0" z="0" />
    <angularLowerLimit x="-0.04" y="-0.04" z="-0.08" />
    <angularUpperLimit x="0.04" y="0.26" z="0.08" />

移動が0で回転だけで動きを表現しているようです。試しに移動をありにして回転を0にしてみましたが、まともに動きませんでした。

y軸のUpperだけが少し大きくなっています。これがスカートが外側に向かって動く範囲になります。

x軸とz軸もわずかに数値が設定されていますが、おそらくはわずかな揺れの動きを実現するためと思います。

さて、私が一番調整したかったのが、めくりあがる高さです。デフォルトの0.26だとめくれすぎると感じます。1まで上げると思いっきりめくれます。0.1であまりあがらなくなりました。

ところが、後ろ側はいいのですが、前もめくれなくなるので、スニークでしゃがんだ時にスカートが太ももに食い込んでしまいます。むしろ、デフォルトの0.26ですら少し食い込んでいました。0.5くらいにあげると良くなりました。でも前に合わせて調整すると、今度は後ろ側がめくれすぎてしまいます。

これはgeneric-constraint-defaultが各ボーンのデフォルト値、つまり前も後ろもgeneric-constraint-defaultの値になっているからです。よって、前と後ろ、上と下でそれぞれ設定をわければいいのです。

ボーン名のLeftが左側でRightが右側、Fが前側でBが後ろ側です。それぞれ高さごとに5個あるので、合計20個のボーンで構成されています。ただし1番は一番上の腰にもっとも近い動かない部分になるので、設定をするのは2~5番です。

<generic-constraint-default>
	<frameInB>
		<basis x="0" y="0" z="0" w="1" />
		<origin x="0" y="0" z="0" />
	</frameInB>
	<useLinearReferenceFrameA>false</useLinearReferenceFrameA>
	<linearLowerLimit x="0" y="0" z="0" />
	<linearUpperLimit x="0" y="0" z="0" />

	<!-- 内向きの可動範囲 -->
	<!--<angularLowerLimit x="-0.04" y="-0.04" z="-0.08" />-->
	<angularLowerLimit x="-0.04" y="-0.5" z="-0.08" />
	<!--このようにコメントアウトして残しておけば初期値と比較できて元に戻すのも楽になる-->
	<!-- 外向きの可動範囲 -->
	<!--<angularUpperLimit x="0.04" y="0.26" z="0.08" />-->
	<angularUpperLimit x="0.04" y="0.5" z="0.08" />

	<linearStiffness x="0" y="0" z="0" />
	<angularStiffness x="0" y="0" z="0" />
	<linearDamping x="0" y="0" z="0" />
	<angularDamping x="0" y="0" z="0" />
	<linearEquilibrium x="0" y="0" z="0" />
	<angularEquilibrium x="0" y="0" z="0" />
</generic-constraint-default>

<constraint-group>
	<generic-constraint bodyA="Dress Left B 2" bodyB="Dress Left B 1">
		<angularLowerLimit x="-0.04" y="0" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.1" z="0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Left B 3" bodyB="Dress Left B 2">
		<angularLowerLimit x="-0.04" y="-0.05" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.1" z="0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Left B 4" bodyB="Dress Left B 3">
		<angularLowerLimit x="-0.04" y="-0.2" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.05" z="0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Left B 5" bodyB="Dress Left B 4">
		<angularLowerLimit x="-0.04" y="-0.3" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.05" z="0.08" />
	</generic-constraint>
</constraint-group>

<constraint-group>
	<generic-constraint bodyA="Dress Right B 2" bodyB="Dress Right B 1">
		<angularLowerLimit x="-0.04" y="0" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.1" z="0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Right B 3" bodyB="Dress Right B 2">
		<angularLowerLimit x="-0.04" y="-0.05" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.1" z="0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Right B 4" bodyB="Dress Right B 3">
		<angularLowerLimit x="-0.04" y="-0.2" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.05" z="0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Right B 5" bodyB="Dress Right B 4">
		<angularLowerLimit x="-0.04" y="-0.3" z="-0.08" />
		<angularUpperLimit x="0.04" y="0.05" z="0.08" />
	</generic-constraint>
</constraint-group>

<constraint-group>
	<generic-constraint bodyA="Dress Left F 2" bodyB="Dress Left F 1">
		<angularLowerLimit x="-0.04" y="0" z="-0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Left F 3" bodyB="Dress Left F 2" />
	<generic-constraint bodyA="Dress Left F 4" bodyB="Dress Left F 3" />
	<generic-constraint bodyA="Dress Left F 5" bodyB="Dress Left F 4" />
</constraint-group>

<constraint-group>
	<generic-constraint bodyA="Dress Right F 2" bodyB="Dress Right F 1">
		<angularLowerLimit x="-0.04" y="0" z="-0.08" />
	</generic-constraint>
	<generic-constraint bodyA="Dress Right F 3" bodyB="Dress Right F 2" />
	<generic-constraint bodyA="Dress Right F 4" bodyB="Dress Right F 3" />
	<generic-constraint bodyA="Dress Right F 5" bodyB="Dress Right F 4" />
</constraint-group>

衝突(コリジョン)の範囲をかえる

太ももの周りに衝突判定を加えるShapeがあります。これが太すぎるので、しゃがんだ時などにスカートが浮いてしまいます。なぜこうしてあるのかは不明ですが、おそらく立った状態でもある程度は広がるようにするためなのかもしれません。

へこましツールで太ももの形状と同じになるまで縮めたら、しゃがんでもスカートが浮かなくなりました。

逆にもっと浮かせたいのであれば、marginを大きくすればいいです。コリジョンというのは、目に見えるshapeの周りに目に見えないバリアがあるというイメージです。marginを大きくするとバリアの範囲が広がるので、shapeとshapeの間に出来る隙間が大きくなります。

<per-triangle-shape name="CollisionLegs">
    <margin>0.1</margin>
    <prenetration>0.5</prenetration>
    <shared>private</shared>
    <tag>legs</tag>
    <no-collide-with-tag>legs</no-collide-with-tag>
</per-triangle-shape>

動きに関する原則

まず、shapeは可動範囲を超えて動きません。例えば、可動範囲を0に設定すると動かないという意味になりますので、コリジョンの範囲をいくら調整してもshapeは動きません。

そして、可動範囲の中でコリジョンの衝突判定が行われます。

スニークした時にスカートの前の部分が太ももに食い込んでしまうケースを考えてみます。

まず、スカートの前の部分の外向きにたいする可動範囲を大きくします。そもそも可動範囲が狭ければスカートが十分な高さまで上がらないので、どうしても太ももに食い込んでしまいます。

それから太ももにあるコリジョンとの衝突範囲を調整します。

負荷を軽減する方法

HDT-SMPはCBPCと比べて負荷が高いと言われます。理由はHDT-SMPの仕事量が多いのでCPUをより多く使うからです。

HDT-SMPの仕事量が多くなってしまう原因はいくつかあります。

まず、ゲーム内で処理するShapeが多いほど仕事も増えます。SMPの装備はプレイヤーやフォロワーだけにして、その他のNPCには持たせないようにします。またはSMP版とCBPC版を用意して使い分けると良いでしょう。

それから、コリジョンはCPUをたくさん使います。ShapeとShapeが干渉したときに衝突判定による動きの制御を行うためです。つまり、常にShapeが衝突していると常に重くなってしまうのです。スカートは普段は太ももから離しておき、揺れた時に衝突するようにします。

仮想地面はスカートが地面に食い込まないようにするためにあるのだと思いますが、Actorが寝ているときや死んで倒れているときはずっと衝突し続けるため、あえて仮想地面を使わないのも手です。

コリジョンはper-triangle-shapeとper-vertex-shapeがあります。vertexがvertexに衝突、もしくはvertexがtriangleに衝突するのが負荷の面では良いようです。逆にtriangle同士、あるいはtriangleがvertexに衝突するのは良くないということになります。上記のサンプルXMLではすべてtriangleで定義していますが、これはvertexを加えるとうまく衝突しなくなったためです。

思わぬ衝突に気をつける

設定を間違えると、コリジョンが常に発生し続けて大変なことになります。

sharedにprivateを指定すると、このメッシュ内のshape同士でのみ処理が行われるようです。publicを指定すると、すべてのメッシュ同士での処理になるようです。

<per-vertex-shape name="JourneymanSkirt">
	<margin>1.0</margin>
	<shared>private</shared>
	<tag>skirt</tag>
</per-vertex-shape>

配布されている設定ファイルを眺めていると、privateだったりinternalだったりします。同じ意味のようですが、違いはわかりません。

設定のコツ

設定に不備があると、以下のログにその旨が書き込まれます。

My Games¥Skyrim Special Edition¥SKSE¥hdtSMP64.log

例えばdoesn't exist, skippedと書かれていたら「意味がわからないので無視しました」ということなので、何が無視されたのか、その結果は問題なのか、といったことを確認すればいいです。

prenetration - unknown elementと書かれていたら「そんな要素は知りません」ということなので、スペルミスや書く位置を確認します。ちなみにprenetrationは英語としては間違っていて、正しくはpenetration(浸透)ですが、HDT-SMPのプログラム側のバグで、prenetrationでないと認識されないようです。それからprenetrationはper-triangle-shapeの中でしか使えませんので、per-vertex-shapeの中で書くとエラーになります。

unknown value in sharedは中に書く値を間違えているということです。例えば<shared>Internal</shared>は間違いで、正しくは<shared>internal</shared>になります。大文字小文字の違いも見ているので注意します。

priority is deprecatedは「priorityという要素はもうサポートされていないので無視します」ということなので、単に削除すればいいです。

理想はエラーや警告が0になることですが、私はエラーだけ直して警告は面倒なので放置しています。

タイトルとURLをコピーしました