見出し画像

[VRChat]Unityによるアバター改変② 軽量化編 ~前半~


はじめに

はじめにお断りしておきますがこの記事は長いです💦

まだまだ勉強中の身なのでいろいろと不足している点があると思いますのであらかじめご了承ください(アドバイスとか頂けると大変ありがたいですっ)。

下でも書かせていただいていますが個人としては「軽量化は必須ではない」と思っています。
ですが、知識の一つとして知っておいて損はないと思いますので、流し読みでも構いませんのでどうぞお付き合いください。

軽量化については「アバター改変なんもわからん集会 Coffee様」よりご指導いただきました。 本記事はそれを元に作成されています。
Coffeeさん並びにご助言いただいたみなさん、本当にありがとうございます!

軽量化は必要?

この記事を書いていて言うのもなんですが、個人的には程度にもよりますが「必須ではない」と思っています。
実際、私がアップロードしているアバターは全て軽量化されている訳ではなく、後述の最適化までのアバターと軽量化したアバターがあり、両仕様アップしたアバターもあれば片一方だけのアバターもあります。

VRChat公式より、今年(2024年)の夏頃のアップデートでアバター容量(ダウンロードサイズ)の制限値を下げることが発表されました。
アバター容量を500MB→200MBにするという変更のようです。
ギミックやパーティクル、装飾の多い衣装などをたくさん使用している場合は200MBを超えてしまうこともあるので制限変更後にアップロードするとき、あるいは現時点(変更前)で200MBを超えているアバターがある場合は確認や注意が必要になるかもしれません。

また、イベントに参加する時にアバターパフォーマンスランクの制限がある場合や、出来るだけ軽いアバターで来てくださいと言われることがあります。
そもそもの回線環境やVR環境(PCスペックやQuest単体など)、ワールド容量も関係あるのですが、大きな容量のアバターがそのワールド(インスタンス)に多数集まると表示が遅く(重く)なってしまったり、イベント進行そのものに影響がでてしまうことがあるためです。

それから、ユーザーの中には「イベント(他のユーザーも参加するインスタンス)に参加するなら軽いアバターにするべき」という意見をお持ちの方が一定数いらっしゃるのも事実で、場合によっては「重いアバターで来るな」と言われて悲しい思いをすることもあるかもしれません。

あくまで個人の意見ですが、やはり可愛い衣装や恰好良い衣装を着たり、綺麗なパーティクルやすごいギミックを組み込んだときなどは、他の人にも見てもらいたいと思うのは何もおかしいことではないと思っています。
実際、可愛い衣装やすごいギミックを見るのは楽しいです。

その一方で、すごい軽量化をしている方をお見掛けした時は素直にすごいなぁと感心します。

私としては可愛さや格好良さなどを出来るだけ残しつつ軽量化できたらいいなぁと模索の日々なのです。

どこまで軽量化するのか目標を立てましょう

軽量化は製作者様が意図したものを削っていく作業と言っても過言ではありません。

「ここはこういう風に動かそう」とか「ここは奇麗に見せたいからメッシュを細かくしよう」とか。
軽量化はこういった製作者様が実装してくださったものを無効化していく作業になります。

なので、どこまで軽量化するのかの目標を立てるようにしましょう。
数回やっていく内に段々軽量化後のイメージができるようになり、どこを重点的に軽量化していくのかのプランが立てられるようになるそうです(私はまだTrial&Errorの段階ですが…)。

私が軽量化するときに意識しているポイント

まだ未熟なのでえらそうなことは言えないのですが、軽量化するときに私が意識していることは次のことです。

  • 製作者様への感謝を忘れない

  • できるだけ素の状態を保つ

  • 妥協は必要

やはりふわっふわなのは良きなのです…

アバターパフォーマンスランクについて

VRChatでたま~に耳にする(かもしれない)「ベリプ」「グッド」などというのが(アバター)パフォーマンスランクです。
これは、VRChat公式で規定されているアバターのパラメータをランク分けしたもので、代表的な数値を抜粋すると以下の表のようになっています。
※抜粋項目原案:アバター改変なんもわからん集会スタッフ Coffee様

Performance Rank(抜粋版)

上の表で"Poor"の規定値を一つでも超えた場合に"Very Poor"、いわゆる「ベリプ」になる訳です。
なので、「ベリプはどこまで許容される?」という声を耳にしたことがあります。 厳しい言い方になりますが「ベリプはベリプ」なのです…

また、この表を見るとPCとQuest(Android)との規定値にかなり差があることがわかります。 PC版で "Excellent" にしても Polygons が引っかかってしまうのでQuest(Android)版では "Very Poor" になってしまいます。 これが「Quest対応は沼」などと言われている理由です。

最近のアバターや衣装などは、衣装を着せただけでもVery Poorになってしまうケースが増えているように感じます。
ただ、こういった衣装は本当に綺麗ですし、ふわっふわなんですよね~。
製作者様、ほんとすごいです。

さてさて、ではどのレベルを目標にすれば良いのか?という話になりますが、行きたいイベントで指定があればそのランクになりますし、特になく理想だけを言えば "Good" です。
しかし、表をご覧いただければお分かりのように軽量化作業開始時に "Very Poor" のものを "Good" にするのは相当厳しいことがほとんどです。
なので、まずは目標を "Midium" に設定してみましょう。

この記事では上の表にあるパラメータを調整していき、"PCでのMidiumランク" を目標として軽量化を進めていきます。

軽量化

ここからは各種ツールを使用していきます。
また、アバターの部位(パーツ)を非表示(非適用)にする作業も行っていきますので、それらは前編「[VRChat]Unityによるアバター改変① 下準備~衣装着替え時のポイント編」に記しておりますので、そちらを併せてご覧ください。

なお、Unityの表記言語は英語表記を使用しており、記事内のパラメータなども英語表記のもので記載されています。

アバターパフォーマンスランクの確認

VRChat SDKでもアバターパフォーマンスランクやパラメータは確認できますが、そこで表示されている数値と実際にVRChatにアップロードした時とで食い違いがあることが多いです。
これは、ツールで軽量化・最適化した効果がアップロード時に適用されるためで、VRChat SDKに表示されている数値などはアップロード前の状態でカウントされているためです。

なので、アップロード後での数値を確認しながら軽量化を進めていく方法をご紹介します。

パフォーマンスランク、パラメータ値を確認するために "anatawa12's gists pack"の機能を使用します。

ファイルメニューから "Tools" → "anatawa12's gist selector" を選択します。

anatawa12's gist selector

すると下のような図が表示されるので、 "ActualPerformanceWindow" を選択して "Apply Changes" をクリックします。
インポートが終わったら右上の「X」を押して閉じてしまって構いません。

ActualPerformanceWindowを選択

次に"VRCアバター/ワールド体重計"をインポートしてアバタールートを右クリックした時のリストから "WeightScale"を選択します。

アバター体重計

すると、"WeightScale"というウィンドウが表示されます。
私の場合はこのウィンドウを "Console" のところに並べています。
このウィンドウではアバターのダウンロードサイズの確認と、何が影響を与えているのかが確認できます。
※ウィンドウレイアウトについてはお好みで構いません。

アバター体重計の配置

ウィンドウ内容から、ダウンロードサイズは11.69MB、ダウンロードサイズに与えている影響が大きいのは素体のポリゴンと衣装や髪のテクスチャであることがわかります。
これはこの後の軽量化を行っていく上での参考になります。

ここまで準備出来たらいよいよパフォーマンスランクの確認を行います。
プレイモードに移ると、先ほど有効にした "Actual Performance Window" が表示されるので、パラメータなどが確認できます。
私の場合はこのウィンドウを、先ほどのWeightScaleと同じように "Console" のところに並べています。

今回はPC向けの軽量化を目標としているので、Actual Performance の  "Calculate Android" のチェックボックスは(脳が沼るので)オフにして構いません。

また、こちらのツールはプレイモードに入れば更新されます。

WeightScaleでの確認は、毎回アバタールートを右クリックしてWeightScaleを選択しなくてはならないのがちょっと面倒ですが、プレイモードに入らなくても確認できます。

Actual Performanceの配置

Avatar PerformanceやWeightScaleでパラメータを確認しつつ、VRChat SDKで対処したい項目を選択し、 Inspector と Hierarchy (上図にはありませんが左側に配置しています)を操作していく、という感じで進めていきます。

パラメータの結果を目標値に照らし合わせると下のようになります。
何をした結果、どのパラメータを軽量化していく必要があるのかがわかるようにExcelのような表計算アプリを使用しています。

目標値との比較(軽量化前)

表にもありますように、Step1~6に分けて軽量化を進めていきます。
記事では各ステップ毎に確認しているように受け止められますが、実際は適時確認しながら進めており、記載を省略させていただいています。

Step1 ~アバター最適化①~

非表示(非適用)にしているメッシュやPhys Boneなどを除外したり、BlendShapesでOFFにした部分をきれいに消したりする項目です。
軽量化というよりは変な不具合が発生しないように最適化する、というイメージで良いです。

衣装の揺れ物やメッシュ、テクスチャなどが製作者様の意図のままの状態でアップロードされるため、私はこのStep1と後述のStep2を全アバターに適用しています。

①アバターの棚卸し

アバターの表示/非表示の設定がきちんとできているかを確認します。
特に Inspector の "Tag" の設定の確認をしておきましょう。
ここが "EditorOnly"になっていると、Unity上で表示されていてもアップロード後に非表示になってしまいます。

また、EXメニューで使わないなぁと思った箇所も消します。
アバタールートを選択して "VRC Avatar Descriptor (Script)"の "Expressions"にある "Menu" と "Parameters" を編集します。
これに合わせて "Playable Layers" の "FX" も編集します。

Exメニューなどの編集

軽量化には直接関係ないかもしれませんが、場合によっては非表示にした衣装のON/OFFなどが含まれていることがあるので、確認だけでもしておくようにしましょう。
あとはExpressionsメニューをすっきりさせるという意味もあります。

衣装に入っているEXメニューがある場合は、そちらも確認および必要に応じて編集します。

EXメニュー(Expressionsメニュー)は、Hierarchyで "Gesture Manager" を選択した状態でプレイモードに入れば確認できます。

②BlendShapesの調整

衣装で隠れる箇所のBlendShapeをOFFにします。
今回は図の5ヶ所をOFFにしました。
腕は指先から袖内を見ると腕が見えるのでOFFにはしませんでした。

BlendShapeの調整

③AAO Trace and Optimizeの適用

この機能は非表示部位(パーツ)に対して、ポリゴンやPhys Boneなどを自動的に非適用にしてくれる非常に便利なものです。
しかもアニメーションなどでON/OFFが行われるものがUnity上で非表示になっていても非適用にはならないという優れものです。

適用方法は、アバタールートを選択して、"Inspector" の一番下にある "Add Component" を選択します。
ウィンドウが表示されるので虫眼鏡マーク(検索)欄に "aao" または "AAO" と入力して表示された "AAO Trace and Optimize" を選択します。
追加した後は特に設定を変える必要はありません。 そのままで大丈夫です。

AAO Trace and Optimizeの追加

④AAO Remove Mesh by BlendShapeの適用

②で調整したBlendShapesを完全に非表示にします。
実はBlendShapesでOFFにしてもVRChatではちょっと線(メッシュ)が残ってしまうので、それを完全に非表示(非適用)にします。

②でOFFにしたBlendShapeがある部位(パーツ)を選択して、"Add Component" から検索欄に "AAO" または "aao" と入力して表示されたリストから"AAO Remove Mesh By BlendShape"を選択します。
※③で検索欄に"aao"などと入力した場合は、そのまま残っているので入力しなおす必要はありません。

AAO Remove Mesh By BlendShapeの追加

表示されたチェックボックスに②でOFFにしたパラメータと同じものにチェックを入れます。
※BlendShapesでOFF設定にしなくても、こちらのチェックボックスにチェックが入っているとアップロード時にその部位(パーツ)は非表示になります。

Remove Mesh By BlendShapeのチェックボックス

AAO Remove Mesh By BlendShape には Preview(プレビュー)があるので、チェックボックスにチェックが終わったら Preview して確認しましょう。
Preview中にチェックボックスのON/OFFで表示/非表示が切り替えられるのでここで確認しつつ調整しても構いません。

注意する点は、BlendShapeの設定で0と100の間の値(50など)のパラメータをAAO Remove Mesh By BlendShapeの項目でチェックを入れてしまうと非表示(非適用)となってしまうことです。 0~100の間の数値としている場合はチェックは外しましょう。

これでStep1は終了です。
結果を見てみましょう

Step1実施後

スタート時と比べるとだいぶ数値が変化しているのがわかります。
AAO Trace and Optimize の効果が大半なので、軽量化する/しないにかかわらず適用しておいて損はないと思います。

Step2 ~アバター最適化②~

上の表をみると大分目標値に近づいていますが、もう1段階最適化します。
効果が期待できるのは、"Skinned Mesh"と"Material Slots"です。
上の表ではすでに目標達成しているのですが、やり方をご紹介します。

VRChat SDK Control Panelを使用しますのでウィンドウを表示およびログインしておいてください。

AAO Merge Skinned Meshの適用

①アバタールートを選択して右クリックしたメニューから"Create Empty”を
 選択します。 するとアバタールート直下(一番下)に"GameObject"が
 作成されます。

GameObjectの追加

② 作成されたGameObjectを選択して、名前を "Merge_Skinned_Mesh" など
 後で見てわかりやすい名前に変更しておきます。
 名前を変更したら少し右にある鍵マークをクリックしてロックしておきま
 す。 このロックを忘れると、この後の操作で "Inspector" に表示される
 項目が変わってしまいますので、忘れずにロックしておきましょう。

GameObjectの名前変更とロック

③アバタールートを選択して右クリックし、そこで表示されるリストから
 "Select Children"を選択するとアバタールートが展開されます。

アバタールートの展開

④"Inspector"にある "Add Component" をクリックして、検索欄に"aao"また
 は"AAO"と入力(この記事の上から順番に行っている方はすでに入力され
 ている状態になっていると思います)して、"AAO Merge Skinned Mesh"
 を選択します。

AAO Merge Skinned Meshを追加

"Inspector" に追加された "AAO Merge Skinned Mesh" にある "Skinned Renderers" を展開して "Element to add" を表示させておきます。

Skinned Renderersを展開

さらに"Add Component"で、"AAO Freeze BlendShapes" を選択(追加)します。

AAO Freeze BlendShapesの追加

⑤VRChat SDKのControl Panelから、"Builder" をクリックして "Validations"にある "Skinned Mesh Renderers" と書かれている欄の "Select" を押します。

VRChat SDK の Skinned Mesh Rendererを選択

すると、Hierarchyに対象となる部位(パーツ)が選択された状態になるので、①~②で作成したGameObjectと、Hierarchyにある"Body(顔パーツ)"を ctrlキーを押しながら選択解除します。
※Bodyを除外するのは、顔パーツまでMergeすると表情が変わる際に他の部位の動作まで計算するようになってしまい、逆にアバターが重くなってしまったり、思わぬ事故になるケースがあるためです。

顔パーツとGameObjectを除外

選択された部位(パーツ)を AAO Merge Skinned Mesh の "Skinned Renderers" の "Element0" にD&Dします。

Skinned RenderersにD&D

"Skinned Renderes" の欄に選択された部位(パーツ)が並んだことを確認できたら AAO Freeze BlendShapes の一番下にある "Check All" をクリックします。

AAO Freeze BlendShapes "Check All"

ここまで終了したら GameObject(この記事では "Merge_Skinned_Mesh)の上の方にある鍵マークをクリックしてロックを解除してください。

これでStep2は終了です。
それでは結果を見てみましょう。

Step2

Skinned Meshが3→2になりました。
先のStep1での効果が大きかったため、ここではわずかに変化した結果でした。 アバターや衣装によってはStep2でもっと効果が出るケースがありますので、Step1とセットでやっておくと良いと思います。

ここまでが衣装のテクスチャや揺れ感などを軽量化(削る)作業を行わない「最適化」の状態となります。

Step3 ~Phys Boneの軽量化~

これまでは機械的にツールを適用してきましたが、ここからはいろいろ検討しつつ適用していく作業となってきます。
つまり、「どこで妥協するか」というのが判断基準となってきます。

まずは「揺れもの(Phys Bone)」に着手します。

たとえばスカートのような衣装の場合、複数のPhys Boneが設定されており、それらがアバターの移動などの動きによって揺れます。
その揺れ方を設定しているのがPhys Boneだという認識で良いと思います。
また、スカートと足が当たった時にスカートを足に合わせて曲げる動作を行うのが Phys Bone Colliderです。

「アバター PhysBone」「PhysBone collider」などで検索すると詳細に説明してくださっている方がいらっしゃいますので、ご興味あればそちらをご覧ください。

①Phys Boneの確認

まずはどういった部位(パーツ)にPhys Boneが設定されているのかを確認します。

VRChat SDK Control Panel から "Phys Bone Component" の欄の "Select" をクリックすると、Hierarchy に Phys Boneが設定されている部位(パーツ)が選択されます。

Phys Bone ComponentをSelect

②Phys Boneを残すか消す(非適用)か

取捨選択の場面がやってきました。
この記事でご紹介している内容に沿う必要はなく、あくまで方法をご紹介するという認識でご覧ください。

Step2までの結果でPhys Bone Componentは43でしたので、これを16になるように今回は以下の方針としました。
()内の数字はPhys Boneの数です。

消す(非適用)判断をした部位(パーツ)
  アホ毛 (1)
  尻尾(1)
  素体の胸(2)
  服のリボン(1)

残す判断をした部位(パーツ)
  前髪(1)
  横髪(2)  
  後ろ髪1(2)
  後ろ髪2(1)
  耳(2)
  スカート(22)
  服の袖(8)

③Phys Boneを消す(非適用にする)

アホ毛を例にしてご紹介します。

対象となる部位(パーツ)を選択して、 Inspector にある "VRC Phys Bone (Script)" の項目のチェックを外します。

この状態でプレイモードに移行して動作確認し、Phys Boneを消しても良いかを判断します。

プレイモードをやめて、消すと判断した場合は "VRC Phys Bone (Script)" の右側にある点が縦に3つ並んでいる個所をクリックして "Remove Component" を選択します。

PB非適用

非適用の作業が終わりましたら、もう一度プレイモードでその部位(パーツ)自体が消えていないか、動かなくなっているかの確認をしましょう。

④Phys Boneを残す(統合する)

この作業は非常に時間がかかります。
スカートを例としてご紹介します。

この衣装のスカートのPhys Boneは "JapaneseMaidRe:SkirtPB" にぶら下がっています。 この内のA、B_L、B_Rを比較します。

スカート A, B_L, B_R のPhys Bone

四角で囲った部分のパラメータが同じ値であることにお気づきでしょうか?
実際に ctrlキーを押しながらこれらのパーツを選択すると以下のように違っている箇所が「ー」で表示されます。

スカート A, B_L, B_R を選択したとき

試しに上の状態から ctrlキーを押しながら E_R を加えてみます。
すると、"Max Yaw"の数値が "2" だったものが "ー" になりました。
確認してみると、E_R の Max Yaw の値は "5" だったので違う値が選択されたために「ー」となったことがわかります。

スカート E_R を加えたとき

このように、設定値が違うとその箇所が「ー」となるので、まずは同じ値のもの同士をまとめた場合に Phys Boneがどのくらいの数になるのかを試してみます。

なんとこのスカートは大きく2種類のPhys Bone設定値となっていました。
今回は思い切ってスカートのPhys Boneを2つに統合してしまいます。
(製作者様すごいっ)

まず、Step2 の Merge Skinned Mesh のときと同様にアバタールートを選択後に右クリックして "Create Empty" を選択します。
作成された GameObject を作成してロックします。

GameObjectを作ってロックする

次に、GameObject(図では "Skirt_PB_A-D" としています)の "Add Componet" をクリックしてリストから "AAO Merge PhysBone" を選択します。

AAO Merge PhysBoneを追加

今回統合するPhysBoneは、スカートの A~D なので、Hierarchyから対象となるパーツをctrlキーを押しながら選択(今回の場合は飛び飛びになっていないのでShiftでも大丈夫です)して、"AAO Merge PhysBone" にある "Element to add" にD&Dします。

統合するPhys BoneをD&D

統合したいPhys Boneが "Element" のところに入ると、Phys Bone の内容が表示されますので、ここで「ー」がないか確認します。 問題がなければロックを解除します。

統合されたPhys Bone

統合した元パーツの Phys Bone のチェックを外します。
このとき、Remove Componetなどはせずにそのままにしておくことが重要です(参照元がなくなることになるのでエラーが起きます)。

統合元のPhys Boneのチェックを外す

必ず動作確認をして統合後のイメージと同じになるかを確認してください。

他の統合できる Phys Bone を同じように統合して動作確認をします。

結果を見てみましょう。

Step3

Phys Bone Componentsは目標値を達成することができましたが、他のPhys Bone関連の数値が目標値に到達していません。
場合によってはこの後の作業でもPhys Bone関連の数値が達成できないことがあるので、その場合はこのStepに戻って再度検討します。

ですが、一旦現状の結果をアニメーションでご覧ください。


~ 同じ設定値のPhys Boneが無い(少ない)場合~

今回は同じ値のPhys Boneが多かったためそのまま統合できましたが、場合によっては同じ値ではないケースがあります(というよりこちらのケースの方が多いです)。

その場合、どうするかというと
  ・割り切ってPhys Boneを消す
  ・近しい設定値のPhys Boneを統合して調整する

という方法をとります。

Phys Boneを消す方法については上でご紹介しましたので、残った方のPhys Boneの調整方法についてですが、はっきりいって難しいです(挑戦しているのですが、毎回沼ってます)。

これにはPhys Boneの各パラメータについて知る必要があり、調整はそれこそ "Trial&Error(調整してはGesture Managerで確認する作業)" を繰り返すことになります(慣れてくればイメージからどのパラメータを調整すればよいかがわかってくるのでしょうが…)。

簡単に設定したいという方、または目安が欲しいという方は統合するPhys Boneのパラメータ値が違っている箇所の値のいいとこどり(平均値くらい?)で一度確認してみて大丈夫そうならそれを採用する、というやり方もアリだと思います。

Phys Boneの各パラメータについては、「unity PhysBone」などで検索すると詳しい説明をしてくださっている方がいらっしゃいますので、そちらをご覧いただくとして、ここでは簡単に各パラメータがどのような動きを設定しているのか簡単に触れておきます。

Force
・integration Type: 揺れ方の計算方法
   Simplfied: 安定性が高いが反応速度が遅い
   Advanced: 安定性は低いが反応速度が速い(デフォルトはこちら)
・Pull: 動いたときに元に戻るときの戻り方(減衰力)
・Momentum: バネっぽい動き方(弾力性)
・Stiffness: どのくらい元の形を保っているか(剛性)
・Gravity: 重力の影響度合(重力性)
・Gravity Falloff: 傾いていないときの重力の影響の無効度合
       (回転角による重力相殺)
・Immobile Type: 元の動作に対する動かなさのタイプ(不動性)
   All Motion: すべの動作を対象にする
   World(Experimental): VRChat上のジョイスティック移動を対象にする
・Immobile: 上のタイプの度合

Limits
・Limit Type: ボーンの動作範囲の指定方法
   None: 指定なし
   Angle: 軸を中心に円錐状に指定(2軸)
   Hige: 平面に沿って指定(1軸)
   Polar: 球面上に沿って指定(極座標)
・Max Angle: ボーンが曲がれる最大角度(前後/左右)
      ※Polar Typeでは設定できません
・Max Pitch: ボーンが曲がれる最大角度(回転方向)
・Max Yaw: 直角方向の最大角度(前後)
      ※Polar Typeのみ
・Rotation: Pitch(X方向)、Roll(Y方向)、Yaw(Z方向)の軸向き

Collision
・Radius: 接触(掴み)判定する範囲(球)の大きさ
・Allow Collision: 接触(掴み)判定の有無(True/Falseで設定)
・Colliders: アバター内で接触判定する要素(パーツ)

Stretch & Squish
・Stretch Motion: ボーンの伸び縮みの度合い
・Max Stretch: ボーンの最大伸び量
・Max Squish: ボーンの最小縮み量

Grab & Pose
・Allow Grabbing: VRChat内で掴めるかどうか(True/Falseで設定)
・Allow Posing: 掴んで位置固定ができるかどうか(True/Falseで設定)
・Grab Movement: 掴み動作をしたときに手に吸い付く速度
・Snap To Hand: 掴んだときにボーンが手に移動するかどうか

Options
・Parameter: Avatar3.0を使用する場合にコマンドを入力
・Is Animated: アニメーション再生中でもPhys Boneを動作させるかどうか
・Reset When Disabled: ボーンを自動的にリセットするかどうか

Gizmos
・Show Gizmos: SceneでPhys Boneを表示するかどうか
・Bone Opacity: Phys Bone表示時の透明具合
・Limit Opacity: Phis Bone可動範囲表示時の透明具合

後編に続きます。




この記事が気に入ったらサポートをしてみませんか?