feat: Remove Vertex Color (#1378)

This commit is contained in:
bd_ 2024-12-01 13:54:43 -08:00 committed by GitHub
parent f35283db51
commit 2c3e24333a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 375 additions and 2 deletions

View File

@ -0,0 +1,39 @@
using System.Diagnostics.CodeAnalysis;
using UnityEditor;
using static nadena.dev.modular_avatar.core.editor.Localization;
namespace nadena.dev.modular_avatar.core.editor
{
[CustomPropertyDrawer(typeof(ModularAvatarRemoveVertexColor.RemoveMode))]
[SuppressMessage("ReSharper", "InconsistentNaming")]
internal class RVCModeDrawer : EnumDrawer<ModularAvatarRemoveVertexColor.RemoveMode>
{
protected override string localizationPrefix => "remove-vertex-color.mode";
}
[CustomEditor(typeof(ModularAvatarRemoveVertexColor))]
internal class RemoveVertexColorEditor : MAEditorBase
{
private SerializedProperty _p_mode;
protected void OnEnable()
{
_p_mode = serializedObject.FindProperty(nameof(ModularAvatarRemoveVertexColor.Mode));
}
protected override void OnInnerInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_p_mode, G("remove-vertex-color.mode"));
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
ShowLanguageUI();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bfcaf601e9f94ba2900e66d66f469037
timeCreated: 1733085477

View File

@ -284,5 +284,8 @@
"ro_sim.effect_group.rule_inverted": "This rule is inverted",
"ro_sim.effect_group.rule_inverted.tooltip": "This rule will be applied when one of its conditions is NOT met",
"ro_sim.effect_group.conditions": "Conditions"
"ro_sim.effect_group.conditions": "Conditions",
"remove-vertex-color.mode": "Mode",
"remove-vertex-color.mode.Remove": "Remove Vertex Colors",
"remove-vertex-color.mode.DontRemove": "Keep Vertex Colors"
}

View File

@ -276,5 +276,8 @@
"ro_sim.effect_group.material.tooltip": "上記の Reactive Component がアクティブな時に設定されるマテリアル",
"ro_sim.effect_group.rule_inverted": "このルールの条件は反転されています",
"ro_sim.effect_group.rule_inverted.tooltip": "このルールは、いずれかの条件が満たされていない場合に適用されます",
"ro_sim.effect_group.conditions": "条件"
"ro_sim.effect_group.conditions": "条件",
"remove-vertex-color.mode": "モード",
"remove-vertex-color.mode.Remove": "頂点カラーを削除する",
"remove-vertex-color.mode.DontRemove": "頂点カラーを削除しない"
}

3
Editor/MiscPreview.meta Normal file
View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ea61a438a5d54a289c6abbb1e05c56da
timeCreated: 1733085642

View File

@ -0,0 +1,121 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using nadena.dev.ndmf.preview;
using UnityEngine;
namespace nadena.dev.modular_avatar.core.editor
{
internal class RemoveVertexColorPreview : IRenderFilter
{
private static string ToPathString(ComputeContext ctx, Transform t)
{
return string.Join("/", ctx.ObservePath(t).Select(t2 => t2.gameObject.name).Reverse());
}
public ImmutableList<RenderGroup> GetTargetGroups(ComputeContext context)
{
var roots = context.GetAvatarRoots();
var removers = roots
.SelectMany(r => context.GetComponentsInChildren<ModularAvatarRemoveVertexColor>(r, true))
.Select(rvc => (ToPathString(context, rvc.transform),
context.Observe(rvc, r => r.Mode) == ModularAvatarRemoveVertexColor.RemoveMode.Remove))
.OrderBy(pair => pair.Item1)
.ToList();
var targets = roots.SelectMany(
r => context.GetComponentsInChildren<SkinnedMeshRenderer>(r, true)
.Concat(
context.GetComponentsInChildren<MeshFilter>(r, true)
.SelectMany(mf => context.GetComponents<Renderer>(mf.gameObject))
)
);
targets = targets.Where(target =>
{
var stringPath = ToPathString(context, target.transform);
var index = removers.BinarySearch((stringPath, true));
if (index >= 0)
{
// There is a component on this mesh
return true;
}
var priorIndex = ~index - 1;
if (priorIndex < 0) return false; // no match
var (maybeParent, mode) = removers[priorIndex];
if (!stringPath.StartsWith(maybeParent)) return false; // no parent matched
return mode;
});
return targets.Select(RenderGroup.For).ToImmutableList();
}
public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs,
ComputeContext context)
{
Dictionary<Mesh, Mesh> conversionMap = new();
foreach (var (_, proxy) in proxyPairs)
{
Component c = proxy;
if (!(c is SkinnedMeshRenderer))
{
c = context.GetComponent<MeshFilter>(proxy.gameObject);
}
if (c == null) continue;
RemoveVertexColorPass.ForceRemove(_ => false, c, conversionMap);
}
return Task.FromResult<IRenderFilterNode>(new Node(conversionMap.Values.FirstOrDefault()));
}
private class Node : IRenderFilterNode
{
private readonly Mesh? _theMesh;
public Node(Mesh? theMesh)
{
_theMesh = theMesh;
}
public Task<IRenderFilterNode> Refresh(IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context,
RenderAspects updatedAspects)
{
if (updatedAspects.HasFlag(RenderAspects.Mesh)) return Task.FromResult<IRenderFilterNode>(null);
if (_theMesh == null) return Task.FromResult<IRenderFilterNode>(null);
return Task.FromResult<IRenderFilterNode>(this);
}
public RenderAspects WhatChanged => RenderAspects.Mesh;
public void Dispose()
{
if (_theMesh != null) Object.DestroyImmediate(_theMesh);
}
public void OnFrame(Renderer original, Renderer proxy)
{
if (_theMesh == null) return;
switch (proxy)
{
case SkinnedMeshRenderer smr: smr.sharedMesh = _theMesh; break;
default:
{
var mf = proxy.GetComponent<MeshFilter>();
if (mf != null) mf.sharedMesh = _theMesh;
break;
}
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b05d5c04f86b4924bf8acdd135448463
timeCreated: 1733085648

View File

@ -88,6 +88,7 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
var maContext = ctx.Extension<ModularAvatarContext>().BuildContext;
FixupExpressionsMenuPass.FixupExpressionsMenu(maContext);
});
seq.Run(RemoveVertexColorPass.Instance).PreviewingWith(new RemoveVertexColorPreview());
#endif
seq.Run(RebindHumanoidAvatarPass.Instance);
seq.Run("Purge ModularAvatar components", ctx =>

View File

@ -0,0 +1,93 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using nadena.dev.ndmf;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using Object = UnityEngine.Object;
namespace nadena.dev.modular_avatar.core.editor
{
internal class RemoveVertexColorPass : Pass<RemoveVertexColorPass>
{
protected override void Execute(ndmf.BuildContext context)
{
var removers = context.AvatarRootTransform.GetComponentsInChildren<ModularAvatarRemoveVertexColor>(true)!;
Dictionary<Mesh, Mesh> conversionMap = new();
foreach (var remover in removers)
{
foreach (var smr in remover!.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
TryRemove(context.IsTemporaryAsset, smr, conversionMap);
}
foreach (var mf in remover.GetComponentsInChildren<MeshFilter>(true))
{
TryRemove(context.IsTemporaryAsset, mf, conversionMap);
}
}
}
private const string PropPath = "m_Mesh";
private static void TryRemove(
Func<Mesh, bool> isTempAsset,
Component c,
Dictionary<Mesh, Mesh> conversionMap
)
{
var nearestRemover = c.GetComponentInParent<ModularAvatarRemoveVertexColor>()!;
if (nearestRemover.Mode != ModularAvatarRemoveVertexColor.RemoveMode.Remove) return;
ForceRemove(isTempAsset, c, conversionMap);
}
internal static void ForceRemove(Func<Mesh, bool> isTempAsset, Component c,
Dictionary<Mesh, Mesh> conversionMap)
{
var obj = new SerializedObject(c);
var prop = obj.FindProperty("m_Mesh");
if (prop == null)
{
throw new Exception("Property not found: " + PropPath);
}
var mesh = prop.objectReferenceValue as Mesh;
if (mesh == null)
{
return;
}
var originalMesh = mesh;
if (conversionMap.TryGetValue(mesh, out var converted))
{
prop.objectReferenceValue = converted;
obj.ApplyModifiedPropertiesWithoutUndo();
return;
}
if (mesh.GetVertexAttributes().All(va => va.attribute != VertexAttribute.Color))
{
// no-op
return;
}
if (!isTempAsset(mesh))
{
mesh = Object.Instantiate(mesh);
prop.objectReferenceValue = mesh;
obj.ApplyModifiedPropertiesWithoutUndo();
}
mesh.colors = null;
conversionMap[originalMesh] = mesh;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a227da6f9f1548c3867b1ed113f28e9d
timeCreated: 1733008734

View File

@ -0,0 +1,23 @@
using System;
using JetBrains.Annotations;
using UnityEngine;
namespace nadena.dev.modular_avatar.core
{
[AddComponentMenu("Modular Avatar/MA Remove Vertex Color")]
[DisallowMultipleComponent]
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/remove-vertex-color?lang=auto")]
[PublicAPI]
public class ModularAvatarRemoveVertexColor : AvatarTagComponent
{
[Serializable]
[PublicAPI]
public enum RemoveMode
{
Remove,
DontRemove
}
public RemoveMode Mode = RemoveMode.Remove;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc5f8bfae24244aeaedcd6c2bb7264f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

View File

@ -0,0 +1,34 @@
# Remove Vertex Color
![Remove Vertex Color](remove-vertex-color.png)
The Remove Vertex Color component removes vertex colors from the object it is attached to and its children.
## When should I use it?
Sometimes, models come with vertex colors that aren't intended for display. When changing to a shader that
makes use of vertex colors, such as the VRChat mobile shaders, this can result in undesired discoloration. You can use
this component to remove these vertex colors nondestructively.
<div style={{display: "flex", "flex-direction": "row"}}>
<div style={{margin: "1em"}}>
<div>
![With unwanted vertex colors](remove-vertex-color-before.png)
</div>
*Without Remove Vertex Color, some unwanted vertex colors discolor this avatar's hair.*
</div>
<div style={{margin: "1em"}}>
<div>
![After removing vertex colors](remove-vertex-color-after.png)
</div>
*After adding Remove Vertex Color, the avatar's hair is the correct color.*
</div>
</div>
## Detailed usage
Simply attach the Remove Vertex Color component to an object in your avatar - often, you can just add it to the root
object. All objects below that object in the hierarchy will have their vertex colors removed.
If you want to exclude some objects, add a Remove Vertex Color component to the object you want to exclude and set
the mode to "Keep Vertex Colors". Any objects below this object will not have their vertex colors removed.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

View File

@ -0,0 +1,33 @@
# Remove Vertex Color
![Remove Vertex Color](remove-vertex-color.png)
Remove Vertex Color コンポーネントは、アタッチされたオブジェクトとその子オブジェクトから頂点カラーを削除します。
## いつ使うものか?
時々、アバターや衣装には意図されていない頂点カラーが付いていることがあります。VRChat Mobile系統など頂点カラーを使用するシェーダーに変更すると、
変色してしまうことがあります。このコンポーネントを使えば、非破壊的に問題の頂点カラーを削除できます。
<div style={{display: "flex", "flex-direction": "row"}}>
<div style={{margin: "1em"}}>
<div>
![不要な頂点カラーがある場合](remove-vertex-color-before.png)
</div>
*Remove Vertex Color を使わないと、このアバターの髪の毛に不要な頂点カラーで変色してしまいます。*
</div>
<div style={{margin: "1em"}}>
<div>
![頂点カラーを削除した後](remove-vertex-color-after.png)
</div>
*Remove Vertex Color を追加した後、アバターの髪の色が正しくなります。*
</div>
</div>
## 詳細な使い方
Remove Vertex Color コンポーネントをアバターのオブジェクトに追加してください。通常、ルートオブジェクトに追加するだけで十分です。
このオブジェクト以下のすべてのオブジェクトの頂点カラーが削除されます。
特定のオブジェクトを除外したい場合は、除外したいオブジェクトに Remove Vertex Color コンポーネントを追加し、モードを「頂点カラーを削除しない」
に設定してください。このオブジェクト以下のオブジェクトの頂点カラーは削除されません。

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB