mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-07-24 11:40:45 -05:00
Merge pull request #1206 from ousttrue/fix/first_person_setup_async
[1.0] FirstPerson 初期化を整理
This commit is contained in:
commit
64af0d24e5
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
using VRMShaders;
|
||||
|
||||
namespace UniGLTF.MeshUtility
|
||||
{
|
||||
|
|
@ -35,6 +36,13 @@ namespace UniGLTF.MeshUtility
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// skip triangles that has weight for exclude, return valid triangle count
|
||||
/// </summary>
|
||||
/// <param name="triangles"></param>
|
||||
/// <param name="bws"></param>
|
||||
/// <param name="exclude"></param>
|
||||
/// <returns></returns>
|
||||
static int ExcludeTriangles(int[] triangles, BoneWeight[] bws, int[] exclude)
|
||||
{
|
||||
int count = 0;
|
||||
|
|
@ -116,8 +124,28 @@ namespace UniGLTF.MeshUtility
|
|||
return new ExcludeBoneIndex(b0, b1, b2, b3);
|
||||
}
|
||||
|
||||
public static Mesh CreateErasedMesh(Mesh src, int[] eraseBoneIndices)
|
||||
/// <summary>
|
||||
/// Erase triangles that has boneWeight for eraseBone
|
||||
/// </summary>
|
||||
/// <param name="indices"></param>
|
||||
/// <param name="boneWeights"></param>
|
||||
/// <param name="eraseBoneIndices"></param>
|
||||
/// <returns></returns>
|
||||
public static int[] GetExcludedIndices(int[] indices, BoneWeight[] boneWeights, int[] eraseBoneIndices)
|
||||
{
|
||||
var count = ExcludeTriangles(indices, boneWeights, eraseBoneIndices);
|
||||
var dst = new int[count];
|
||||
Array.Copy(indices, 0, dst, 0, count);
|
||||
return dst;
|
||||
}
|
||||
|
||||
public static async Task<Mesh> CreateErasedMeshAsync(Mesh src, int[] eraseBoneIndices, IAwaitCaller awaitCaller)
|
||||
{
|
||||
if (awaitCaller == null)
|
||||
{
|
||||
awaitCaller = new ImmediateCaller();
|
||||
}
|
||||
|
||||
/*
|
||||
Debug.LogFormat("{0} exclude: {1}",
|
||||
src.name,
|
||||
|
|
@ -140,16 +168,20 @@ namespace UniGLTF.MeshUtility
|
|||
mesh.subMeshCount = src.subMeshCount;
|
||||
for (int i = 0; i < src.subMeshCount; ++i)
|
||||
{
|
||||
var indices = src.GetIndices(i);
|
||||
var count = ExcludeTriangles(indices, mesh.boneWeights, eraseBoneIndices);
|
||||
var dst = new int[count];
|
||||
Array.Copy(indices, 0, dst, 0, count);
|
||||
var dst = await awaitCaller.Run(() => GetExcludedIndices(src.GetIndices(i), mesh.boneWeights, eraseBoneIndices));
|
||||
mesh.SetIndices(dst, MeshTopology.Triangles, i);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public static Mesh CreateErasedMesh(Mesh src, int[] eraseBoneIndices)
|
||||
{
|
||||
var task = CreateErasedMeshAsync(src, eraseBoneIndices, new ImmediateCaller());
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
public static IEnumerable<Transform> Ancestor(this Transform t)
|
||||
{
|
||||
yield return t;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@ namespace UniGLTF
|
|||
return loaded;
|
||||
}
|
||||
|
||||
public void AddRenderers(IEnumerable<Renderer> renderers)
|
||||
{
|
||||
_renderers.AddRange(renderers);
|
||||
}
|
||||
|
||||
public void ShowMeshes()
|
||||
{
|
||||
foreach (var r in Renderers)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
public static class Vrm10FirstPersonLayerSettings
|
||||
{
|
||||
public const int DEFAULT_FIRSTPERSON_ONLY_LAYER = 9;
|
||||
public const string FIRSTPERSON_ONLY_LAYER_NAME = "VRMFirstPersonOnly";
|
||||
|
||||
public const int DEFAULT_THIRDPERSON_ONLY_LAYER = 10;
|
||||
public const string THIRDPERSON_ONLY_LAYER_NAME = "VRMThirdPersonOnly";
|
||||
|
||||
public static int GetLayer(int? arg, string name, int fallback)
|
||||
{
|
||||
if (arg.HasValue)
|
||||
{
|
||||
return arg.Value;
|
||||
}
|
||||
var layer = LayerMask.NameToLayer(name);
|
||||
if (layer != -1)
|
||||
{
|
||||
return layer;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static int GetFirstPersonOnlyLayer(int? arg)
|
||||
{
|
||||
return GetLayer(arg, FIRSTPERSON_ONLY_LAYER_NAME, DEFAULT_FIRSTPERSON_ONLY_LAYER);
|
||||
}
|
||||
|
||||
public static int GetThirdPersonOnlyLayer(int? arg)
|
||||
{
|
||||
return GetLayer(arg, THIRDPERSON_ONLY_LAYER_NAME, DEFAULT_THIRDPERSON_ONLY_LAYER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8ca895b6d3e950a47a3166f832146d8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UniGLTF.MeshUtility;
|
||||
using UnityEngine;
|
||||
using VRMShaders;
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
|
|
@ -27,24 +29,6 @@ namespace UniVRM10
|
|||
}
|
||||
}
|
||||
|
||||
// If no layer names are set, use the default layer IDs.
|
||||
// Otherwise use the two Unity layers called "VRMFirstPersonOnly" and "VRMThirdPersonOnly".
|
||||
public static bool TriedSetupLayer = false;
|
||||
public static int FIRSTPERSON_ONLY_LAYER = 9;
|
||||
public static int THIRDPERSON_ONLY_LAYER = 10;
|
||||
|
||||
public static void SetupLayers()
|
||||
{
|
||||
if (!TriedSetupLayer)
|
||||
{
|
||||
TriedSetupLayer = true;
|
||||
int layer = LayerMask.NameToLayer("VRMFirstPersonOnly");
|
||||
FIRSTPERSON_ONLY_LAYER = (layer == -1) ? FIRSTPERSON_ONLY_LAYER : layer;
|
||||
layer = LayerMask.NameToLayer("VRMThirdPersonOnly");
|
||||
THIRDPERSON_ONLY_LAYER = (layer == -1) ? THIRDPERSON_ONLY_LAYER : layer;
|
||||
}
|
||||
}
|
||||
|
||||
static int[] GetBonesThatHasAncestor(SkinnedMeshRenderer smr, Transform ancestor)
|
||||
{
|
||||
var eraseBones = smr.bones
|
||||
|
|
@ -59,9 +43,9 @@ namespace UniVRM10
|
|||
// </summary>
|
||||
// <parameter>renderer: 元になるSkinnedMeshRenderer</parameter>
|
||||
// <parameter>eraseBones: 削除対象になるボーンのindex</parameter>
|
||||
private static SkinnedMeshRenderer CreateHeadlessMesh(SkinnedMeshRenderer renderer, int[] eraseBones)
|
||||
private async static Task<SkinnedMeshRenderer> CreateHeadlessMeshAsync(SkinnedMeshRenderer renderer, int[] eraseBones, IAwaitCaller awaitCaller)
|
||||
{
|
||||
var mesh = BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones);
|
||||
var mesh = await BoneMeshEraser.CreateErasedMeshAsync(renderer.sharedMesh, eraseBones, awaitCaller);
|
||||
|
||||
var go = new GameObject("_headless_" + renderer.name);
|
||||
var erased = go.AddComponent<SkinnedMeshRenderer>();
|
||||
|
|
@ -76,77 +60,111 @@ namespace UniVRM10
|
|||
|
||||
bool m_done;
|
||||
|
||||
/// <summary>
|
||||
/// 配下のモデルのレイヤー設定など
|
||||
/// </summary>
|
||||
public void Setup(GameObject go)
|
||||
async Task<SkinnedMeshRenderer> SetupRendererAsync(GameObject go, Transform FirstPersonBone, RendererFirstPersonFlags x,
|
||||
(int FirstPersonOnly, int ThirdPersonOnly) layer, IAwaitCaller awaitCaller = null)
|
||||
{
|
||||
SetupLayers();
|
||||
switch (x.FirstPersonFlag)
|
||||
{
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto:
|
||||
{
|
||||
if (x.GetRenderer(go.transform) is SkinnedMeshRenderer smr)
|
||||
{
|
||||
var eraseBones = GetBonesThatHasAncestor(smr, FirstPersonBone);
|
||||
if (eraseBones.Any())
|
||||
{
|
||||
// オリジナルのモデルを3人称用にする
|
||||
smr.gameObject.layer = layer.ThirdPersonOnly;
|
||||
|
||||
// 頭を取り除いた複製モデルを作成し、1人称用にする
|
||||
var headless = await CreateHeadlessMeshAsync(smr, eraseBones, awaitCaller);
|
||||
headless.enabled = false;
|
||||
headless.gameObject.layer = layer.FirstPersonOnly;
|
||||
headless.transform.SetParent(smr.transform, false);
|
||||
return headless;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 削除対象が含まれないので何もしない
|
||||
}
|
||||
}
|
||||
else if (x.GetRenderer(go.transform) is MeshRenderer mr)
|
||||
{
|
||||
if (mr.transform.Ancestors().Any(y => y == FirstPersonBone))
|
||||
{
|
||||
// 頭の子孫なので1人称では非表示に
|
||||
mr.gameObject.layer = layer.ThirdPersonOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 特に変更しない => 両方表示
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.firstPersonOnly:
|
||||
// 1人称のカメラでだけ描画されるようにする
|
||||
x.GetRenderer(go.transform).gameObject.layer = layer.FirstPersonOnly;
|
||||
break;
|
||||
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly:
|
||||
// 3人称のカメラでだけ描画されるようにする
|
||||
x.GetRenderer(go.transform).gameObject.layer = layer.ThirdPersonOnly;
|
||||
break;
|
||||
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both:
|
||||
// 特に何もしない。すべてのカメラで描画される
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup first person
|
||||
///
|
||||
/// * SetupLayers
|
||||
/// * Each renderer is set according to the first person flag. If the flag is `auto`, headless mesh creation will be performed. It's a heavy process.
|
||||
/// * If visible is false, the created headless mesh will be hidden.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="go"></param>
|
||||
/// <param name="firstPersonOnlyLayer"></param>
|
||||
/// <param name="thirdPersonOnlyLayer"></param>
|
||||
/// <param name="awaitCaller"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<SkinnedMeshRenderer>> SetupAsync(GameObject go, int? firstPersonOnlyLayer = default, int? thirdPersonOnlyLayer = default, IAwaitCaller awaitCaller = default)
|
||||
{
|
||||
if (awaitCaller == null)
|
||||
{
|
||||
awaitCaller = new ImmediateCaller();
|
||||
}
|
||||
|
||||
var layer = (
|
||||
Vrm10FirstPersonLayerSettings.GetFirstPersonOnlyLayer(firstPersonOnlyLayer),
|
||||
Vrm10FirstPersonLayerSettings.GetThirdPersonOnlyLayer(thirdPersonOnlyLayer));
|
||||
|
||||
var created = new List<SkinnedMeshRenderer>();
|
||||
if (m_done)
|
||||
{
|
||||
return;
|
||||
return created;
|
||||
}
|
||||
m_done = true;
|
||||
|
||||
var FirstPersonBone = go.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Head);
|
||||
foreach (var x in Renderers)
|
||||
{
|
||||
switch (x.FirstPersonFlag)
|
||||
var renderer = await SetupRendererAsync(go, FirstPersonBone, x, layer, awaitCaller);
|
||||
if (renderer)
|
||||
{
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto:
|
||||
{
|
||||
if (x.GetRenderer(go.transform) is SkinnedMeshRenderer smr)
|
||||
{
|
||||
var eraseBones = GetBonesThatHasAncestor(smr, FirstPersonBone);
|
||||
if (eraseBones.Any())
|
||||
{
|
||||
// オリジナルのモデルを3人称用にする
|
||||
smr.gameObject.layer = THIRDPERSON_ONLY_LAYER;
|
||||
|
||||
// 頭を取り除いた複製モデルを作成し、1人称用にする
|
||||
var headless = CreateHeadlessMesh(smr, eraseBones);
|
||||
headless.gameObject.layer = FIRSTPERSON_ONLY_LAYER;
|
||||
headless.transform.SetParent(smr.transform, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 削除対象が含まれないので何もしない
|
||||
}
|
||||
}
|
||||
else if (x.GetRenderer(go.transform) is MeshRenderer mr)
|
||||
{
|
||||
if (mr.transform.Ancestors().Any(y => y == FirstPersonBone))
|
||||
{
|
||||
// 頭の子孫なので1人称では非表示に
|
||||
mr.gameObject.layer = THIRDPERSON_ONLY_LAYER;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 特に変更しない => 両方表示
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.firstPersonOnly:
|
||||
// 1人称のカメラでだけ描画されるようにする
|
||||
x.GetRenderer(go.transform).gameObject.layer = FIRSTPERSON_ONLY_LAYER;
|
||||
break;
|
||||
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.thirdPersonOnly:
|
||||
// 3人称のカメラでだけ描画されるようにする
|
||||
x.GetRenderer(go.transform).gameObject.layer = THIRDPERSON_ONLY_LAYER;
|
||||
break;
|
||||
|
||||
case UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both:
|
||||
// 特に何もしない。すべてのカメラで描画される
|
||||
break;
|
||||
created.Add(renderer);
|
||||
}
|
||||
}
|
||||
return created;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
Assets/VRM10/Samples/VRM10FirstPersonSample.meta
Normal file
8
Assets/VRM10/Samples/VRM10FirstPersonSample.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 63991f54f0175cf4294770b968b5b456
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
namespace UniVRM10.FirstPersonSample
|
||||
{
|
||||
public class VRM10CanvasManager : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
public Button LoadVRMButton;
|
||||
|
||||
[SerializeField]
|
||||
public Button LoadBVHButton;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
LoadVRMButton = GameObject.FindObjectsOfType<Button>().FirstOrDefault(x => x.name == "LoadVRM");
|
||||
LoadBVHButton = GameObject.FindObjectsOfType<Button>().FirstOrDefault(x => x.name == "LoadBVH");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 58bd4ef7b3a863142ae9960c21106ba3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
#if UNITY_STANDALONE_WIN
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
|
||||
namespace UniVRM10.FirstPersonSample
|
||||
{
|
||||
public static class VRM10FileDialogForWindows
|
||||
{
|
||||
#if UNITY_STANDALONE_WIN
|
||||
#region GetOpenFileName
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public class OpenFileName
|
||||
{
|
||||
public int structSize = 0;
|
||||
public IntPtr dlgOwner = IntPtr.Zero;
|
||||
public IntPtr instance = IntPtr.Zero;
|
||||
public String filter = null;
|
||||
public String customFilter = null;
|
||||
public int maxCustFilter = 0;
|
||||
public int filterIndex = 0;
|
||||
public String file = null;
|
||||
public int maxFile = 0;
|
||||
public String fileTitle = null;
|
||||
public int maxFileTitle = 0;
|
||||
public String initialDir = null;
|
||||
public String title = null;
|
||||
public int flags = 0;
|
||||
public short fileOffset = 0;
|
||||
public short fileExtension = 0;
|
||||
public String defExt = null;
|
||||
public IntPtr custData = IntPtr.Zero;
|
||||
public IntPtr hook = IntPtr.Zero;
|
||||
public String templateName = null;
|
||||
public IntPtr reservedPtr = IntPtr.Zero;
|
||||
public int reservedInt = 0;
|
||||
public int flagsEx = 0;
|
||||
}
|
||||
|
||||
[DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
|
||||
/*
|
||||
public static bool GetOpenFileName1([In, Out] OpenFileName ofn)
|
||||
{
|
||||
return GetOpenFileName(ofn);
|
||||
}
|
||||
*/
|
||||
|
||||
[DllImport("Comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
|
||||
|
||||
static string Filter(params string[] filters)
|
||||
{
|
||||
return string.Join("\0", filters) + "\0";
|
||||
}
|
||||
public static string FileDialog(string title, params string[] extensions)
|
||||
{
|
||||
OpenFileName ofn = new OpenFileName();
|
||||
ofn.structSize = Marshal.SizeOf(ofn);
|
||||
|
||||
var filters = new List<string>();
|
||||
filters.Add("All Files"); filters.Add("*.*");
|
||||
foreach(var ext in extensions)
|
||||
{
|
||||
filters.Add(ext); filters.Add("*" + ext);
|
||||
}
|
||||
ofn.filter = Filter(filters.ToArray());
|
||||
ofn.filterIndex = 2;
|
||||
ofn.file = new string(new char[256]);
|
||||
ofn.maxFile = ofn.file.Length;
|
||||
ofn.fileTitle = new string(new char[64]);
|
||||
ofn.maxFileTitle = ofn.fileTitle.Length;
|
||||
ofn.initialDir = UnityEngine.Application.dataPath;
|
||||
ofn.title = title;
|
||||
//ofn.defExt = "PNG";
|
||||
ofn.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008;//OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST| OFN_ALLOWMULTISELECT|OFN_NOCHANGEDIR
|
||||
if (!GetOpenFileName(ofn))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ofn.file;
|
||||
}
|
||||
public static string SaveDialog(string title, string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
OpenFileName ofn = new OpenFileName();
|
||||
ofn.structSize = Marshal.SizeOf(ofn);
|
||||
ofn.filter = Filter("All Files", "*.*", extension, "*" + extension);
|
||||
ofn.filterIndex = 2;
|
||||
var chars = new char[256];
|
||||
var it = Path.GetFileName(path).GetEnumerator();
|
||||
for (int i = 0; i < chars.Length && it.MoveNext(); ++i)
|
||||
{
|
||||
chars[i] = it.Current;
|
||||
}
|
||||
ofn.file = new string(chars);
|
||||
ofn.maxFile = ofn.file.Length;
|
||||
ofn.fileTitle = new string(new char[64]);
|
||||
ofn.maxFileTitle = ofn.fileTitle.Length;
|
||||
ofn.initialDir = Path.GetDirectoryName(path);
|
||||
ofn.title = title;
|
||||
//ofn.defExt = "PNG";
|
||||
ofn.flags = 0x00000002 | 0x00000004; // OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
|
||||
if (!GetSaveFileName(ofn))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ofn.file;
|
||||
}
|
||||
#endregion
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2e637d7baec8ac742bcb7709db745193
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 056ca4a0e47b8b5408a1c557768e188b
|
||||
timeCreated: 1520832729
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using UnityEngine;
|
||||
using UniGLTF;
|
||||
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
public class VRM10LookTarget : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
public Transform Target;
|
||||
|
||||
[SerializeField]
|
||||
Vector3 m_offset = new Vector3(0, 0.05f, 0);
|
||||
|
||||
[SerializeField, Range(0, 3.0f)]
|
||||
float m_distance = 0.7f;
|
||||
|
||||
public VRM10OffsetOnTransform m_offsetTransform;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Target != m_offsetTransform.Transform)
|
||||
{
|
||||
m_offsetTransform = VRM10OffsetOnTransform.Create(Target);
|
||||
}
|
||||
|
||||
var target = m_offsetTransform.Transform;
|
||||
if (target != null)
|
||||
{
|
||||
var targetPosition = target.position + m_offset;
|
||||
transform.position = targetPosition + (m_offsetTransform.WorldMatrix.ExtractRotation() * Vector3.forward) * m_distance;
|
||||
transform.LookAt(targetPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b74319fb726e30c4ca99bd741af8bd3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
[Serializable]
|
||||
public struct VRM10OffsetOnTransform
|
||||
{
|
||||
public Transform Transform;
|
||||
public Matrix4x4 OffsetRotation;
|
||||
|
||||
public Matrix4x4 WorldMatrix
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Transform == null) return Matrix4x4.identity;
|
||||
return Transform.localToWorldMatrix * OffsetRotation;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 WorldForward
|
||||
{
|
||||
get
|
||||
{
|
||||
var m = WorldMatrix;
|
||||
return m.GetColumn(2); // zaxis
|
||||
}
|
||||
}
|
||||
|
||||
Matrix4x4 m_initialLocalMatrix;
|
||||
public void Setup()
|
||||
{
|
||||
if (Transform == null) return;
|
||||
m_initialLocalMatrix = Transform.parent.worldToLocalMatrix * Transform.localToWorldMatrix;
|
||||
}
|
||||
|
||||
public Matrix4x4 InitialWorldMatrix
|
||||
{
|
||||
get
|
||||
{
|
||||
return Transform.parent.localToWorldMatrix * m_initialLocalMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
public static VRM10OffsetOnTransform Create(Transform transform)
|
||||
{
|
||||
var coordinate = new VRM10OffsetOnTransform
|
||||
{
|
||||
Transform = transform
|
||||
};
|
||||
|
||||
if (transform != null)
|
||||
{
|
||||
coordinate.OffsetRotation = transform.worldToLocalMatrix.RotationToWorldAxis();
|
||||
}
|
||||
|
||||
return coordinate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 463fb7b24167d6a49a43bbcc4dfd42b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
#pragma warning disable 0414
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using UniGLTF;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.FirstPersonSample
|
||||
{
|
||||
public class VRM10RuntimeLoader : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Header("GUI")]
|
||||
VRM10CanvasManager m_canvas = default;
|
||||
|
||||
[SerializeField]
|
||||
VRM10LookTarget m_faceCamera = default;
|
||||
|
||||
[SerializeField, Header("loader")]
|
||||
UniHumanoid.HumanPoseTransfer m_source;
|
||||
|
||||
[SerializeField]
|
||||
UniHumanoid.HumanPoseTransfer m_target;
|
||||
|
||||
|
||||
void SetupTarget(UniHumanoid.HumanPoseTransfer m_target)
|
||||
{
|
||||
if (m_target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_target.Source = m_source;
|
||||
m_target.SourceType = UniHumanoid.HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseTransfer;
|
||||
|
||||
var animator = m_target.GetComponent<Animator>();
|
||||
if (animator != null)
|
||||
{
|
||||
if (m_faceCamera != null)
|
||||
{
|
||||
m_faceCamera.Target = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (m_canvas == null)
|
||||
{
|
||||
Debug.LogWarning("no canvas");
|
||||
return;
|
||||
}
|
||||
|
||||
m_canvas.LoadVRMButton.onClick.AddListener(LoadVRMClicked);
|
||||
m_canvas.LoadBVHButton.onClick.AddListener(LoadBVHClicked);
|
||||
}
|
||||
|
||||
async void LoadVRMClicked()
|
||||
{
|
||||
#if UNITY_STANDALONE_WIN
|
||||
var path = VRM10FileDialogForWindows.FileDialog("open VRM", ".vrm");
|
||||
#elif UNITY_EDITOR
|
||||
var path = UnityEditor.EditorUtility.OpenFilePanel("Open VRM", "", "vrm");
|
||||
#else
|
||||
var path = Application.dataPath + "/default.vrm";
|
||||
#endif
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var instance = await LoadAsync(path);
|
||||
|
||||
var root = instance.gameObject;
|
||||
root.transform.SetParent(transform, false);
|
||||
|
||||
// add motion
|
||||
var humanPoseTransfer = root.AddComponent<UniHumanoid.HumanPoseTransfer>();
|
||||
if (m_target != null)
|
||||
{
|
||||
GameObject.Destroy(m_target.gameObject);
|
||||
}
|
||||
m_target = humanPoseTransfer;
|
||||
SetupTarget(m_target);
|
||||
|
||||
instance.ShowMeshes();
|
||||
}
|
||||
|
||||
async Task<RuntimeGltfInstance> LoadAsync(string path)
|
||||
{
|
||||
var data = new GlbFileParser(path).Parse();
|
||||
if (!Vrm10Data.TryParseOrMigrate(data, true, out Vrm10Data vrm))
|
||||
{
|
||||
throw new System.Exception("vrm parse error !");
|
||||
}
|
||||
using (var loader = new Vrm10Importer(vrm))
|
||||
{
|
||||
var instance = await loader.LoadAsync();
|
||||
|
||||
// VR用 FirstPerson 設定
|
||||
var controller = instance.GetComponent<VRM10Controller>();
|
||||
var created = await controller.Vrm.FirstPerson.SetupAsync(controller.gameObject);
|
||||
instance.AddRenderers(created);
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadBVHClicked()
|
||||
{
|
||||
#if UNITY_STANDALONE_WIN
|
||||
var path = VRM10FileDialogForWindows.FileDialog("open BVH", ".bvh");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
LoadBvh(path);
|
||||
}
|
||||
#elif UNITY_EDITOR
|
||||
var path = UnityEditor.EditorUtility.OpenFilePanel("Open BVH", "", "bvh");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
LoadBvh(path);
|
||||
}
|
||||
#else
|
||||
LoadBvh(Application.dataPath + "/default.bvh");
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoadBvh(string path)
|
||||
{
|
||||
Debug.LogFormat("ImportBvh: {0}", path);
|
||||
var context = new UniHumanoid.BvhImporterContext();
|
||||
|
||||
context.Parse(path);
|
||||
context.Load();
|
||||
|
||||
if (m_source != null)
|
||||
{
|
||||
GameObject.Destroy(m_source.gameObject);
|
||||
}
|
||||
m_source = context.Root.GetComponent<UniHumanoid.HumanPoseTransfer>();
|
||||
|
||||
SetupTarget(m_target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bd151410f22d0f6409cf4b17c03a3c6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -14,8 +14,8 @@ TagManager:
|
|||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
- VRMFirstPersonOnly
|
||||
- VRMThirdPersonOnly
|
||||
-
|
||||
-
|
||||
-
|
||||
|
|
|
|||
|
|
@ -47,5 +47,5 @@
|
|||
href: vrm1/vrm1_expression.md
|
||||
- name: 🚧LookAt
|
||||
href: vrm1/vrm1_lookat.md
|
||||
- name: 🚧FirstPerson
|
||||
- name: FirstPerson
|
||||
href: vrm1/vrm1_firstperson.md
|
||||
|
|
|
|||
|
|
@ -1,5 +1,62 @@
|
|||
---
|
||||
title: 🚧FirstPerson
|
||||
weight: 50
|
||||
---
|
||||
The VR First Person settings.
|
||||
|
||||
1. Load
|
||||
2. Get VRM10Controller
|
||||
3. Call `controller.Vrm.FirstPerson.SetupAsync`
|
||||
4. Add the result of `controller.Vrm.FirstPerson.SetupAsync` to `RuntimeGltfInstance`
|
||||
5. ShowMeshes
|
||||
|
||||
```cs
|
||||
async Task<RuntimeGltfInstance> LoadAsync(string path)
|
||||
{
|
||||
var data = new GlbFileParser(path).Parse();
|
||||
if (!Vrm10Data.TryParseOrMigrate(data, true, out Vrm10Data vrm))
|
||||
{
|
||||
throw new System.Exception("vrm parse error !");
|
||||
}
|
||||
using (var loader = new Vrm10Importer(vrm))
|
||||
{
|
||||
// 1.
|
||||
var instance = await loader.LoadAsync();
|
||||
|
||||
// 2.
|
||||
var controller = instance.GetComponent<VRM10Controller>();
|
||||
|
||||
// 3.
|
||||
var created = await controller.Vrm.FirstPerson.SetupAsync(controller.gameObject);
|
||||
|
||||
// 4.
|
||||
instance.AddRenderers(created);
|
||||
|
||||
// 5.
|
||||
instance.ShowMeshes();
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Recommended camera layer settings
|
||||
|
||||
Suppose your scene has a camera that represents a head-mounted display and other cameras.
|
||||
|
||||
* FIRSTPERSON_ONLY_LAYER(The gameObject that specifies this layer disappears from other cameras)
|
||||
* THIRDPERSON_ONLY_LAYER(The gameObject that specifies this layer disappears from the head-mounted display)
|
||||
|
||||
Stops drawing the avatar from the head-mounted display perspective and makes it visible to other cameras.
|
||||
|
||||
Example: Stop drawing the avatar's head so you can see the front
|
||||
|
||||
VRM reserves layers named `VRMFirstPersonOnly` and` VRMThirdPersonOnly`.
|
||||
|
||||
Please set `VRMFirstPersonOnly` and` VRMThirdPersonOnly` to `Project Settings`-`Tags and Layers`.
|
||||
|
||||
In the sample, we assign `9` and` 10` to each.
|
||||
|
||||
# Specify layer at initialization
|
||||
|
||||
It can be specified with an additional argument.
|
||||
|
||||
```cs
|
||||
var created = await controller.Vrm.FirstPerson.SetupAsync(controller.gameObject, firstPersonOnlyLayer: 9, thirdPersonOnlyLayer: 10);
|
||||
```
|
||||
|
|
|
|||
|
|
@ -47,5 +47,5 @@
|
|||
href: vrm1/vrm1_expression.md
|
||||
- name: 🚧LookAt
|
||||
href: vrm1/vrm1_lookat.md
|
||||
- name: 🚧FirstPerson
|
||||
- name: FirstPerson
|
||||
href: vrm1/vrm1_firstperson.md
|
||||
|
|
|
|||
|
|
@ -1,5 +1,66 @@
|
|||
---
|
||||
title: 🚧FirstPerson
|
||||
weight: 50
|
||||
---
|
||||
# Runtime に FirstPerson 機能を有効にする
|
||||
|
||||
VR向け FirstPerson 設定の初期化手順です。
|
||||
|
||||
1. Load する
|
||||
2. VRM10Controller を取得する
|
||||
3. `controller.Vrm.FirstPerson.SetupAsync` を呼び出す
|
||||
4. `controller.Vrm.FirstPerson.SetupAsync` した結果新規に作成されたモデルを `RuntimeGltfInstance` に渡す
|
||||
5. ShowMeshes
|
||||
|
||||
```cs
|
||||
async Task<RuntimeGltfInstance> LoadAsync(string path)
|
||||
{
|
||||
var data = new GlbFileParser(path).Parse();
|
||||
if (!Vrm10Data.TryParseOrMigrate(data, true, out Vrm10Data vrm))
|
||||
{
|
||||
throw new System.Exception("vrm parse error !");
|
||||
}
|
||||
using (var loader = new Vrm10Importer(vrm))
|
||||
{
|
||||
// 1.
|
||||
var instance = await loader.LoadAsync();
|
||||
|
||||
// 2.
|
||||
var controller = instance.GetComponent<VRM10Controller>();
|
||||
|
||||
// 3.
|
||||
var created = await controller.Vrm.FirstPerson.SetupAsync(controller.gameObject);
|
||||
|
||||
// 4.
|
||||
instance.AddRenderers(created);
|
||||
|
||||
// 5.
|
||||
instance.ShowMeshes();
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# VRMの推奨する VR 向けのカメラ構成
|
||||
|
||||
ヘッドマウントディスプレイを表すカメラ と その他のカメラという2種類のカメラを想定ます。
|
||||
それぞれに対して、
|
||||
|
||||
* FIRSTPERSON_ONLY_LAYER(このレイヤーを指定した gameObject はその他のカメラから消えます)
|
||||
* THIRDPERSON_ONLY_LAYER(このレイヤーを指定した gameObject はヘッドマウントディスプレイから消えます)
|
||||
|
||||
を定義します。
|
||||
これにより、ヘッドマウント視点のアバターの描画を抑止しつつ、他者からは見えるようにします。
|
||||
|
||||
例: アバターの頭の描画を抑止して前が見えるようにする
|
||||
|
||||
VRMは、`VRMFirstPersonOnly` と `VRMThirdPersonOnly` という名前のレイヤーを予約しています。
|
||||
|
||||
`Project Settings` - `Tags and Layers` に `VRMFirstPersonOnly` と `VRMThirdPersonOnly` を
|
||||
設定してください。
|
||||
サンプルでは、それぞれに `9` と `10` を割り当ています。
|
||||
|
||||
# 初期化時に layer を明示する
|
||||
|
||||
追加の引数で指定できます。
|
||||
|
||||
```cs
|
||||
var created = await controller.Vrm.FirstPerson.SetupAsync(controller.gameObject, firstPersonOnlyLayer: 9, thirdPersonOnlyLayer: 10);
|
||||
```
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user