そんなに頑張らないアバター改変 ~ mstdn.maud.io Advent Calendar 2024

これは「mstdn.maud.io Advent Calendar 2024」1日目の記事です。

去年はRadeonの話をしました。今も状況はそんなに変わらないけど、古すぎる情報はちょっとだけ書き直ししてあります。

直近のアバター改変

Windows XP

普段はライムちゃんを使っていますが、最近新しく迎えて使っている子がいます。

 

だーれだ。

VRChatに入り浸っている皆さんなら、ひと目見ればお分かりですよね。セレスティアちゃんです。

オリジナル3Dモデル「セレスティア」ver1.02 - STUDIO JINGO - BOOTH
オリジナル3Dモデル「セレスティア」 製作者: 진권/ジンゴ このモデルはBlender 2.83で製作されました。 他のプログラムでの互換性は保障いたしません。

ここのところセールラッシュで色んなアバターをお迎えするいい機会となっています。マヌカちゃんは既に持っているので、ジンゴアバターでピンときたのはセレスティアちゃんでした。手癖でサクッと改変していった話をしようと思います。

髪型を変えてみる

ライムちゃんのときはデフォルトと似たような髪型にしていましたが、今回は大きく変更してみます。とはいえ手持ちの髪型はとても少ないです。

アリスティアちゃんの髪型である「お嬢様ヘア」を使って、「セレスティアちゃんのセレスとアリスティアちゃんのティアを合わせてセレスティアや!!」とかいう、しょうもないカドカワ方式のギャグをやろうかと思いましたがやめました。

BOOTH | お探しの商品が見つかりませんでした… (404)

で、なんやかんやでセレスティア対応済みの「風鈴レモネードHair」に落ち着きました。非対応髪型をUnity上で全部済ませようとすると、大抵うなじの辺りが形状合わなくてBlender案件になりがちですね。

風鈴レモネードHair - みかんずめいど - BOOTH
こちらは髪型のみの販売となっており、アバターは付属していません

色を変えてみる

シアンちゃんの色を考えていたときは、クリスタでざっくりトレスして好きなように色を塗って考えてました。スクショ切り抜くの面倒くさいので描いた方が早い(?)

自由に描き足せる利点はありますが、あんまりやってません。

シアンちゃんの色を考えていたときのやつ

今回は頭を使いたくなかったので、いつも通り無彩色に。ホントなにも考えてないと白か黒になりがち。

自分のイメージカラーが銀髪緑目で定着してしまった(?)事もあり、瞳の色を同梱のPSDファイル内のプリセットから緑色に変更します。

うーん、なんか足りない。

テクスチャを描いてみよう

とりあえず瞳テクスチャだけ手描きしました。デフォルトの白フチ部分以外はいつものノリで描いていきます。肌とかその他は割とそのまんま。

自作するといつも通りになるというか、いくらか個性が出る気がします。「目は口程に物を言う」というやつですね!(そうか?)

なんか記事書いてるうちに気になってきたので、描き直しなどの微調整をしました。さぁ、どこが変わったでしょうか。いやごめん、自分でも分からん。

ロングヘアじゃないのでPhysBoneが少なく、パフォーマンスランクはMediumと結構軽めな感じです。あれ、これMobile向けにも対応できるんじゃ?

できる!Mobile対応

できる!(どのアバターでもできるとは言ってない)

VRChatのQuestやPICO単体版でもアップロードしたアバターで遊べるようにするには、かなりキツめの制約をクリアする必要があります。昨今のリッチなアバターで対応させるのはかなり面倒です。最初からMobile対応してるアバター以外でやるもんじゃないぞ。(やるけど)

Performance Ranks | VRChat Creation
The Avatar Performance Ranking System allows you to see how much a user's avatar is affecting performance via analysis o...

Mobile対応したらついでにFallbackアバターも作ってしまいましょう。Fallbackの条件はPCとMobileそれぞれのプラットフォームで「GoodかExcellentのパフォーマンスランク」である必要があります。

揺れものが少ないなら、ポリゴン数さえ減らせば勝ったようなもん

PhysBoneが少ないと楽なので、モカ・モードを着せた状態で作ってみることにします。髪もロングヘアじゃない方が楽ですね。

【18アバター対応】Mocha mode -モカ・モード- - HINO shop - BOOTH
Full set版は対応アバターの増加に伴って価格が変更される場合がございます。Full set版をご購入済みの方は、追加分もDLできます。 ※商品画像は実際の見た目とは異なる場合がございます。 ※アバターは付属しておりません。

とりあえず現在のポリゴン数を確認。

16052
身体30516
後ろ髪2931
前髪5597
髪飾り3024
ポニーテール4198
ビスチェ1288
シャツ8828
パンツ3948
パンプス1900
ヘイロー6912
合計85194

わぁ、8万ポリゴン以上。Mobile向けではExcellentが7500でGoodが1万までの制限があります。1/10に削減するのは無理があるって?いやぁ、行ける行ける。

とはいえ、1万ポリゴンに収める予定なのでかなり削る必要があります。髪飾りとビスチェとヘイローはなくしてしまいましょう。

まずはAAO Remove Mesh in Boxなどで服に隠れている身体部分を消しておきます。記事を書いているときに足の部分を攻めすぎた事に気が付きましたので、しれっと直しておきます。

アクセサリーや見えない部分などある程度減らしたら、残りはMantis LOD Editor(のNDMF化ツール)を使って、ゴッソリ削っていきます。

Mantis LOD Editor - Professional Edition | Modeling | Unity Asset Store
Get the Mantis LOD Editor - Professional Edition package from Mesh Online and speed up your game development process. Fi...
【無料】【非破壊でポリゴン削減】Mantis LOD EditorのNDMF化ツール - ひつぶの倉庫 - BOOTH
非破壊でポリゴンを削れるツールです。 使うには有料アセットのMantis LOD Editorが必要です。 ・分かる人向け説明 Mantis LOD EditorをNDMF(なでもふ)で動くようにしたものです。 Unity2019、Unit...

無料で使えるUnityMeshSimplifierという手もありますが、Mantisのがまだ形を保ったままポリゴン数を減らせます。あと、Blender上でデシメートしようとするとシェイプキーがあって処理できないので、Unity上でやる方が楽です。

しかしハイポリなモデルからローポリのモデルを生成すべきではない、それはそう……。ホントは最初からローポリのモデルを作るのが正解だと思います。

VRChatのMobile用シェーダーは透過が使えないので、頬染めや青ざめ、場合によってはハート目やぐるぐる目なども削除しておく必要があります。それらはAAO Remove Mesh By BlendShapeでゴッソリ削れます。AAO便利すぎか?

また、Mobile用シェーダーは裏面も描画されないので、スカートとかはメッシュを張っておかないと裏が透明になってしまいます。今回、ひらひらとした袖を特に処理せず手抜きしたので見栄えが悪くなっています。Blenderとかで大きくカットしてしまう事も考えましたが、そんなに頑張らないことにしたので妥協です。

2351
身体2037
後ろ髪1101
前髪1883
ポニーテール616
シャツ1309
パンツ362
パンプス328
合計9987

あとはAAO Trace And Optimizeにお任せしてしまい、TexTransToolで1マテリアル1テクスチャにします。ギミックなども組み込まないので、自動統合されなかったメッシュは手動で1メッシュに統合してあります。(※衣装のオンオフなどの入ったメニュー類には手をつけなかったので、Trace And Optimizeでの統合対象にならない。)

Introduction
AAO: Avatar Optimizer # anatawa12によるアバター軽量化用のちょっとした非破壊ユーティリティ群です。 これらのユーティリティはPlayモードに入るときかアバターをビルドするときに適用されます。Avatar Op...
ホーム | TexTransTool
TexTransTool Document Home

ロングヘアでもスカートでもないので、使われていないボーンをAAOが自動で削減してくれるだけで十分となり、気合いでボーン数を減らす手間が省けました。ボーンを減らすには統合していくらか間引く感じになるので、動きも悪くなるんですよね。

パフォーマンスランクは無事Goodで収まっています。ライムちゃんで同じ事を試したらガビガビで滅茶苦茶な出来になったので、PC用しか想定してないアバターでこんなこと挑戦するのはお勧めしません。

Android用アップロードの際にフォールバックとして設定できるぞ

Androidプラットフォーム用に上げるときは色々と怒られてアップロードが出来ないと思うので、Manual Bake Avatarしてすべて処理済みのデータを作っておきます。パフォーマンスランクがGoodまでに収まれば、同じブループリントIDでWindowsプラットフォーム用にも上げておくと「Set this avatar as fallback」を押してカスタムフォールバックアバターとして使えます。

Mobile用をそのまま上げると物足りなさすぎるので、PC用のフォールバックは3万ポリゴンぐらいまで制限を緩めても大丈夫です。制限が緩くなる分、PCの方はパフォーマンスランクExcellentで上げることも余裕でしょう。

動作を確認

ポリゴン数を減らすと顔面が大変なことになりがちですが、意外と大丈夫でしたね。指とかも動かしたときに壊れやすいのですが、許容できるレベルです。

最近のVRChatにはインポスター機能もありますけど、やっぱちゃんと顔が見えた方が良いですよね。

しかし、この感じだとシャツだけローポリで新規に作って着せ直した方が……。ボタンも潰れております。じゃあ完全新規に1300ポリゴン以内で作れますかと言われると、Blender力が……。

ここまでやっても非破壊編集なので、差し換えなども容易です。数年前と比べると、驚くほど融通が利くようになったなぁと思いました。

無印のPICO 4で軽くテスト。

頑張らないって言ったじゃないすか!やだーーーー!

やっぱ作るか、シャツ……!

ねぇ、頑張らないって言ったよね??ねぇ????

大まかな形とサイズはモカ・モードを参考にしつつ、Blenderで4時間ぐらいかけて全部新規に作りました。自分用なのでウェイトはそのまま転送して済ませています。1,386ポリゴンだけど、多分Mantisで調整が利くはず。アドカレ書きながらやる事じゃないぞ。

き、着せた。無事着られた……。

裏面も対処済み!ヨシッ!

動作も大丈夫そうです。

シフォンシフォンシフォン……

Post by @pikepikeid@mstdn.maud.io
View on Mastodon

パイモンのグルメツアー帰りにポチり。瞳テクスチャ自作してライムちゃん用に使ってる髪に変えて、あまり使わない色を使って……。

おや、これは。やや青髪赤目だ。

シフォンちゃんのデフォルトだと幼すぎるかなと感じたので、若干大人びた感じにしてあります。輪郭をシュッとさせて、目元も少し上に上げてあります。まつげとかも減らしてスッキリとした雰囲気になってます。かわいい系だといつも通りなんですが、かわいいからは逃れられない……!

目元ぱっちりは被るので、ちょっとジトッとした感じで弄っていこうかなと考えてます。

髪型もおさげ髪にしてみました。

それとも、こうか?

暗めの所だと黒髪に見えるので、もう少し明るい青にしてもいいかな?

頑張ってにらめっこしてるとわけがわからなくなってしまうので、今後ものんびり気楽に改変していきたいですね。フォールバックアバターも一度整ったら当面はそのままで済みそうです。

いやぁ、この短い期間で常用アバターが増えたため、ライム・シフォン・セレスティアを行ったり来たりで忙しいVRChatになりましたね。皆さんはどの子が好きですか?

以上、mstdn.maud.io Advent Calendar 2024 1日目でした。明日は8mitsuさんです。

Stable Diffusion XLで散々遊んでみた結果
この記事は mstdn.maud.io Advent Calendar 2024 2日目の記事です。NSFWな…
");const o=Ee?Ee.createHTML(e):e;if(St===At)try{t=(new pe).parseFromString(o,wt)}catch(e){}if(!t||!t.documentElement){t=Oe.createDocument(St,"template",null);try{t.documentElement.innerHTML=Et?Ne:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),St===At?we.call(t,et?"html":"body")[0]:et?t.documentElement:i},Ht=function(e){return De.call(e.ownerDocument||e,e,k.SHOW_ELEMENT|k.SHOW_COMMENT|k.SHOW_TEXT|k.SHOW_PROCESSING_INSTRUCTION|k.SHOW_CDATA_SECTION,null)},Bt=function(e){e.normalize();const t=De.call(e.ownerDocument||e,e,k.SHOW_TEXT|k.SHOW_COMMENT|k.SHOW_CDATA_SECTION|k.SHOW_PROCESSING_INSTRUCTION,null);let n=t.nextNode();for(;n;){let e=n.data;p([Ce,xe,ke],t=>{e=E(e,t," ")}),n.data=e,n=t.nextNode()}},Gt=function(e){return e instanceof me&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof F)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},jt=function(e){if(!Se||"object"!=typeof e||null===e)return!1;try{return"number"==typeof Se(e)}catch(e){return!1}};function Wt(e,t,n){p(e,e=>{e.call(o,t,n,Ct)})}const Yt=function(e){let t=null;if(Wt(ve.beforeSanitizeElements,e,null),Gt(e))return zt(e),!0;const n=vt(e.nodeName);if(Wt(ve.uponSanitizeElement,e,{tagName:n,allowedTags:He}),Qe&&e.hasChildNodes()&&!jt(e.firstElementChild)&&C(/<[/\w!]/g,e.innerHTML)&&C(/<[/\w!]/g,e.textContent))return zt(e),!0;if(Qe&&e.namespaceURI===At&&"style"===n&&jt(e.firstElementChild))return zt(e),!0;if(e.nodeType===ce)return zt(e),!0;if(Qe&&e.nodeType===se&&C(/<[/\w]/g,e.data))return zt(e),!0;if(Ye[n]||!(qe.tagCheck instanceof Function&&qe.tagCheck(n))&&!He[n]){if(!Ye[n]&&$t(n)){if(We.tagNameCheck instanceof RegExp&&C(We.tagNameCheck,n))return!1;if(We.tagNameCheck instanceof Function&&We.tagNameCheck(n))return!1}if(st&&!mt[n]){const t=Ae(e)||e.parentNode,n=be(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=ge(n[o],!0);t.insertBefore(r,Te(e))}}}return zt(e),!0}return e instanceof m&&!function(e){let t=Ae(e);t&&t.tagName||(t={namespaceURI:St,tagName:"template"});const n=b(e.tagName),o=b(t.tagName);return!!Nt[e.namespaceURI]&&(e.namespaceURI===bt?t.namespaceURI===At?"svg"===n:t.namespaceURI===Tt?"svg"===n&&("annotation-xml"===o||Ot[o]):Boolean(Mt[n]):e.namespaceURI===Tt?t.namespaceURI===At?"math"===n:t.namespaceURI===bt?"math"===n&&Dt[o]:Boolean(Ft[n]):e.namespaceURI===At?!(t.namespaceURI===bt&&!Dt[o])&&!(t.namespaceURI===Tt&&!Ot[o])&&!Ft[n]&&(Rt[n]||!Mt[n]):!("application/xhtml+xml"!==wt||!Nt[e.namespaceURI]))}(e)?(zt(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!C(/<\/no(script|embed|frames)/i,e.innerHTML)?(Je&&e.nodeType===le&&(t=e.textContent,p([Ce,xe,ke],e=>{t=E(t,e," ")}),e.textContent!==t&&(g(o.removed,{element:e.cloneNode()}),e.textContent=t)),Wt(ve.afterSanitizeElements,e,null),!1):(zt(e),!0)},Xt=function(e,t,n){if(Xe[t])return!1;if(at&&("id"===t||"name"===t)&&(n in r||n in xt))return!1;const o=Ge[t]||qe.attributeCheck instanceof Function&&qe.attributeCheck(t,e);if(Ke&&!Xe[t]&&C(Le,t));else if($e&&C(Me,t));else if(!o||Xe[t]){if(!($t(e)&&(We.tagNameCheck instanceof RegExp&&C(We.tagNameCheck,e)||We.tagNameCheck instanceof Function&&We.tagNameCheck(e))&&(We.attributeNameCheck instanceof RegExp&&C(We.attributeNameCheck,t)||We.attributeNameCheck instanceof Function&&We.attributeNameCheck(t,e))||"is"===t&&We.allowCustomizedBuiltInElements&&(We.tagNameCheck instanceof RegExp&&C(We.tagNameCheck,n)||We.tagNameCheck instanceof Function&&We.tagNameCheck(n))))return!1}else if(gt[t]);else if(C(Ue,E(n,ze,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==N(n,"data:")||!dt[e]){if(Ve&&!C(Fe,E(n,ze,"")));else if(n)return!1}return!0},qt=M({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),$t=function(e){return!qt[b(e)]&&C(Pe,e)},Kt=function(e){Wt(ve.beforeSanitizeAttributes,e,null);const t=e.attributes;if(!t||Gt(e))return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Ge,forceKeepAttr:void 0};let r=t.length;for(;r--;){const i=t[r],a=i.name,l=i.namespaceURI,c=i.value,s=vt(a),u=c;let f="value"===a?u:_(u);if(n.attrName=s,n.attrValue=f,n.keepAttr=!0,n.forceKeepAttr=void 0,Wt(ve.uponSanitizeAttribute,e,n),f=n.attrValue,!lt||"id"!==s&&"name"!==s||0===N(f,ct)||(Pt(a,e),f=ct+f),Qe&&C(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,f)){Pt(a,e);continue}if("attributename"===s&&S(f,"href")){Pt(a,e);continue}if(n.forceKeepAttr)continue;if(!n.keepAttr){Pt(a,e);continue}if(!Ze&&C(/\/>/i,f)){Pt(a,e);continue}Je&&p([Ce,xe,ke],e=>{f=E(f,e," ")});const m=vt(e.nodeName);if(Xt(m,s,f)){if(Ee&&"object"==typeof de&&"function"==typeof de.getAttributeType)if(l);else switch(de.getAttributeType(m,s)){case"TrustedHTML":f=Ee.createHTML(f);break;case"TrustedScriptURL":f=Ee.createScriptURL(f)}if(f!==u)try{l?e.setAttributeNS(l,a,f):e.setAttribute(a,f),Gt(e)?zt(e):h(o.removed)}catch(t){Pt(a,e)}}else Pt(a,e)}Wt(ve.afterSanitizeAttributes,e,null)},Vt=function(e){let t=null;const n=Ht(e);for(Wt(ve.beforeSanitizeShadowDOM,e,null);t=n.nextNode();)Wt(ve.uponSanitizeShadowNode,t,null),Yt(t),Kt(t),t.content instanceof c&&Vt(t.content);Wt(ve.afterSanitizeShadowDOM,e,null)},Zt=function(e){if(e.nodeType===ae&&e.shadowRoot instanceof c){const t=e.shadowRoot;Zt(t),Vt(t)}const t=e.childNodes;if(!t)return;const n=[];p(t,e=>{g(n,e)});for(const e of n)Zt(e)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,a=null,l=null;if(Et=!e,Et&&(e="\x3c!--\x3e"),"string"!=typeof e&&!jt(e)&&"string"!=typeof(e=function(e){switch(typeof e){case"string":return e;case"number":return O(e);case"boolean":return D(e);case"bigint":return R?R(e):"0";case"symbol":return w?w(e):"Symbol()";case"undefined":default:return v(e);case"function":case"object":{if(null===e)return v(e);const t=e,n=P(t,"toString");if("function"==typeof n){const e=n(t);return"string"==typeof e?e:v(e)}return v(e)}}}(e)))throw x("dirty is not a string, aborting");if(!o.isSupported)return e;if(tt||Lt(t),o.removed=[],"string"==typeof e&&(ut=!1),ut){const t=e.nodeName;if("string"==typeof t){const e=vt(t);if(!He[e]||Ye[e])throw x("root node is forbidden and cannot be sanitized in-place")}Zt(e)}else if(jt(e))n=Ut("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===ae&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r),Zt(r);else{if(!ot&&!Je&&!et&&-1===e.indexOf("<"))return Ee&&it?Ee.createHTML(e):e;if(n=Ut(e),!n)return ot?null:it?Ne:""}n&&nt&&zt(n.firstChild);const s=Ht(ut?e:n);for(;a=s.nextNode();)Yt(a),Kt(a),a.content instanceof c&&Vt(a.content);if(ut)return Je&&Bt(e),e;if(ot){if(Je&&Bt(n),rt)for(l=Re.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Ge.shadowroot||Ge.shadowrootmode)&&(l=Ie.call(i,l,!0)),l}let u=et?n.outerHTML:n.innerHTML;return et&&He["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&C(re,n.ownerDocument.doctype.name)&&(u="\n"+u),Je&&p([Ce,xe,ke],e=>{u=E(u,e," ")}),Ee&&it?Ee.createHTML(u):u},o.setConfig=function(){Lt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),tt=!0},o.clearConfig=function(){Ct=null,tt=!1},o.isValidAttribute=function(e,t,n){Ct||Lt({});const o=vt(e),r=vt(t);return Xt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&g(ve[e],t)},o.removeHook=function(e,t){if(void 0!==t){const n=d(ve[e],t);return-1===n?void 0:y(ve[e],n,1)[0]}return h(ve[e])},o.removeHooks=function(e){ve[e]=[]},o.removeAllHooks=function(){ve={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},o}();return me})
タイトルとURLをコピーしました