mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-09-30 14:53:09 -05:00
add exporting captured image feature
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UniGLTF;
|
||||
using UnityEngine;
|
||||
using VRM10.MToon10;
|
||||
|
||||
@@ -115,23 +117,51 @@ namespace VRM10.Samples.MToon10Showcase
|
||||
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 + new Vector3(width / 2f, -1f, 0.0f),
|
||||
bottomLeft + new Vector3(width / 2f, -1f, 0.0f),
|
||||
InitialOrigin + floorOffset,
|
||||
bottomLeft + floorOffset,
|
||||
dependencies.floorPrefab);
|
||||
|
||||
const float cameraHeight = 5f;
|
||||
|
||||
var cameraOffset = new Vector3(width / 2f + screenCenterXOffset, cameraHeight, 0);
|
||||
cameraScroller.Initialize(
|
||||
InitialOrigin + new Vector3(width / 2f, cameraHeight, 0.0f),
|
||||
bottomLeft + new Vector3(width / 2f, cameraHeight, 0.0f),
|
||||
InitialOrigin + cameraOffset,
|
||||
bottomLeft + cameraOffset,
|
||||
0.5f);
|
||||
|
||||
const float captureSpacing = 0.1f;
|
||||
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);
|
||||
}
|
||||
|
||||
topDownCapture.Capture(
|
||||
InitialOrigin + new Vector3(-captureSpacing, cameraHeight, 0.0f),
|
||||
bottomLeft + new Vector3(width + captureSpacing, cameraHeight, 0.0f));
|
||||
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,
|
||||
@@ -176,7 +206,7 @@ namespace VRM10.Samples.MToon10Showcase
|
||||
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 = startOrigin;
|
||||
var floorLength = 10f * floorPrefab.transform.localScale.y;
|
||||
|
||||
@@ -1,40 +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;
|
||||
|
||||
[SerializeField] private RenderTexture _renderTarget;
|
||||
|
||||
public RenderTexture RenderTarget => _renderTarget;
|
||||
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;
|
||||
_camera.transform.position = new Vector3(topLeft.x + width / 2f, topLeft.y, topLeft.z - height / 2f);
|
||||
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;
|
||||
var texWidth = 480;
|
||||
var texHeight = (int)(texWidth / aspect);
|
||||
|
||||
if (texWidth * texHeight > 4096 * 4096) throw new Exception("Too many pixels to capture.");
|
||||
|
||||
_renderTarget = new RenderTexture(texWidth, texHeight, 16, RenderTextureFormat.ARGB32);
|
||||
_camera.targetTexture = _renderTarget;
|
||||
_camera.enabled = true;
|
||||
|
||||
// Capture
|
||||
_camera.Render();
|
||||
|
||||
_camera.enabled = false;
|
||||
_camera.targetTexture = null;
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:e47c917724578cc43b5506c17a27e9a0",
|
||||
"GUID:0aaf403bd13871a44b7127aef2695ff8"
|
||||
"GUID:0aaf403bd13871a44b7127aef2695ff8",
|
||||
"GUID:8d76e605759c3f64a957d63ef96ada7c"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
Reference in New Issue
Block a user