アバターの着せ替え手順紹介

アバターの着せ替え手順を紹介したいと思います
アバターというとVRMやVRChat向けなどいろいろあると思いますが、今回はどんなアバターにも使える汎用的な手法を紹介したいと思います
その分少しハードルは高いかも…

モデルはSUZURIさんの墨澄(スミスミ)ちゃんと

https://suzuri.jp/surisurikun/digital_products/53046?srsltid=AfmBOopg_itR2FitDycSTh7Qdd_83xhDGPyG5z_hA5nztOeqj06PWxEP

3BONさんのTech Mesh (for Female avatar)をお借りしました

https://booth.pm/ja/items/5848688

スミスミちゃんにこのズボンを履かせてみましょう

1. FBXで書き出します

用意したモデルたちをUnityで読み込みます
Unityには公式でFBX Exporterが用意されているのでこちらを利用します(Autodesk FBX SDKを利用しているのでそこそこ信頼性は高いです

2. 3dsMAXで読み込みます

3dsMAXかよ!!!と突っ込まれそうですが、Blenderでも何でも他のDCCツールでも構いません
残念ながら私が慣れ親しんだツールが3dsMAXだったというだけです…

とりあえず読み込んでみます
もちろんサイズは全然合いません

とりあえず難しいことは考えずにFFDで形を合わせていきます
いったんスキンウェイトのことは忘れましょう

ぐにょっと
股の高さも合わないのでこれも合わせます

足部分も
便利ですねFFD

これが終わったらスキニングです
スキンウェイトは元のものを維持しながらBoneを入れ子にしたりして、衣装とアバターを一体化する方法もありますが、Boneの位置が合わなかったりしてどんな形状のものでも好きに移植できるというわけではないので今回は汎用性の高いスキンのつけなおしを選んでいます
ちょっぴり面倒ですがね

3dsMAXにはSkinWrapという機能があります
既存のスキンを持ったオブジェクトを参照していい感じに補完して、スキンウェイトを設定してくれるというものです
今回はこれを「面変形」に設定し、参照先に元のスミスミちゃんのメッシュを割り当てました

これだけで動画のようにそれっぽく動きます
意外と簡単にできそうな気がしませんか?

最後はうまくいってない部分をちょちょっと修正します
修正するときにはあらかじめ足を折り曲げるようなアニメーションキーを設定しておくと便利です
折り曲げた状態を見ながらスキンウェイトを調整できます

さぁ調整していきましょう
SkinWrapをSkinに変換します

ウェイトツールを選び

「エンベロープを編集」→「頂点」にチェック

Boneを右のListから選択し、ウェイトを設定していきます

3dsMAXには他のDCCツール同様、ミラー機能もありますので片側だけ調整できたらさっと移植することもできます
「ミラー閾値」でどのくらいずれている頂点でも同一とみなすか設定できます

一通りスキンウェイトを調整して
Unityへエクスポートして、マテリアルをアサインしなおしたら…

可愛くてちょっとパンクなスミスミちゃんの完成です


【MAXScript】WPFをMAXScriptで使ってみる

Windows Presentation Foundation (WPF)のGUIをMAXScriptで使ってみよう企画?です。

ライブラリ(DLL)にしてMAXScriptでアクセスするという感じ

Visual Studioで普通にWPFアプリケーションを作成すると普通に.exeになってしまうので、ちょっと回り道をします。

「WPF ユーザーコントロール ライブラリ」として作成し
ウインドウを追加

あとはゴリゴリ書きたいことを書くだけ

今回はこんな感じ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace WpfControlLibrary1
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>

    public partial class Window1 : Window
    {
        private ObservableCollection<string> messages = new ObservableCollection<string>();
        private ObservableCollection<LvList> ListView_List = new ObservableCollection<LvList>();

        public Window1()
        {
            InitializeComponent();

            ListBox1.ItemsSource = messages;
            ListView1.ItemsSource = ListView_List;
        }

        public void AddItemToList(string mess) // MAX君から来たやつ
        {
            messages.Add(mess); // 突っ込む 
        }

        public void Fill_ListView(bool MAXcheck, string MAXName, string MAXState) // MAX君から来たやつ
        {
            LvList item = new LvList { Check = MAXcheck, Name = MAXName, State = MAXState }; // 追加する項目を作成
            ListView_List.Add(item); // Listに項目を追加

        }

        public class LvList
        {
            public bool Check { get; set; }
            public string Name { get; set; }
            public string State { get; set; }
        }

    }




}
<Window x:Class="WpfControlLibrary1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfControlLibrary1"
        mc:Ignorable="d"
        Title="Window1" Height="450" Width="800">
    <Grid>
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFA9FFBD" Offset="0"/>
                <GradientStop Color="Black" Offset="1"/>
            </LinearGradientBrush>
        </Grid.Background>
        <Grid Margin="10,10,10,9.6">
            <Grid.RowDefinitions>
                <RowDefinition Height="12*"/>
                <RowDefinition Height="10*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="10*"/>
                <ColumnDefinition Width="5*"/>
            </Grid.ColumnDefinitions>
            <Button x:Name="Button" Content="Button" x:FieldModifier="public" Margin="30.2,0,30,10.4" Grid.Column="1" Height="39" VerticalAlignment="Bottom" Background="#FF64BF68" Grid.Row="1"/>
            <ListBox x:Name="ListBox1" x:FieldModifier="public" Margin="10,10,10,10.4"/>
            <ListView x:Name="ListView1" x:FieldModifier="public" Margin="10,10,9.8,10.4" Grid.Row="1">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Width="28">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <CheckBox IsChecked="{Binding Check}" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="300" />
                        <GridViewColumn Header="State" DisplayMemberBinding="{Binding State}" Width="170"/>
                    </GridView>
                </ListView.View>
            </ListView>
        </Grid>
    </Grid>
</Window>
fn main =(
	dotnet.loadAssembly (getFilenamePath (getSourceFileName()) +@"WpfControlLibrary1.dll")
	global Form1 = dotnetObject "WpfControlLibrary1.Window1"
		
	Form1.topmost = true	
	Form1.Show()
		
	Form1.AddItemToList "test"
	Form1.Fill_ListView true "こんにちは" "OK!!"
		
	fn test =(
		Form1.AddItemToList "Test!!"
	)
		
	dotNet.addEventHandler Form1.button "Click" test
)

main()

動きました

MAXでアクセスしたいUI要素には x:FieldModifier="public" を付けないとアクセスできません。
dotNet.addEventHandler WpfControlLibrary1.Window1 "Click" Fn で普通にイベントハンドラを追加できます。

dotNet.setLifetimeControl ******** #dotnetを実行するとMAX側にイベントハンドラを渡せるようです。これしないとgc()でメモリからハンドラが消える。

:おまけ
IL DASM便利です。