2using UnityEngine.Rendering;
3using UnityEngine.Rendering.Universal;
4using System.Collections.Generic;
5using UnityEngine.Events;
30 private enum CubemapIndex
40 [Header(
"Camera Settings")]
45 [SerializeField, Range(0.05f, 0.08f)]
private float interpupillaryDistance = 0.065f;
47 [SerializeField, Min(0.1f)]
private float cavernHeight = 2.0f;
49 [SerializeField, Min(0.1f)]
private float cavernRadius = 3.0f;
51 [SerializeField, Range(1.0f, 360.0f)]
private float cavernAngle = 270.0f;
53 [SerializeField, Range(-0.5f, 0.5f)]
private float cavernElevation = 0.0f;
55 [SerializeField]
private bool enableConvergence =
false;
57 [SerializeField]
private bool swapEyes =
false;
59 [Header(
"Head Tracking")]
61 [SerializeField]
private bool tetherEar =
true;
63 [SerializeField]
private bool clampHeadPosition =
true;
68 [SerializeField, Range(0.0f, 1.0f)]
private float clampHeadRatio = 0.9f;
73 [SerializeField, Tooltip(
"Should the CAVERN preview live update?")]
private bool livePreview =
true;
75 [Header(
"References (Do NOT edit!)")]
76 [SerializeField]
private Transform head;
77 [SerializeField]
private Camera eye;
78 [SerializeField]
private Camera guiCamera;
79 [SerializeField]
private AudioListener ear;
80 [SerializeField]
private Shader shader;
81 [SerializeField]
private Material previewMaterial;
84 private Material material =
null;
85 private RenderTexture[] cubemaps =
null;
86 private Mesh previewMesh =
null;
87 private RenderTexture previewTexture =
null;
88 private CavernRenderPass cavernRenderPass;
97 get => interpupillaryDistance;
100 if (0.05f <= value && 0.08f >= value)
103 interpupillaryDistance = value;
111 public float GetAspectRatio() {
return ((cavernAngle / 360.0f) * Mathf.PI * cavernRadius * 2.0f) / cavernHeight; }
112 public GameObject
GetHead() {
return head.gameObject; }
113 public GameObject
GetEye() {
return eye.gameObject; }
114 public GameObject
GetEar() {
return ear.gameObject; }
134 private void OnEnable()
136 RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
137 RenderPipelineManager.endCameraRendering += OnEndCameraRendering;
139 UnityEditor.SceneManagement.EditorSceneManager.sceneSaved += OnSceneSaved;
140 UnityEditor.EditorApplication.delayCall += OnEditorDelayCall;
144 private void OnDisable()
146 RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
147 RenderPipelineManager.endCameraRendering -= OnEndCameraRendering;
149 UnityEditor.SceneManagement.EditorSceneManager.sceneSaved -= OnSceneSaved;
150 UnityEditor.EditorApplication.delayCall -= OnEditorDelayCall;
159 CreatePreviewTexture();
160 cavernRenderPass =
new CavernRenderPass(material);
171 private void Update()
175 if (clampHeadPosition)
177 Vector2 horizontalPosition =
new Vector2(head.transform.localPosition.x, head.transform.localPosition.z);
178 if (horizontalPosition.sqrMagnitude > clampHeadRatio * clampHeadRatio * cavernRadius * cavernRadius)
180 horizontalPosition = horizontalPosition.normalized * clampHeadRatio * cavernRadius;
181 head.transform.localPosition =
new Vector3(horizontalPosition.x, head.transform.localPosition.y, horizontalPosition.y);
187 ear.gameObject.transform.position = head.transform.position;
188 ear.gameObject.transform.rotation = head.transform.rotation;
193 if (UnityEditor.EditorApplication.isPlaying || livePreview)
196 if (previewTexture !=
null && material !=
null)
198 Graphics.Blit(
null, previewTexture, material);
210 private void GetRenderFaces(out
int monoMask, out
int northMask, out
int southMask, out
int eastMask, out
int westMask)
213 const int rightMask = 1 << (int)CubemapFace.PositiveX;
214 const int leftMask = 1 << (
int)CubemapFace.NegativeX;
215 const int topMask = 1 << (int)CubemapFace.PositiveY;
216 const int bottomMask = 1 << (
int)CubemapFace.NegativeY;
217 const int frontMask = 1 << (int)CubemapFace.PositiveZ;
218 const int backMask = 1 << (
int)CubemapFace.NegativeZ;
221 monoMask = 0; northMask = 0; southMask = 0; eastMask = 0; westMask = 0;
223 Vector3 headPosition = head.transform.localPosition;
260 Vector3 southWestBoundary = Vector3.zero;
261 Vector3 northEastBoundary = Vector3.zero;
262 Vector3 northWestBoundary = Vector3.zero;
263 Vector3 southEastBoundary = Vector3.zero;
286 List<float> xIntersectSouthWestToNorthEast = MathsUtil.SolveQuadraticEquation(
288 headPosition.x + headPosition.z,
289 -0.5f * (cavernRadius * cavernRadius - headPosition.x * headPosition.x - headPosition.z * headPosition.z));
292 if (xIntersectSouthWestToNorthEast.Count == 1)
294 northEastBoundary =
new Vector3(xIntersectSouthWestToNorthEast[0], 0.0f, xIntersectSouthWestToNorthEast[0]);
295 southWestBoundary =
new Vector3(xIntersectSouthWestToNorthEast[0], 0.0f, xIntersectSouthWestToNorthEast[0]);
298 else if (xIntersectSouthWestToNorthEast.Count == 2)
300 northEastBoundary =
new Vector3(xIntersectSouthWestToNorthEast[1], 0.0f, xIntersectSouthWestToNorthEast[1]);
301 southWestBoundary =
new Vector3(xIntersectSouthWestToNorthEast[0], 0.0f, xIntersectSouthWestToNorthEast[0]);
305 List<float> xIntersectNorthWestToSouthEast = MathsUtil.SolveQuadraticEquation(
307 headPosition.x - headPosition.z,
308 -0.5f * (cavernRadius * cavernRadius - headPosition.x * headPosition.x - headPosition.z * headPosition.z));
309 if (xIntersectNorthWestToSouthEast.Count == 1)
311 northWestBoundary =
new Vector3(xIntersectNorthWestToSouthEast[0], 0.0f, -xIntersectNorthWestToSouthEast[0]);
312 southEastBoundary =
new Vector3(xIntersectNorthWestToSouthEast[0], 0.0f, -xIntersectNorthWestToSouthEast[0]);
314 else if (xIntersectNorthWestToSouthEast.Count == 2)
316 northWestBoundary =
new Vector3(xIntersectNorthWestToSouthEast[0], 0.0f, -xIntersectNorthWestToSouthEast[0]);
317 southEastBoundary =
new Vector3(xIntersectNorthWestToSouthEast[1], 0.0f, -xIntersectNorthWestToSouthEast[1]);
325 if (xIntersectSouthWestToNorthEast.Count == 0 && xIntersectNorthWestToSouthEast.Count == 0)
340 if (0.0f < headPosition.z &&
341 Mathf.Abs(headPosition.x) < Mathf.Abs(headPosition.z))
343 monoMask |= backMask;
344 eastMask |= backMask;
345 westMask |= backMask;
362 if (headPosition.z < 0.0f &&
363 Mathf.Abs(headPosition.x) < Mathf.Abs(headPosition.z))
365 monoMask |= frontMask;
366 eastMask |= frontMask;
367 westMask |= frontMask;
372 if (headPosition.x < 0.0f &&
373 Mathf.Abs(headPosition.z) < Mathf.Abs(headPosition.x))
375 monoMask |= rightMask;
376 northMask |= rightMask;
377 southMask |= rightMask;
382 if (headPosition.x > 0.0f &&
383 Mathf.Abs(headPosition.z) < Mathf.Abs(headPosition.x))
385 monoMask |= leftMask;
386 northMask |= leftMask;
387 southMask |= leftMask;
393 if (xIntersectSouthWestToNorthEast.Count > 0 && xIntersectNorthWestToSouthEast.Count == 0)
410 if (Vector3.Dot(
new Vector3(1.0f, 1.0f),
new Vector2(headPosition.x, headPosition.z)) > 1.0f)
412 monoMask |= (backMask | leftMask);
413 eastMask |= backMask;
414 westMask |= backMask;
415 northMask |= leftMask;
416 southMask |= leftMask;
435 if (Vector3.Dot(
new Vector3(-1.0f, -1.0f),
new Vector2(headPosition.x, headPosition.z)) > 1.0f)
437 monoMask = (frontMask | rightMask);
438 eastMask |= frontMask;
439 westMask |= frontMask;
440 northMask |= rightMask;
441 southMask |= rightMask;
447 if (xIntersectSouthWestToNorthEast.Count == 0 && xIntersectNorthWestToSouthEast.Count > 0)
450 if (Vector3.Dot(
new Vector3(1.0f, -1.0f),
new Vector2(headPosition.x, headPosition.z)) > 1.0f)
452 monoMask = (frontMask | leftMask);
453 eastMask |= frontMask;
454 westMask |= frontMask;
455 northMask |= leftMask;
456 southMask |= leftMask;
461 if (Vector3.Dot(
new Vector3(-1.0f, 1.0f),
new Vector2(headPosition.x, headPosition.z)) > 1.0f)
463 monoMask = (backMask | rightMask);
464 eastMask |= backMask;
465 westMask |= backMask;
466 northMask |= rightMask;
467 southMask |= rightMask;
476 float screenTop = cavernElevation + cavernHeight - headPosition.y;
477 float screenBottom = cavernElevation - headPosition.y;
478 Vector3 headOffset =
new Vector3(headPosition.x, 0.0f, headPosition.z);
481 monoMask |= frontMask;
482 westMask |= frontMask | (enableConvergence ? rightMask : 0);
483 eastMask |= frontMask | (enableConvergence ? leftMask : 0);
486 if (Vector3.Angle(headOffset + southWestBoundary, Vector3.forward) < cavernAngle * 0.5f ||
487 Vector3.Angle(headOffset + southEastBoundary, Vector3.forward) < cavernAngle * 0.5f)
489 monoMask |= backMask;
490 eastMask |= backMask;
491 westMask |= backMask;
495 if (Vector3.Angle(headOffset + northEastBoundary, Vector3.forward) < cavernAngle * 0.5f ||
496 Vector3.Angle(headOffset + southEastBoundary, Vector3.forward) < cavernAngle * 0.5f)
498 monoMask |= rightMask;
499 northMask |= rightMask | (enableConvergence ? backMask : 0);
500 southMask |= rightMask | (enableConvergence ? frontMask : 0);
504 if (Vector3.Angle(headOffset + northWestBoundary, Vector3.forward) < cavernAngle * 0.5f ||
505 Vector3.Angle(headOffset + southWestBoundary, Vector3.forward) < cavernAngle * 0.5f)
507 monoMask |= leftMask;
508 southMask |= leftMask | (enableConvergence ? frontMask : 0);
509 northMask |= leftMask | (enableConvergence ? backMask : 0);
513 if (Mathf.Abs(northEastBoundary.z) < Mathf.Abs(screenTop) ||
514 Mathf.Abs(northWestBoundary.z) < Mathf.Abs(screenTop) ||
515 Mathf.Abs(southEastBoundary.z) < Mathf.Abs(screenTop) ||
516 Mathf.Abs(southWestBoundary.z) < Mathf.Abs(screenTop))
522 if (Mathf.Abs(northEastBoundary.z) < Mathf.Abs(screenBottom) ||
523 Mathf.Abs(northWestBoundary.z) < Mathf.Abs(screenBottom) ||
524 Mathf.Abs(southEastBoundary.z) < Mathf.Abs(screenBottom) ||
525 Mathf.Abs(southWestBoundary.z) < Mathf.Abs(screenBottom))
527 monoMask |= bottomMask;
528 eastMask |= bottomMask;
529 westMask |= bottomMask;
531 if (Mathf.Abs(northEastBoundary.x) < Mathf.Abs(screenTop) ||
532 Mathf.Abs(southEastBoundary.x) < Mathf.Abs(screenTop) ||
533 Mathf.Abs(northWestBoundary.x) < Mathf.Abs(screenTop) ||
534 Mathf.Abs(southWestBoundary.x) < Mathf.Abs(screenTop))
537 northMask |= topMask;
538 southMask |= topMask;
540 if (Mathf.Abs(northEastBoundary.x) < Mathf.Abs(screenBottom) ||
541 Mathf.Abs(southEastBoundary.x) < Mathf.Abs(screenBottom) ||
542 Mathf.Abs(northWestBoundary.x) < Mathf.Abs(screenBottom) ||
543 Mathf.Abs(southWestBoundary.x) < Mathf.Abs(screenBottom))
545 monoMask |= bottomMask;
546 northMask |= bottomMask;
547 southMask |= bottomMask;
551 private void RenderEyes()
555 int monoMask = 0;
int northMask = 0;
int southMask = 0;
int eastMask = 0;
int westMask = 0;
556 GetRenderFaces(out monoMask, out northMask, out southMask, out eastMask, out westMask);
560 eye.stereoSeparation = 0.0f;
561 eye.transform.rotation = gameObject.transform.rotation;
562 eye.transform.localPosition = Vector3.zero;
563 eye.RenderToCubemap(cubemaps[(
int)CubemapIndex.North], monoMask, Camera.MonoOrStereoscopicEye.Left);
566 eye.stereoSeparation = 0.0f;
567 eye.transform.rotation = gameObject.transform.rotation;
568 eye.transform.localPosition =
new Vector3(0.0f, 0.0f, interpupillaryDistance * 0.5f);
569 eye.RenderToCubemap(cubemaps[(
int)CubemapIndex.North], northMask, Camera.MonoOrStereoscopicEye.Left);
570 eye.transform.localPosition =
new Vector3(0.0f, 0.0f, interpupillaryDistance * -0.5f);
571 eye.RenderToCubemap(cubemaps[(
int)CubemapIndex.South], southMask, Camera.MonoOrStereoscopicEye.Right);
572 eye.transform.localPosition =
new Vector3(interpupillaryDistance * 0.5f, 0.0f, 0.0f);
573 eye.RenderToCubemap(cubemaps[(
int)CubemapIndex.East], eastMask, Camera.MonoOrStereoscopicEye.Right);
574 eye.transform.localPosition =
new Vector3(interpupillaryDistance * -0.5f, 0.0f, 0.0f);
575 eye.RenderToCubemap(cubemaps[(
int)CubemapIndex.West], westMask, Camera.MonoOrStereoscopicEye.Left);
576 eye.transform.localPosition = Vector3.zero;
581 material.SetFloat(
"_CavernHeight", cavernHeight);
582 material.SetFloat(
"_CavernRadius", cavernRadius);
583 material.SetFloat(
"_CavernAngle", cavernAngle);
584 material.SetFloat(
"_CavernElevation", cavernElevation);
587 material.SetVector(
"_HeadPosition", head.transform.localPosition);
590 material.SetInteger(
"_EnableStereoscopic", stereoMode ==
StereoscopicMode.Stereo ? 1 : 0);
591 material.SetInteger(
"_EnableConvergence", enableConvergence ? 1 : 0);
592 material.SetFloat(
"_InterpupillaryDistance", interpupillaryDistance);
593 material.SetInteger(
"_SwapEyes", swapEyes ? 1 : 0);
596 private void CreateCubemaps()
598 cubemaps =
new RenderTexture[(int)CubemapIndex.Num];
599 for (
int i = 0; i < (int)CubemapIndex.Num; ++i)
601 cubemaps[i] =
new RenderTexture((
int)cubemapResolution, (
int)cubemapResolution, 32, RenderTextureFormat.ARGB32);
602 cubemaps[i].dimension = TextureDimension.Cube;
603 cubemaps[i].wrapMode = TextureWrapMode.Clamp;
607 private void CreateMaterial()
609 material =
new Material(shader);
610 material.SetTexture(
"_CubemapNorth", cubemaps[(
int)CubemapIndex.North]);
611 material.SetTexture(
"_CubemapSouth", cubemaps[(
int)CubemapIndex.South]);
612 material.SetTexture(
"_CubemapEast", cubemaps[(
int)CubemapIndex.East]);
613 material.SetTexture(
"_CubemapWest", cubemaps[(
int)CubemapIndex.West]);
614 cavernRenderPass?.SetMaterial(material);
620 Mesh mesh =
new Mesh();
622 int numPanels = Mathf.Max(1, (
int)(cavernAngle / 10.0f));
623 int numVertices = (numPanels + 1) * 2;
625 Vector3[] positions =
new Vector3[numVertices];
626 Vector3[] normals =
new Vector3[numVertices];
627 Vector2[] uvs =
new Vector2[numVertices];
628 int[] indices =
new int[numPanels * 6];
632 float cavernBottomHeight = cavernElevation;
633 float cavernTopHeight = cavernHeight + cavernElevation;
635 float topUV = (previewEye ==
PreviewEye.Left) ? 1.0f : 0.5f;
636 float bottomUV = (previewEye ==
PreviewEye.Left) ? 0.5f : 0.0f;
638 float deltaAngle = cavernAngle / (float)numPanels;
641 for (
int i = 0; i <= numPanels; i++)
643 float ratio = (float)i / (
float)numPanels;
644 float currAngle = (ratio - 0.5f) * cavernAngle;
647 float directionX = Mathf.Sin(currAngle * Mathf.Deg2Rad);
648 float directionZ = Mathf.Cos(currAngle * Mathf.Deg2Rad);
650 positions[i * 2] =
new Vector3(cavernRadius * directionX, cavernTopHeight, cavernRadius * directionZ);
651 normals[i * 2] =
new Vector3(cavernRadius * directionX, 0.0f, cavernRadius * directionZ);
652 uvs[i * 2] =
new Vector2((
float)i / (
float)numPanels, topUV);
654 positions[i * 2 + 1] =
new Vector3(cavernRadius * directionX, cavernBottomHeight, cavernRadius * directionZ);
655 normals[i * 2 + 1] =
new Vector3(cavernRadius * directionX, 0.0f, cavernRadius * directionZ);
656 uvs[i * 2 + 1] =
new Vector2((
float)i / (
float)numPanels, bottomUV);
662 for (
int i = 0; i < numPanels; ++i)
665 indices[i * 6] = i * 2;
666 indices[i * 6 + 1] = i * 2 + 2;
667 indices[i * 6 + 2] = i * 2 + 1;
670 indices[i * 6 + 3] = i * 2 + 1;
671 indices[i * 6 + 4] = i * 2 + 2;
672 indices[i * 6 + 5] = i * 2 + 3;
675 mesh.name =
"Cavern Mesh";
676 mesh.vertices = positions;
677 mesh.normals = normals;
679 mesh.triangles = indices;
686 private void CreatePreviewMesh()
689 previewMesh.name =
"Cavern Preview Mesh";
693 private void CreatePreviewTexture()
695 previewTexture =
new RenderTexture((
int)previewResolution, (
int)previewResolution, 32, RenderTextureFormat.ARGB32);
696 previewTexture.dimension = TextureDimension.Tex2D;
697 previewTexture.wrapMode = TextureWrapMode.Clamp;
700 private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
703 if (camera == guiCamera)
705 camera.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(cavernRenderPass);
709 private void OnEndCameraRendering(ScriptableRenderContext context, Camera camera) { }
712 private void OnValidate()
721 private void OnSceneSaved(UnityEngine.SceneManagement.Scene scene)
726 private void OnEditorDelayCall()
731 private void OnDrawGizmos()
733 if (previewMaterial ==
null)
735 Debug.LogAssertion(
"CavernRenderer: Preview material cannot be null!");
737 previewMaterial.SetPass(0);
738 previewMaterial.mainTexture = livePreview ? previewTexture :
null;
741 Graphics.DrawMeshNow(previewMesh, transform.position, transform.rotation);
float GetCavernElevation()
GameObject GetGUICamera()
void SetStereoscopicMode(StereoscopicMode mode)
UnityEvent settingsChanged
CubemapResolution GetCubemapResolution()
StereoscopicMode GetStereoscopicMode()