Merge branch 'master' into feature/mtoon10-showcase-custom-editor

This commit is contained in:
ousttrue 2025-04-09 23:59:15 +09:00 committed by GitHub
commit bc8304466a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 328 additions and 22 deletions

View File

@ -570,6 +570,7 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
cameraScroller: {fileID: 1644774163}
topDownCapture: {fileID: 1640562119}
dependencies: {fileID: 11400000, guid: 21a44348ad7e90744a8e320392d6a0b6, type: 2}
alphaModeShowcase: {fileID: 11400000, guid: 9a1685921243e0045902dd01741b2146, type: 2}
renderQueueOffsetShowcase: {fileID: 11400000, guid: 029bd96435085fa4fbe25e6c987bf21e,
@ -819,6 +820,150 @@ MonoBehaviour:
serializedVersion: 2
m_Bits: 4294967295
m_MaxRayIntersections: 0
--- !u!1 &1640562115
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1640562118}
- component: {fileID: 1640562117}
- component: {fileID: 1640562116}
- component: {fileID: 1640562119}
m_Layer: 0
m_Name: TopDownCapture
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1640562116
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1640562115}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
m_Name:
m_EditorClassIdentifier:
m_RenderShadows: 1
m_RequiresDepthTextureOption: 2
m_RequiresOpaqueTextureOption: 2
m_CameraType: 0
m_Cameras: []
m_RendererIndex: -1
m_VolumeLayerMask:
serializedVersion: 2
m_Bits: 1
m_VolumeTrigger: {fileID: 0}
m_VolumeFrameworkUpdateModeOption: 2
m_RenderPostProcessing: 0
m_Antialiasing: 0
m_AntialiasingQuality: 2
m_StopNaN: 0
m_Dithering: 0
m_ClearDepth: 1
m_AllowXRRendering: 1
m_AllowHDROutput: 1
m_UseScreenCoordOverride: 0
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
m_RequiresDepthTexture: 0
m_RequiresColorTexture: 0
m_Version: 2
m_TaaSettings:
m_Quality: 3
m_FrameInfluence: 0.1
m_JitterScale: 1
m_MipBias: 0
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
--- !u!20 &1640562117
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1640562115}
m_Enabled: 0
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0, g: 0, b: 0, a: 1}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &1640562118
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1640562115}
serializedVersion: 2
m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0}
--- !u!114 &1640562119
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1640562115}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e55e85e81e284c6894f606adcf7ddc56, type: 3}
m_Name:
m_EditorClassIdentifier:
_camera: {fileID: 1640562117}
_renderTarget: {fileID: 0}
_screenshot: {fileID: 0}
--- !u!1 &1644774162
GameObject:
m_ObjectHideFlags: 0
@ -978,3 +1123,4 @@ SceneRoots:
- {fileID: 867928055}
- {fileID: 1948350611}
- {fileID: 1644774167}
- {fileID: 1640562118}

View File

@ -1,4 +1,6 @@
using System;
using System.IO;
using UniGLTF;
using UnityEngine;
using VRM10.MToon10;
@ -7,6 +9,7 @@ namespace VRM10.Samples.MToon10Showcase
public class MToon10ShowcaseEntryPoint : MonoBehaviour
{
[SerializeField] private CameraScroller cameraScroller;
[SerializeField] private TopDownCapture topDownCapture;
[SerializeField] private MToon10ShowcaseDependencies dependencies;
[SerializeField] private AlphaModeShowcase alphaModeShowcase;
@ -28,8 +31,8 @@ namespace VRM10.Samples.MToon10Showcase
private static readonly float PrimitiveRadius = 0.3f;
private static readonly float PrimitiveSpacing = 0.2f;
private static readonly float LabelSpacing = 0.1f;
private Vector3 _nextOrigin = new(-2.0f, 1.0f, 2.0f);
private static readonly Vector3 InitialOrigin = new(0f, 1.0f, 0f);
private Vector3 _nextOrigin = InitialOrigin;
private void Start()
{
@ -40,12 +43,13 @@ namespace VRM10.Samples.MToon10Showcase
context.AlphaCutoff = entry.alphaCutoff;
context.DoubleSidedMode = entry.doubleSidedMode;
});
CreateShowcase(renderQueueOffsetShowcase.entries, renderQueueOffsetShowcase.baseMaterial, (entry, context) =>
{
context.AlphaMode = MToon10AlphaMode.Transparent;
context.BaseColorFactorSrgb = entry.litColor;
context.RenderQueueOffsetNumber = entry.renderQueueOffset;
});
CreateShowcase(renderQueueOffsetShowcase.entries, renderQueueOffsetShowcase.baseMaterial,
(entry, context) =>
{
context.AlphaMode = MToon10AlphaMode.Transparent;
context.BaseColorFactorSrgb = entry.litColor;
context.RenderQueueOffsetNumber = entry.renderQueueOffset;
});
CreateShowcase(litShowcase.entries, litShowcase.baseMaterial, (entry, context) =>
{
context.BaseColorFactorSrgb = entry.litColor;
@ -71,10 +75,8 @@ namespace VRM10.Samples.MToon10Showcase
context.ShadingShiftTextureScale = entry.shadingShiftTextureScale;
context.ShadingShiftTexture = entry.shadingShiftTexture;
}, primitiveType: PrimitiveType.Quad);
CreateShowcase(giEqualizationShowcase.entries, giEqualizationShowcase.baseMaterial, (entry, context) =>
{
context.GiEqualizationFactor = entry.giEqualizationFactor;
});
CreateShowcase(giEqualizationShowcase.entries, giEqualizationShowcase.baseMaterial,
(entry, context) => { context.GiEqualizationFactor = entry.giEqualizationFactor; });
CreateShowcase(emissionShowcase.entries, emissionShowcase.baseMaterial, (entry, context) =>
{
context.EmissiveTexture = entry.emissiveTexture;
@ -111,13 +113,59 @@ namespace VRM10.Samples.MToon10Showcase
context.UvAnimationScrollYSpeedFactor = entry.uvAnimationScrollYSpeedFactor;
context.UvAnimationRotationSpeedFactor = entry.uvAnimationRotationSpeedFactor;
}, PrimitiveType.Quad);
CreateFloor();
cameraScroller.Initialize(new Vector3(0.0f, 5.0f, 0.0f), new Vector3(0.0f, 5.0f, _nextOrigin.z), 0.5f);
var width = (PrimitiveRadius * 2 + PrimitiveSpacing) * Columns - PrimitiveSpacing;
var bottomLeft = _nextOrigin;
const float screenCenterXOffset = 2f;
var floorOffset = new Vector3(width / 2f + screenCenterXOffset, -1f, 0.0f);
CreateFloor(
InitialOrigin + floorOffset,
bottomLeft + floorOffset,
dependencies.floorPrefab);
const float cameraHeight = 5f;
var cameraOffset = new Vector3(width / 2f + screenCenterXOffset, cameraHeight, 0);
cameraScroller.Initialize(
InitialOrigin + cameraOffset,
bottomLeft + cameraOffset,
0.5f);
const float padding = 0.1f;
var topLeft = InitialOrigin + new Vector3(-padding, 0, 0);
var bottomRight = bottomLeft + new Vector3(width + padding, 0, 0);
topDownCapture.Capture(topLeft, bottomRight);
}
private Transform CreateShowcase<T>(T[] entries, Material baseMaterial, Action<T, MToon10Context> setup, PrimitiveType primitiveType = PrimitiveType.Sphere)
private void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 150, 30), "Show Captured Image"))
{
topDownCapture.ShowCapturedImageNextToCaptureTarget(0.9f);
}
if (GUI.Button(new Rect(10, 45, 150, 30), "Export Captured Image"))
{
var fileName = $"MToon10Showcase_{PackageVersion.VERSION}.png";
#if UNITY_EDITOR
var path = UnityEditor.EditorUtility.SaveFilePanel("Export Screenshot", "", fileName, "png");
#else
var path = Path.Combine(Application.dataPath, fileName);
#endif
if (string.IsNullOrEmpty(path))
{
Debug.LogWarning("Export cancelled");
return;
}
topDownCapture.ExportCapturedImage(path);
Application.OpenURL(path);
Debug.Log($"Exported screenshot to {path}");
}
}
private Transform CreateShowcase<T>(T[] entries, Material baseMaterial, Action<T, MToon10Context> setup,
PrimitiveType primitiveType = PrimitiveType.Sphere)
where T : class
{
const int columnCount = 4;
@ -155,18 +203,22 @@ namespace VRM10.Samples.MToon10Showcase
return root.transform;
}
private void CreateFloor()
private static GameObject CreateFloor(Vector3 startOrigin, Vector3 endOrigin, GameObject floorPrefab)
{
if (startOrigin.z < endOrigin.z) throw new ArgumentException();
var floors = new GameObject("Floors");
var nextFloorOrigin = Vector3.zero;
var floorLength = 10f * dependencies.floorPrefab.transform.localScale.y;
var nextFloorOrigin = startOrigin;
var floorLength = 10f * floorPrefab.transform.localScale.y;
var viewportOffset = Camera.main!.orthographicSize;
while (nextFloorOrigin.z + floorLength / 2f > _nextOrigin.z - viewportOffset)
while (endOrigin.z - viewportOffset < nextFloorOrigin.z + floorLength / 2f)
{
var floor = Instantiate(dependencies.floorPrefab, floors.transform);
var floor = Instantiate(floorPrefab, floors.transform);
floor.transform.position = nextFloorOrigin;
nextFloorOrigin -= new Vector3(0.0f, 0.0f, floorLength);
}
return floors;
}
}
}

View File

@ -0,0 +1,104 @@
using System;
using System.IO;
using UnityEngine;
using UnityEngine.Rendering;
namespace VRM10.Samples.MToon10Showcase
{
public sealed class TopDownCapture : MonoBehaviour
{
private static readonly float CameraHeight = 5f;
private static readonly int MaxPixels = 4096 * 4096;
private static readonly int TextureWidth = 480;
[SerializeField] private Camera _camera;
private RenderTexture _renderTarget;
private GameObject _quad;
private Vector3 _topLeftOfCaptureArea;
private Vector3 _bottomRightOfCaptureArea;
public void Capture(Vector3 topLeft, Vector3 bottomRight)
{
if (topLeft.x > bottomRight.x) throw new ArgumentException("TopLeft.x must be less than BottomRight.x");
if (topLeft.z < bottomRight.z) throw new ArgumentException("TopLeft.z must be greater than BottomRight.z");
// Set up render texture
var width = bottomRight.x - topLeft.x;
var height = topLeft.z - bottomRight.z;
var aspect = width / height;
var texHeight = (int)(TextureWidth / aspect);
if (TextureWidth * texHeight > MaxPixels) throw new Exception("Too many pixels to capture.");
if (_renderTarget != null) _renderTarget.Release();
_renderTarget = new RenderTexture(TextureWidth, texHeight, 16, RenderTextureFormat.ARGB32);
// Set up camera
_camera.transform.position = new Vector3(topLeft.x + width / 2f, CameraHeight, topLeft.z - height / 2f);
_camera.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
_camera.orthographic = true;
_camera.orthographicSize = height / 2f;
_camera.targetTexture = _renderTarget;
// Capture
_camera.Render();
// Save capture area for next step
_topLeftOfCaptureArea = topLeft;
_bottomRightOfCaptureArea = bottomRight;
}
public void ShowCapturedImageNextToCaptureTarget(float xSpacing)
{
if (_renderTarget == null)
{
Debug.LogWarning("Call Capture() before ShowCaptureOnMesh()");
return;
}
if (_quad != null)
{
return;
}
var offset = new Vector3(_bottomRightOfCaptureArea.x + xSpacing, 0, 0);
var topLeft = _topLeftOfCaptureArea + offset;
var bottomRight = _bottomRightOfCaptureArea + offset;
var width = bottomRight.x - topLeft.x;
var height = topLeft.z - bottomRight.z;
_quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
_quad.gameObject.name = "Capture";
_quad.transform.position = new Vector3(topLeft.x + width / 2f, topLeft.y, topLeft.z - height / 2f);
_quad.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
_quad.transform.localScale = new Vector3(width, height, 1f);
var material = new Material(Shader.Find("Unlit/Texture"));
material.mainTexture = _renderTarget;
_quad.GetComponent<Renderer>().material = material;
}
public void ExportCapturedImage(string path)
{
if (_renderTarget == null)
{
Debug.LogWarning("Call Capture() before ExportCapturedImage()");
return;
}
AsyncGPUReadback.Request(_renderTarget, 0, request =>
{
if (request.hasError)
{
Debug.LogError("AsyncGPUReadback error");
return;
}
var texture = new Texture2D(_renderTarget.width, _renderTarget.height, TextureFormat.RGBA32, false);
var data = request.GetData<Color32>();
texture.LoadRawTextureData(data);
texture.Apply();
var encodedData = texture.EncodeToPNG();
File.WriteAllBytes(path, encodedData);
DestroyImmediate(texture);
});
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e55e85e81e284c6894f606adcf7ddc56
timeCreated: 1743760457

View File

@ -3,7 +3,8 @@
"rootNamespace": "",
"references": [
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:0aaf403bd13871a44b7127aef2695ff8"
"GUID:0aaf403bd13871a44b7127aef2695ff8",
"GUID:8d76e605759c3f64a957d63ef96ada7c"
],
"includePlatforms": [],
"excludePlatforms": [],