Merge pull request #1206 from ousttrue/fix/first_person_setup_async

[1.0] FirstPerson 初期化を整理
This commit is contained in:
ousttrue 2021-09-09 21:20:51 +09:00 committed by GitHub
commit 64af0d24e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2603 additions and 97 deletions

View File

@ -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;

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ca895b6d3e950a47a3166f832146d8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 63991f54f0175cf4294770b968b5b456
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58bd4ef7b3a863142ae9960c21106ba3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 056ca4a0e47b8b5408a1c557768e188b
timeCreated: 1520832729
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b74319fb726e30c4ca99bd741af8bd3c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 463fb7b24167d6a49a43bbcc4dfd42b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd151410f22d0f6409cf4b17c03a3c6e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -14,8 +14,8 @@ TagManager:
-
-
-
-
-
- VRMFirstPersonOnly
- VRMThirdPersonOnly
-
-
-

View File

@ -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

View File

@ -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);
```

View File

@ -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

View File

@ -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);
```