Skip to content

Commit

Permalink
feat: Remove Vertex Color (#1378)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdunderscore authored Dec 1, 2024
1 parent f35283d commit 2c3e243
Show file tree
Hide file tree
Showing 20 changed files with 375 additions and 2 deletions.
39 changes: 39 additions & 0 deletions Editor/Inspector/RemoveVertexColorEditor.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
3 changes: 3 additions & 0 deletions Editor/Inspector/RemoveVertexColorEditor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Editor/Localization/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
5 changes: 4 additions & 1 deletion Editor/Localization/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 0 deletions Editor/MiscPreview.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

121 changes: 121 additions & 0 deletions Editor/MiscPreview/RemoveVertexColorPreview.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
}
}
3 changes: 3 additions & 0 deletions Editor/MiscPreview/RemoveVertexColorPreview.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Editor/PluginDefinition/PluginDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ protected override void Configure()
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 =>
Expand Down
93 changes: 93 additions & 0 deletions Editor/RemoveVertexColorPass.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
3 changes: 3 additions & 0 deletions Editor/RemoveVertexColorPass.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions Runtime/RemoveVertexColor.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
11 changes: 11 additions & 0 deletions Runtime/RemoveVertexColor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions docs~/docs/reference/remove-vertex-color.md
Original file line number Diff line number Diff line change
@@ -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 added docs~/docs/reference/remove-vertex-color.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2c3e243

Please sign in to comment.