Skip to content

Commit

Permalink
Merge pull request #998 from anatawa12/remove-mesh-by-mask
Browse files Browse the repository at this point in the history
feat: Remove mesh by mask
  • Loading branch information
anatawa12 authored Apr 13, 2024
2 parents 386a56b + 797c156 commit 4a57452
Show file tree
Hide file tree
Showing 19 changed files with 539 additions and 34 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions .docs/content/docs/reference/remove-mesh-by-mask/index.ja.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Remove Mesh By Mask
weight: 25
---

# Remove Mesh By Mask

マスクテクスチャで指定した範囲のポリゴンを削除します。

このコンポーネントは、SkinnedMeshRendererコンポーネントのあるGameObjectに追加してください。(分類: [Modifying Edit Skinned Mesh Component](../../component-kind/edit-skinned-mesh-components#modifying-component))

## 利点 {#benefits}

服で隠れていたりして見えないような部分のメッシュを削除すると、見た目に影響させずに描画負荷やBlendShapeの処理負荷などを減らして軽量化することができます。

このコンポーネントを使用すると、アルファマスク用のテクスチャや、gatosyocoraさんの[MeshDeleterWithTexture]用のマスクテクスチャを利用して簡単にメッシュを削除することができます。

[MeshDeleterWithTexture]: https://github.com/gatosyocora/MeshDeleterWithTexture

## 設定 {#settings}

![component.png](component.png)

メッシュのマテリアルスロットの一覧が表示されます。
マスクテクスチャによるポリゴンの削除を行う対象のマテリアルスロットを選択してください。

### マスクテクスチャ {#mask-texture}

ポリゴンの削除に利用するマスクテクスチャです。

### 削除モード {#remove-mode}

マスクテクスチャは物によって色が異なるため、対応するモードを選択する必要があります。

黒(に近い色)の場合にポリゴンを削除するように設計されているマスクテクスチャを利用する場合は、`Remove Black`に設定してください。\
白(に近い色)の場合にポリゴンを削除するように設計されているマスクテクスチャを利用する場合は、`Remove White`に設定してください。
36 changes: 36 additions & 0 deletions .docs/content/docs/reference/remove-mesh-by-mask/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Remove Mesh By Mask
weight: 25
---

# Remove Mesh By Mask

Remove some polygons in any area specified by mask textures.

This component should be added to a GameObject which has a SkinnedMeshRenderer component. (Kind: [Modifying Edit Skinned Mesh Component](../../component-kind/edit-skinned-mesh-components#modifying-component))

## Benefits

By removing polygons which are hidden by clothes or something, you can reduce rendering cost, BlendShape processing cost, etc. without affecting the appearance so much.

You can use this component to easily remove polygons with alpha mask texture or mask texture for [MeshDeleterWithTexture] by gatosyocora.

[MeshDeleterWithTexture]: https://github.com/gatosyocora/MeshDeleterWithTexture

## Settings

![component.png](component.png)

You'll see the list of material slots of the mesh.
Select the material slots you want to remove polygons with mask texture.

### Mask Texture

The mask texture to remove polygons.

### Remove Mode

Since the mask textures have different colors depending on the case, you need to select the corresponding mode.

When you use the mask texture which is designed to remove polygons if the color is (close to) black, select `Remove Black` mode.\
When you use the mask texture which is designed to remove polygons if the color is (close to) white, select `Remove White` mode.
5 changes: 5 additions & 0 deletions CHANGELOG-PRERELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ The format is based on [Keep a Changelog].
- Animations animating missing GameObject is removed `#994`
- Since this update, animations that targeting GameObjects with post-AAO paths is no longer working.
- Please create animations that targeting GameObjects with pre-AAO paths.
- Remove Mesh by Mask `#998`
- With this component, you can remove polygons with mask texture.
- You can use use mask for [MeshDeleterWithTexture] or alpha mask to remove polygons.

[MeshDeleterWithTexture]: https://github.com/gatosyocora/MeshDeleterWithTexture

### Changed

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ The format is based on [Keep a Changelog].
- Animations animating missing GameObject is removed `#994`
- Since this update, animations that targeting GameObjects with post-AAO paths is no longer working.
- Please create animations that targeting GameObjects with pre-AAO paths.
- Remove Mesh by Mask `#998`
- With this component, you can remove polygons with mask texture.
- You can use use mask for [MeshDeleterWithTexture] or alpha mask to remove polygons.

[MeshDeleterWithTexture]: https://github.com/gatosyocora/MeshDeleterWithTexture

### Changed
- MergePhysBone now corrects curve settings `#775`
Expand Down
6 changes: 6 additions & 0 deletions Editor/ComponentValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public static void ValidateAll(GameObject root)
break;
}
}

if (component is INoSourceEditSkinnedMeshComponent)
{
if ((Component)component.GetComponent<ISourceSkinnedMeshComponent>())
BuildLog.LogError("NoSourceEditSkinnedMeshComponent:HasSourceSkinnedMeshComponent", component);
}
}
}
}
Expand Down
175 changes: 143 additions & 32 deletions Editor/EditModePreview/RemoveMeshPreviewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class RemoveMeshPreviewController : IDisposable
{
typeof(RemoveMeshByBlendShape),
typeof(RemoveMeshInBox),
typeof(RemoveMeshByMask),
};

public RemoveMeshPreviewController([NotNull] SkinnedMeshRenderer targetRenderer, Mesh originalMesh = null, Mesh previewMesh = null)
Expand All @@ -37,6 +38,7 @@ public RemoveMeshPreviewController([NotNull] SkinnedMeshRenderer targetRenderer,

_removeMeshInBox = default;
_removeMeshByBlendShape = default;
_removeMeshByMask = default;

var subMeshes = new SubMeshDescriptor[OriginalMesh.subMeshCount];
_subMeshTriangleEndIndices = new int[OriginalMesh.subMeshCount];
Expand Down Expand Up @@ -72,6 +74,8 @@ public RemoveMeshPreviewController([NotNull] SkinnedMeshRenderer targetRenderer,
}
}

_uv = new NativeArray<Vector2>(OriginalMesh.uv, Allocator.Persistent);

if (previewMesh)
{
PreviewMesh = previewMesh;
Expand All @@ -94,10 +98,12 @@ public RemoveMeshPreviewController([NotNull] SkinnedMeshRenderer targetRenderer,
private ComponentHolder<Transform>[] _boneTransforms;
private ComponentHolder<RemoveMeshInBox> _removeMeshInBox;
private ComponentHolder<RemoveMeshByBlendShape> _removeMeshByBlendShape;
private ComponentHolder<RemoveMeshByMask> _removeMeshByMask;

private readonly BlendShapePreviewContext _blendShapePreviewContext;
private readonly int[] _subMeshTriangleEndIndices;
private NativeArray<Triangle> _triangles;
private NativeArray<Vector2> _uv;
[CanBeNull] private RemoveMeshWithBoxPreviewContext _removeMeshWithBoxPreviewContext;
[CanBeNull] private RemoveMeshByBlendShapePreviewContext _removeMeshByBlendShapePreviewContext;
private readonly string[] _blendShapeNames;
Expand Down Expand Up @@ -202,11 +208,31 @@ bool ShouldStopPreview()
break;
}

switch (_removeMeshByMask.Update(TargetGameObject))
{
default:
case Changed.Updated:
modified = true;
break;
case Changed.Removed:
modified = true;
break;
case Changed.Created:
// TODO
//if (_removeMeshByBlendShapePreviewContext == null)
// _removeMeshByBlendShapePreviewContext =
// new RemoveMeshByBlendShapePreviewContext(_blendShapePreviewContext, OriginalMesh);
modified = true;
break;
case Changed.Nothing:
break;
}

if (modified)
UpdatePreviewMesh();

// modifier component not found
if (!(_removeMeshInBox.Value || _removeMeshByBlendShape.Value)) return false;
if (!(_removeMeshInBox.Value || _removeMeshByBlendShape.Value || _removeMeshByMask.Value)) return false;

return false;
}
Expand Down Expand Up @@ -238,42 +264,79 @@ private void UpdatePreviewMesh()
var tolerance =
(float)(_removeMeshByBlendShape.Value ? _removeMeshByBlendShape.Value.tolerance : 0);

new FlagTrianglesJob
{
Triangles = _triangles,
RemoveFlags = flags,
VertexCount = OriginalMesh.vertexCount,
PreviewMesh.subMeshCount = _subMeshTriangleEndIndices.Length;
var triIdx = 0;

Boxes = boxes,
BlendShapeAppliedVertices = blendShapeAppliedVertices,
for (var subMeshIdx = 0; subMeshIdx < _subMeshTriangleEndIndices.Length; subMeshIdx++)
{
_indexBuffer.Clear();

BlendShapeIndices = blendShapeIndices,
ToleranceSquared = tolerance * tolerance,
BlendShapeMovements = blendShapeMovements,
}.Schedule(_triangles.Length, 1).Complete();
}
var maskData = Array.Empty<Color32>();
var maskMode = MaskMode.Disabled;
var maskWidth = 0;
var maskHeight = 0;
if (_removeMeshByMask.Value)
{
var materials = _removeMeshByMask.Value.materials;
if (subMeshIdx < materials.Length)
{
var submeshInfo = materials[subMeshIdx];
if (submeshInfo.enabled)
{
var maskTexture = submeshInfo.mask;
var mode = submeshInfo.mode;
if (maskTexture != null && maskTexture.isReadable)
{
maskData = maskTexture.GetPixels32();
maskWidth = maskTexture.width;
maskHeight = maskTexture.height;
maskMode = (MaskMode)mode;
}
}
}
}

var subMeshIdx = 0;
var triStart = triIdx;
var triEnd = _subMeshTriangleEndIndices[subMeshIdx];
var triLen = triEnd - triStart;

_indexBuffer.Clear();
using (var maskDataArray = new NativeArray<Color32>(maskData, Allocator.TempJob))
{
new FlagTrianglesJob
{
Triangles = _triangles.Slice(triStart, triLen),
RemoveFlags = flags.Slice(triStart, triLen),
VertexCount = OriginalMesh.vertexCount,

Boxes = boxes,
BlendShapeAppliedVertices = blendShapeAppliedVertices,

BlendShapeIndices = blendShapeIndices,
ToleranceSquared = tolerance * tolerance,
BlendShapeMovements = blendShapeMovements,

UV = _uv,
Mask = maskDataArray,
MaskWidth = maskWidth,
MaskHeight = maskHeight,
MaskMode = maskMode,
}.Schedule(triLen, 1).Complete();
}

for (var triIdx = 0; triIdx < _triangles.Length; triIdx++)
{
if (!flags[triIdx])
{
_indexBuffer.Add(_triangles[triIdx].First);
_indexBuffer.Add(_triangles[triIdx].Second);
_indexBuffer.Add(_triangles[triIdx].Third);
}
for (; triIdx < _subMeshTriangleEndIndices[subMeshIdx]; triIdx++)
{
if (!flags[triIdx])
{
_indexBuffer.Add(_triangles[triIdx].First);
_indexBuffer.Add(_triangles[triIdx].Second);
_indexBuffer.Add(_triangles[triIdx].Third);
}
}

PreviewMesh.subMeshCount = _subMeshTriangleEndIndices.Length;
while (subMeshIdx < _subMeshTriangleEndIndices.Length &&
triIdx + 1 == _subMeshTriangleEndIndices[subMeshIdx])
{
PreviewMesh.SetTriangles(_indexBuffer, subMeshIdx);
_indexBuffer.Clear();
subMeshIdx++;
}

_indexBuffer.Clear();
}
}
}
Expand All @@ -282,9 +345,9 @@ private void UpdatePreviewMesh()
struct FlagTrianglesJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<Triangle> Triangles;
public NativeSlice<Triangle> Triangles;
public int VertexCount;
public NativeArray<bool> RemoveFlags;
public NativeSlice<bool> RemoveFlags;

// Remove Mesh in Box
[ReadOnly]
Expand All @@ -298,6 +361,15 @@ struct FlagTrianglesJob : IJobParallelFor
[ReadOnly]
public NativeArray<Vector3> BlendShapeMovements;

// Remove Mesh by Mask
[ReadOnly]
public NativeArray<Vector2> UV;
[ReadOnly]
public NativeArray<Color32> Mask;
public int MaskWidth;
public int MaskHeight;
public MaskMode MaskMode;

public float ToleranceSquared { get; set; }

public void Execute(int index) => RemoveFlags[index] = TestTriangle(Triangles[index]);
Expand Down Expand Up @@ -331,16 +403,55 @@ private bool TestTriangle(Triangle triangle)
}
}

switch (MaskMode)
{
case MaskMode.RemoveWhite:
if (GetValue(UV[triangle.First]) > 127
&& GetValue(UV[triangle.Second]) > 127
&& GetValue(UV[triangle.Third]) > 127)
{
return true;
}
break;
case MaskMode.RemoveBlack:
if (GetValue(UV[triangle.First]) <= 127
&& GetValue(UV[triangle.Second]) <= 127
&& GetValue(UV[triangle.Third]) <= 127)
{
return true;
}
break;
case MaskMode.Disabled:
default:
break;
}

return false;
}

private bool TestBlendShape(int movementBase, int index) =>
BlendShapeMovements[movementBase + index].sqrMagnitude > ToleranceSquared;

private int GetValue(Vector2 uv)
{
var x = Mathf.RoundToInt(uv.x % 1 * MaskWidth);
var y = Mathf.RoundToInt(uv.y % 1 * MaskHeight);
var color = Mask[x + y * MaskWidth];
return Mathf.Max(Mathf.Max(color.r, color.g), color.b);
}
}

enum MaskMode
{
Disabled = -1,
RemoveWhite = RemoveMeshByMask.RemoveMode.RemoveWhite,
RemoveBlack = RemoveMeshByMask.RemoveMode.RemoveBlack,
}

public void Dispose()
{
_triangles.Dispose();
_uv.Dispose();
_removeMeshWithBoxPreviewContext?.Dispose();
_removeMeshByBlendShapePreviewContext?.Dispose();
_blendShapePreviewContext?.Dispose();
Expand Down Expand Up @@ -401,4 +512,4 @@ internal enum Changed
Created,
}
}
}
}
1 change: 1 addition & 0 deletions Editor/EditSkinnedMeshComponentUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ private T CheckRecursive<T>(Func<T> compute)
[typeof(MergeToonLitMaterial)] = x => new MergeToonLitMaterialProcessor((MergeToonLitMaterial)x),
[typeof(RemoveMeshInBox)] = x => new RemoveMeshInBoxProcessor((RemoveMeshInBox)x),
[typeof(RemoveMeshByBlendShape)] = x => new RemoveMeshByBlendShapeProcessor((RemoveMeshByBlendShape)x),
[typeof(RemoveMeshByMask)] = x => new RemoveMeshByMaskProcessor((RemoveMeshByMask)x),
[typeof(InternalAutoFreezeMeaninglessBlendShape)] = x => new InternalAutoFreezeMeaninglessBlendShapeProcessor((InternalAutoFreezeMeaninglessBlendShape)x),
};

Expand Down
Loading

0 comments on commit 4a57452

Please sign in to comment.