Files
Studio/Assets/Scripts/ExternalAssets/RuntimePreviewGenerator/RuntimePreviewGenerator.cs

816 lines
32 KiB
C#

//#define DEBUG_BOUNDS
using System;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_2018_2_OR_NEWER
using UnityEngine.Rendering;
#endif
using Object = UnityEngine.Object;
public static class RuntimePreviewGenerator
{
private class CameraSetup
{
private Vector3 position;
private Quaternion rotation;
private Color backgroundColor;
private bool orthographic;
private float orthographicSize;
private float nearClipPlane;
private float farClipPlane;
private float aspect;
private int cullingMask;
private CameraClearFlags clearFlags;
private RenderTexture targetTexture;
//RuntimePreviewGenerator 의 설정값을 가져오는 메서드
public void GetSetup( Camera camera )
{
position = camera.transform.position;
rotation = camera.transform.rotation;
backgroundColor = camera.backgroundColor;
orthographic = camera.orthographic;
orthographicSize = camera.orthographicSize;
nearClipPlane = camera.nearClipPlane;
farClipPlane = camera.farClipPlane;
aspect = camera.aspect;
cullingMask = camera.cullingMask;
clearFlags = camera.clearFlags;
targetTexture = camera.targetTexture;
}
//카메라의 설정값을 RuntimePreviewGenerator 의 설정값으로 적용하는 메서드
public void ApplySetup( Camera camera )
{
camera.transform.position = position;
camera.transform.rotation = rotation;
camera.backgroundColor = backgroundColor;
camera.orthographic = orthographic;
camera.orthographicSize = orthographicSize;
camera.aspect = aspect;
camera.cullingMask = cullingMask;
camera.clearFlags = clearFlags;
if( nearClipPlane < camera.farClipPlane )
{
camera.nearClipPlane = nearClipPlane;
camera.farClipPlane = farClipPlane;
}
else
{
camera.farClipPlane = farClipPlane;
camera.nearClipPlane = nearClipPlane;
}
camera.targetTexture = targetTexture;
targetTexture = null;
}
}
private static int PREVIEW_LAYER => LayerMask.NameToLayer("ThumbnailObject");
private static Vector3 PREVIEW_POSITION = new Vector3( -250f, -250f, -250f );
private static Camera renderCamera;//모델을 렌더링 하는 카메라
private static readonly CameraSetup cameraSetup = new CameraSetup();//카메라의 설정 값
private static readonly Vector3[] boundingBoxPoints = new Vector3[8];
private static readonly Vector3[] localBoundsMinMax = new Vector3[2];
private static readonly List<Renderer> renderersList = new List<Renderer>( 64 );
private static readonly List<int> layersList = new List<int>( 64 );
private static Camera m_internalCamera = null;
private static Camera InternalCamera
{
get
{
if( m_internalCamera == null )
{
m_internalCamera = new GameObject( "ModelPreviewGeneratorCamera" ).AddComponent<Camera>();
m_internalCamera.enabled = false;
m_internalCamera.nearClipPlane = 0.01f;
m_internalCamera.cullingMask = 1 << PREVIEW_LAYER;
m_internalCamera.gameObject.hideFlags = HideFlags.HideAndDontSave;
}
return m_internalCamera;
}
}
private static Camera m_previewRenderCamera;
public static Camera PreviewRenderCamera
{
get { return m_previewRenderCamera; }
set { m_previewRenderCamera = value; }
}
private static Vector3 m_previewDirection = new Vector3( -0.57735f, -0.57735f, -0.57735f ); // Normalized (-1,-1,-1)
public static Vector3 PreviewDirection
{
get { return m_previewDirection; }
set { m_previewDirection = value.normalized; }
}
private static float m_padding;
public static float Padding
{
get { return m_padding; }
set { m_padding = Mathf.Clamp( value, -0.25f, 0.25f ); }
}
private static Color m_backgroundColor = Color.white;
public static Color BackgroundColor
{
get { return m_backgroundColor; }
set { m_backgroundColor = value; }
}
private static bool m_orthographicMode = false;
public static bool OrthographicMode
{
get { return m_orthographicMode; }
set { m_orthographicMode = value; }
}
private static bool m_useLocalBounds = false;
public static bool UseLocalBounds
{
get { return m_useLocalBounds; }
set { m_useLocalBounds = value; }
}
private static float m_renderSupersampling = 1f;
public static float RenderSupersampling
{
get { return m_renderSupersampling; }
set { m_renderSupersampling = Mathf.Max( value, 0.1f ); }
}
private static bool m_markTextureNonReadable = true;
public static bool MarkTextureNonReadable
{
get { return m_markTextureNonReadable; }
set { m_markTextureNonReadable = value; }
}
public static Texture2D GenerateModelPreview( Transform model, int width = 64, int height = 64, bool shouldCloneModel = false, bool shouldIgnoreParticleSystems = true )
{
return GenerateModelPreviewInternal( model, null, null, width, height, shouldCloneModel, shouldIgnoreParticleSystems );
}
#if UNITY_2018_2_OR_NEWER
#endif
private static Texture2D GenerateModelPreviewInternal( Transform model, Shader shader, string replacementTag, int width, int height, bool shouldCloneModel, bool shouldIgnoreParticleSystems, Action<Texture2D> asyncCallback = null )
{
//만약 model 이 존재하지 않을 경우
if( !model )
{
#if UNITY_2018_2_OR_NEWER
//만약 asyncCallback(비동기 호출)이 없을 경우
if( asyncCallback != null )
//asynCallback 을 null 로 호출
asyncCallback( null );
#endif
//null 을 반환
return null;
}
Texture2D result = null;
//만약 model이 속한 장면이 유효하지 않거나, model 이 장면에 로드되지 않았을 경우
if( !model.gameObject.scene.IsValid() || !model.gameObject.scene.isLoaded )
shouldCloneModel = true;//모델의 복제 여부를 true로 변경
Transform previewObject;
//만약 shouldCloneModel(모델의 복제 여부)가 true 인 경우
if( shouldCloneModel )
{
//모델을 복제합니다.
//복제된 모델은 계층 구조에 표시되지 않고, 장면에 저장되지 않습니다.
previewObject = Object.Instantiate( model, null, false );
previewObject.gameObject.hideFlags = HideFlags.HideAndDontSave;
}
//그 이외의 경우
else
{
previewObject = model;
//layerList 를 초기화
//GetLayerRecursively 호출 - model과 model 의 모든 자식의 layer를 layerList 에 추가
layersList.Clear();
GetLayerRecursively( previewObject );
}
bool isStatic = IsStatic( model );//model 과 model 의 모든 자식중 정적객체가 있는지 여부
bool wasActive = previewObject.gameObject.activeSelf;
Vector3 prevPos = previewObject.position;
Quaternion prevRot = previewObject.rotation;
#if UNITY_2018_2_OR_NEWER
bool asyncOperationStarted = false; //비동기 작업이 시작되었는지 여부
#endif
try
{
//카메라를 설정하는 메서드 호출
//previewObject의 layer 를 PREVIEW_LAYER로 설정
SetupCamera();
SetLayerRecursively( previewObject );
//만약 정적객체가 없을 경우
if( !isStatic )
{
//previewObject 의 position, rotation 을 설정
previewObject.position = PREVIEW_POSITION;
previewObject.rotation = Quaternion.identity;
}
//만약 previewObject 가 활성화 되어있지 않은 경우
if( !wasActive )
//previewObject 를 활성화
previewObject.gameObject.SetActive( true );
Quaternion cameraRotation = Quaternion.LookRotation( previewObject.rotation * m_previewDirection, previewObject.up );
Bounds previewBounds = new Bounds();
//만약 경계 계산 메서드를 통해서, 경계를 가져온 경우
if (!CalculateBounds(previewObject, shouldIgnoreParticleSystems, cameraRotation, out previewBounds))
{
#if UNITY_2018_2_OR_NEWER
if (asyncCallback != null)
asyncCallback(null);
#endif
//null 을 반환
return null;
}
//renderCamera 의 가로,세로 비율을 조정
//renderCamera 의 회전값을 cameraRoatation 으로 지정
renderCamera.aspect = (float) width / height;
renderCamera.transform.rotation = cameraRotation;
//카메라 위치 계산 메서드 호출
CalculateCameraPosition( renderCamera, previewBounds, m_padding );
//카메라에서 멀리 떨어진 클리핑 평면의 거리 지정
renderCamera.farClipPlane = ( renderCamera.transform.position - previewBounds.center ).magnitude + ( m_useLocalBounds ? ( previewBounds.extents.z * 1.01f ) : previewBounds.size.magnitude );
//activeRT = 현재 활성화 된 텍스쳐
RenderTexture activeRT = RenderTexture.active;
RenderTexture renderTexture = null;
try
{
//supersampledWidth = width * m_renderSupersampling 결과 값 반올림
//supersampledheight = height * m_renderSupersampling 결과 값 반올림
int supersampledWidth = Mathf.RoundToInt( width * m_renderSupersampling );
int supersampledHeight = Mathf.RoundToInt( height * m_renderSupersampling );
//너비 : supersampledWidth, 높이 : supersampledHeight, 깊이 16의 임시 렌더 텍스처를 할당
renderTexture = RenderTexture.GetTemporary( supersampledWidth, supersampledHeight, 16 );
//현재 활성화 된 텍스처 = renderTexture
RenderTexture.active = renderTexture;
//만약 배경색의 알파 값이 1보다 작으면 현재 렌더링 버퍼를 지움
if ( m_backgroundColor.a < 1f )
GL.Clear( true, true, m_backgroundColor );
//대상 렌더링 텍스처를 renderTexture 로 할당
renderCamera.targetTexture = renderTexture;
//만약 shader 가 없는 경우 카메라를 렌더링
if( !shader )
renderCamera.Render();
//이외의 경우 셰이더 교체로 카메라를 렌더링
else
renderCamera.RenderWithShader( shader, replacementTag ?? string.Empty );
//대상 렌더링 텍스처 = null
renderCamera.targetTexture = null;
//만약 초본의 너비가 현재 너비와 맞지 않거나, 초본의 높이가 현재 높이와 맞지 않는 경우
if( supersampledWidth != width || supersampledHeight != height )
{
RenderTexture _renderTexture = null;
try
{
//현재 너비와 높이에 맞는 임시 렌더 텍스쳐 생성
_renderTexture = RenderTexture.GetTemporary( width, height, 16 );
//현재 활성화 된 텍스처 = _renderTexture
RenderTexture.active = _renderTexture;
//만약 배경색의 알파 값이 1보다 작으면 현재 렌더링 버퍼를 지움
if ( m_backgroundColor.a < 1f )
GL.Clear( true, true, m_backgroundColor );
//셰이더를 사용하여 텍스처의 픽셀 데이터를 렌더 텍스처로 복사
Graphics.Blit( renderTexture, _renderTexture );
}
finally
{
//만약 _renderTexture 가 존재한다면 GetTemporary 로 할당된 임시 텍스처를 해제
if ( _renderTexture )
{
RenderTexture.ReleaseTemporary( renderTexture );
renderTexture = _renderTexture;
}
}
}
#if UNITY_2018_2_OR_NEWER
//비동기 호출 이벤트가 없는 경우
if (asyncCallback != null)
{
//GPU 리소스에서 데이터(renderTexture)를 비동기적으로 검색
AsyncGPUReadback.Request(renderTexture, 0, m_backgroundColor.a < 1f ? TextureFormat.RGBA32 : TextureFormat.RGB24, (asyncResult) =>
{
try
{
//너비, 높이, 배경 색을 지정한 새로운 텍스처 생성
result = new Texture2D(width, height, m_backgroundColor.a < 1f ? TextureFormat.RGBA32 : TextureFormat.RGB24, false);
//요청에 오류가 발생한 경우 CPU 메모리의 텍스처에 대한 픽셀 데이터를 설정
if (!asyncResult.hasError)
result.LoadRawTextureData(asyncResult.GetData<byte>());
//이외의 경우
else
{
Debug.LogWarning("Async thumbnail request failed, falling back to conventional method");
//_activeRT = 현재 활성화 된 텍스처
RenderTexture _activeRT = RenderTexture.active;
try
{
//현재 활성화 된 텍스처를 renderTexture 로 할당
RenderTexture.active = renderTexture;
//GPU의 현재 활성 렌더 대상에서 픽셀 색상의 직사각형 영역을 복사 하고 이를 CPU의 위치에 있는 텍스처를 사용
result.ReadPixels(new Rect(0f, 0f, width, height), 0, 0, false);
}
finally
{
//현재 활성화 된 텍스처를 _activeRT 로 할당
RenderTexture.active = _activeRT;
}
}
//CPU 텍스처의 변경 사항을 GPU에 복사
result.Apply(false, m_markTextureNonReadable);
asyncCallback(result);
}
finally
{
//만약 renderTexture 가 존재할 경우 GetTemporary 로 할당된 임시 텍스처를 해제
if (renderTexture)
RenderTexture.ReleaseTemporary(renderTexture);
}
});
//비동기 작업이 시작되었는지 여부를 true 로 변경
asyncOperationStarted = true;
}
//이외의 경우
else
#endif
{
//너비, 높이, 배경 색을 지정한 새로운 텍스처 생성
result = new Texture2D( width, height, m_backgroundColor.a < 1f ? TextureFormat.RGBA32 : TextureFormat.RGB24, false );
//GPU의 현재 활성 렌더 대상에서 픽셀 색상의 직사각형 영역을 복사 하고 이를 CPU의 위치에 있는 텍스처를 사용
result.ReadPixels( new Rect( 0f, 0f, width, height ), 0, 0, false );
//CPU 텍스처의 변경 사항을 GPU에 복사
result.Apply( false, m_markTextureNonReadable );
}
}
finally
{
//현재 활성화 된 텍스처를 activeRT 로 할당
RenderTexture.active = activeRT;
//만약 renderTexture 가 존재하면
if( renderTexture )
{
#if UNITY_2018_2_OR_NEWER
//만약 비동기 작업 시작 여부가 true 라면
if ( !asyncOperationStarted )
#endif
{
//GetTemporary 로 할당된 임시 텍스처를 해제
RenderTexture.ReleaseTemporary( renderTexture );
}
}
}
}
catch( Exception e )
{
Debug.LogException( e );
}
finally
{
//만약 모델의 복제 여부가 true 라면 previewObject 를 즉시 파괴
if ( shouldCloneModel )
Object.DestroyImmediate( previewObject.gameObject );
//이외의 경우
else
{
//만약 활성화 여부가 false 라면 previewObject 를 비활성화
if( !wasActive )
previewObject.gameObject.SetActive( false );
//만약 정적 객체 존재 유무가 false 라면 previewObject 의 위치, 회전값을 초기화
if( !isStatic )
{
previewObject.position = prevPos;
previewObject.rotation = prevRot;
}
int index = 0;
SetLayerRecursively( previewObject, ref index );
}
//만약 renderCamera 가 m_previewRenderCamera 인 경우 카메라 설정을 RuntimePreviewGenerator 의 설정값으로 적용
if ( renderCamera == m_previewRenderCamera )
cameraSetup.ApplySetup( renderCamera );
}
#if UNITY_2018_2_OR_NEWER
if( !asyncOperationStarted && asyncCallback != null )
asyncCallback( null );
#endif
return result;
}
//대상 객체의 AABB 경계를 계산하는 메서드
//AABB - Axis Aligned Bounding Box : 똑같은 축의 방향으로 물체의 경계상자가 만들어짐
public static bool CalculateBounds( Transform target, bool shouldIgnoreParticleSystems, Quaternion cameraRotation, out Bounds bounds )
{
//renderersList 초기화
//target 의 자식중 Renderer Component 를 가진 자식을 renderersList 에 넣기
renderersList.Clear();
target.GetComponentsInChildren( renderersList );
//inverseCameraRotation = cameraRoation 의 역수값 반환
Quaternion inverseCameraRotation = Quaternion.Inverse( cameraRotation );
Vector3 localBoundsMin = new Vector3( float.MaxValue - 1f, float.MaxValue - 1f, float.MaxValue - 1f );
Vector3 localBoundsMax = new Vector3( float.MinValue + 1f, float.MinValue + 1f, float.MinValue + 1f );
bounds = new Bounds();
bool hasBounds = false;
for( int i = 0; i < renderersList.Count; i++ )
{
//만약 renderersList 의 i 번째 Renderer Component 가 비활성화 되어있으면 continue;
if( !renderersList[i].enabled )
continue;
//만약 파티클 시스템 무시여부가 true이고, renderersList 중에 ParticleSystemRenderer 가 있다면 continue;
if ( shouldIgnoreParticleSystems && renderersList[i] is ParticleSystemRenderer )
continue;
//Local 의 경계를 계산하는 로직: https://github.com/Unity-Technologies/UnityCsReference/blob/0355e09029fa1212b7f2e821f41565df8e8814c7/Editor/Mono/InternalEditorUtility.bindings.cs#L710
//만약 m_useLocalBounds 가 true 인 경우
if( m_useLocalBounds )
{
#if UNITY_2021_2_OR_NEWER
#endif
Bounds localBounds = renderersList[i].localBounds;
Transform transform = renderersList[i].transform;
localBoundsMinMax[0] = localBounds.min;
localBoundsMinMax[1] = localBounds.max;
//이 부분은 세 개의 중첩된 루프를 사용하여 주어진 로컬 바운딩 박스의 모든 꼭지점을 카메라의 역회전 및 변환을
//거쳐 전역 공간에서의 좌표로 변환, 이를 이용하여 전역 공간에서의 최소 및 최대 지점을 계산하는 로직입니다.
//각각 x, y, z 축에 대해 두 가지 경우를 반복함으로써 세 개의 루프를 통해 2 * 3 = 8가지 꼭지점의 조합을생성
for ( int x = 0; x < 2; x++ )
{
for( int y = 0; y < 2; y++ )
{
for( int z = 0; z < 2; z++ )
{
//각 꼭지점의 로컬 좌표를 localBoundsMinMax에서 가져와 새로운 Vector3로 만듭니다.
//이후, 이 좌표를 transform.TransformPoint를 사용하여 해당 객체의 변환을 적용하고,
//그 결과를 inverseCameraRotation으로 역회전시킵니다.
Vector3 point = inverseCameraRotation * transform.TransformPoint( new Vector3( localBoundsMinMax[x].x, localBoundsMinMax[y].y, localBoundsMinMax[z].z ) );
//각 꼭지점에 대한 전역 좌표를 사용하여 localBoundsMin 및 localBoundsMax를 갱신합니다.
//localBoundsMin = 바운딩 박스의 최소 꼭지점
//localBoundsMax = 최대 꼭지점
localBoundsMin = Vector3.Min( localBoundsMin, point );
localBoundsMax = Vector3.Max( localBoundsMax, point );
}
}
}
//bound 를 가졌는지에 대한 여부를 true 로 지정
hasBounds = true;
}
//만약 bounds 를 가져오지 못했다면
else if( !hasBounds )
{
//bounds 를 renderersList 의 i번째 bounds 로 지정
//bounds 를 가져오는 여부를 true 로 지정
bounds = renderersList[i].bounds;
hasBounds = true;
}
//그 이외의 경우
else
//renderersList i 번째의 bounds 를 포함하도록 경계를 늘리는 작업을 수행
bounds.Encapsulate( renderersList[i].bounds );
}
//만약 m_useLocalBounds 와 bounds 를 가졌는지의 여부가 true 이면
if( m_useLocalBounds && hasBounds )
//bounds 를 재정의
bounds = new Bounds( cameraRotation * ( ( localBoundsMin + localBoundsMax ) * 0.5f ), localBoundsMax - localBoundsMin );
//bounds 를 가졌는지의 여부를 반환
return hasBounds;
}
// 경계를 완벽하게 캡슐화하는 방식으로 카메라를 이동하는 메서드
public static void CalculateCameraPosition( Camera camera, Bounds bounds, float padding = 0f )
{
//cameraTR = camera 의 transform
Transform cameraTR = camera.transform;
//cameraDirection = 카메라의 방향
//aspect = 카메라의 가로,세로 비율
Vector3 cameraDirection = cameraTR.forward;
float aspect = camera.aspect;
//만약 padding 이 0이 아닌 경우
if( padding != 0f )
bounds.size *= 1f + padding * 2f; //양쪽 가장자리에 패딩이 적용되어 2를 곱한 값
//bound 의 Center, Extents, Size 를 설정
Vector3 boundsCenter = bounds.center;
Vector3 boundsExtents = bounds.extents;
Vector3 boundsSize = 2f * boundsExtents;
// 경계의 모서리 점을 계산하는 로직
//로컬 좌표계(local space)를 기준으로 한 경계 상자의 꼭지점을 계산
if ( m_useLocalBounds )
{
//로컬 좌표계를 나타내는 변환 행렬을 생성합니다.
//boundsCenter를 중심으로 하고 camera.transform.rotation으로 회전하며 크기는 Vector3.one로 설정
//꼭지점의 초기값을 boundsExtents로 설정합니다.
Matrix4x4 localBoundsMatrix = Matrix4x4.TRS( boundsCenter, camera.transform.rotation, Vector3.one );
Vector3 point = boundsExtents;
//각 꼭지점을 localBoundsMatrix.MultiplyPoint3x4 메서드를 사용하여 로컬 좌표계에 맞게 변환한 후 boundingBoxPoints 배열에 저장합니다.
//꼭지점의 좌표를 계산하는 방식은 각 꼭지점마다 point를 이동 또는 변형시키는 것입니다.
boundingBoxPoints[0] = localBoundsMatrix.MultiplyPoint3x4( point );
point.x -= boundsSize.x;
boundingBoxPoints[1] = localBoundsMatrix.MultiplyPoint3x4( point );
point.y -= boundsSize.y;
boundingBoxPoints[2] = localBoundsMatrix.MultiplyPoint3x4( point );
point.x += boundsSize.x;
boundingBoxPoints[3] = localBoundsMatrix.MultiplyPoint3x4( point );
point.z -= boundsSize.z;
boundingBoxPoints[4] = localBoundsMatrix.MultiplyPoint3x4( point );
point.x -= boundsSize.x;
boundingBoxPoints[5] = localBoundsMatrix.MultiplyPoint3x4( point );
point.y += boundsSize.y;
boundingBoxPoints[6] = localBoundsMatrix.MultiplyPoint3x4( point );
point.x += boundsSize.x;
boundingBoxPoints[7] = localBoundsMatrix.MultiplyPoint3x4( point );
}
//전역 좌표계(global space)를 기준으로 한 경계 상자의 꼭지점을 계산
else
{
//꼭지점의 초기값을 boundsCenter와 boundsExtents를 더한 값으로 설정
Vector3 point = boundsCenter + boundsExtents;
//이후 각 꼭지점을 point를 이동 또는 변형시키는 방식으로 계산하고, 계산된 꼭지점을 boundingBoxPoints 배열에 저장합니다.
boundingBoxPoints[0] = point;
point.x -= boundsSize.x;
boundingBoxPoints[1] = point;
point.y -= boundsSize.y;
boundingBoxPoints[2] = point;
point.x += boundsSize.x;
boundingBoxPoints[3] = point;
point.z -= boundsSize.z;
boundingBoxPoints[4] = point;
point.x -= boundsSize.x;
boundingBoxPoints[5] = point;
point.y += boundsSize.y;
boundingBoxPoints[6] = point;
point.x += boundsSize.x;
boundingBoxPoints[7] = point;
}
//만약 카메라의 직각 투영 여부가 true 인 경우
if( camera.orthographic )
{
//camera 의 position 은 boundsCenter 로 지정
cameraTR.position = boundsCenter;
//minX, minY를 양의 무한대로 지정
//maxX, maxY를 음의 무한대로 지정
float minX = float.PositiveInfinity, minY = float.PositiveInfinity;
float maxX = float.NegativeInfinity, maxY = float.NegativeInfinity;
//boundingBoxPoints 의 갯수 만큼 반복
for( int i = 0; i < boundingBoxPoints.Length; i++ )
{
//boundingBoxPoints의 i번째의 위치를 로컬 공간 기준으로 변환
Vector3 localPoint = cameraTR.InverseTransformPoint( boundingBoxPoints[i] );
if( localPoint.x < minX )
minX = localPoint.x;
if( localPoint.x > maxX )
maxX = localPoint.x;
if( localPoint.y < minY )
minY = localPoint.y;
if( localPoint.y > maxY )
maxY = localPoint.y;
}
//길이를 경계상자의 범위+1 값으로 지정
//직교 투영 카메라의 보기 볼륨을 지정
//카메라의 위치값 지정
float distance = boundsExtents.magnitude + 1f;
camera.orthographicSize = Mathf.Max( maxY - minY, ( maxX - minX ) / aspect ) * 0.5f;
cameraTR.position = boundsCenter - cameraDirection * distance;
}
//그 이외의 경우
else
{
Vector3 cameraUp = cameraTR.up, cameraRight = cameraTR.right;
//수직의 시야각 지정
//수평의 시야각 지정
float verticalFOV = camera.fieldOfView * 0.5f;
float horizontalFOV = Mathf.Atan( Mathf.Tan( verticalFOV * Mathf.Deg2Rad ) * aspect ) * Mathf.Rad2Deg;
Vector3 topFrustumPlaneNormal = Quaternion.AngleAxis(90f + verticalFOV, -cameraRight) * cameraDirection;
Vector3 bottomFrustumPlaneNormal = Quaternion.AngleAxis( 90f + verticalFOV, cameraRight ) * cameraDirection;
Vector3 rightFrustumPlaneNormal = Quaternion.AngleAxis( 90f + horizontalFOV, cameraUp ) * cameraDirection;
Vector3 leftFrustumPlaneNormal = Quaternion.AngleAxis( 90f + horizontalFOV, -cameraUp ) * cameraDirection;
// Credit for algorithm: https://stackoverflow.com/a/66113254/2373034
int leftmostPoint = -1, rightmostPoint = -1, topmostPoint = -1, bottommostPoint = -1;
//카메라의 평면을 사용하여 경계의 가장자리 점 찾기
for ( int i = 0; i < boundingBoxPoints.Length; i++ )
{
if( leftmostPoint < 0 && IsOutermostPointInDirection( i, leftFrustumPlaneNormal ) )
leftmostPoint = i;
if( rightmostPoint < 0 && IsOutermostPointInDirection( i, rightFrustumPlaneNormal ) )
rightmostPoint = i;
if( topmostPoint < 0 && IsOutermostPointInDirection( i, topFrustumPlaneNormal ) )
topmostPoint = i;
if( bottommostPoint < 0 && IsOutermostPointInDirection( i, bottomFrustumPlaneNormal ) )
bottommostPoint = i;
}
//점을 통과하는 각 모서리 점에 대한 평면을 작성
Ray horizontalIntersection = GetPlanesIntersection( new Plane( leftFrustumPlaneNormal, boundingBoxPoints[leftmostPoint] ), new Plane( rightFrustumPlaneNormal, boundingBoxPoints[rightmostPoint] ) );
Ray verticalIntersection = GetPlanesIntersection( new Plane( topFrustumPlaneNormal, boundingBoxPoints[topmostPoint] ), new Plane( bottomFrustumPlaneNormal, boundingBoxPoints[bottommostPoint] ) );
//수평 모서리 점의 평면과 수직 모서리 점의 평면의 교점선 찾기
//카메라를 수평 교차로를 따라 이동하면 경계는 항상 카메라 폭이 완벽하게 설정됩니다
Vector3 closestPoint1, closestPoint2;
FindClosestPointsOnTwoLines( horizontalIntersection, verticalIntersection, out closestPoint1, out closestPoint2 );
//이 두 선 사이의 가장 가까운 선분을 찾고 해당 선의 가장 먼 지점에 카메라를 놓습니다
cameraTR.position = Vector3.Dot( closestPoint1 - closestPoint2, cameraDirection ) < 0 ? closestPoint1 : closestPoint2;
}
}
//경계의 모든 점 중에서 주어진 점이 주어진 방향의 가장 바깥쪽 점인지 여부를 반환하는 메서드
private static bool IsOutermostPointInDirection( int pointIndex, Vector3 direction )
{
Vector3 point = boundingBoxPoints[pointIndex];
//boundingBoxPoints 갯수 만큼 반복
for( int i = 0; i < boundingBoxPoints.Length; i++ )
{
//만약 i 가 pointIndex 가 아니고, direction 과 boundingBoxPoints[i] - point의 각도가 0보다 큰 경우 false 를 반환
//현재의 점 point와 다른 점 boundingBoxPoints[i] 간의 방향과 주어진 direction 벡터 간의 내적을 계산합니다.
//이 내적 값이 0보다 크다면, 현재의 point가 boundingBoxPoints[i]에 비해 주어진 방향으로 더 멀리 위치해 있는 것
if ( i != pointIndex && Vector3.Dot( direction, boundingBoxPoints[i] - point ) > 0 )
return false;
}
//true 를 반환
return true;
}
// Credit: https://stackoverflow.com/a/32410473/2373034
// 두 평면의 교점 선을 반환하는 메서드
private static Ray GetPlanesIntersection( Plane p1, Plane p2 )
{
//p1의 법선벡터와 p2의 법선벡터를 교차하여, 수직 벡터를 얻는 작업
//det = p3Normal 의 제곱 길이
Vector3 p3Normal = Vector3.Cross( p1.normal, p2.normal );
float det = p3Normal.sqrMagnitude;
//수직인 벡터 p3Normal을 두 번째 평면의 법선 벡터와 교차한 벡터에 첫 번째 평면의 길이를 곱함
//수직인 벡터 p3Normal을 첫 번째 평면의 법선 벡터와 교차한 벡터에 두 번째 평면의 길이를 곱함
//위 두 벡터의 합을 p3Normal 의 제곱 길이로 나누어 교차 지점을 계산, 방향은 p3Normal로 설정합니다.
return new Ray( ( ( Vector3.Cross( p3Normal, p2.normal ) * p1.distance ) + ( Vector3.Cross( p1.normal, p3Normal ) * p2.distance ) ) / det, p3Normal );
}
// Credit: http://wiki.unity3d.com/index.php/3d_Math_functions
//두 선 사이의 가장 가까운 선분의 모서리 점을 반환하는 메서드
private static void FindClosestPointsOnTwoLines( Ray line1, Ray line2, out Vector3 closestPointLine1, out Vector3 closestPointLine2 )
{
//line1,2 방향 지정
Vector3 line1Direction = line1.direction;
Vector3 line2Direction = line2.direction;
float a = Vector3.Dot( line1Direction, line1Direction );//첫 번째 선의 방향 벡터의 제곱 길이를 계산하여 변수 a에 저장합니다.
float b = Vector3.Dot( line1Direction, line2Direction );//첫 번째 선의 방향 벡터와 두 번째 선의 방향 벡터의 내적을 계산하여 변수 b에 저장합니다.
float e = Vector3.Dot( line2Direction, line2Direction );//두 번째 선의 방향 벡터의 제곱 길이를 계산하여 변수 e에 저장합니다.
float d = a * e - b * b;
Vector3 r = line1.origin - line2.origin; //두 선의 원점 간의 벡터를 계산하여 변수 r에 저장합니다.
float c = Vector3.Dot( line1Direction, r ); //각 선의 방향 벡터와 r 벡터의 내적을 계산하여 c에 저장합니다.
float f = Vector3.Dot( line2Direction, r ); //각 선의 방향 벡터와 r 벡터의 내적을 계산하여 f에 저장합니다.
float s = ( b * f - c * e ) / d;
float t = ( a * f - c * b ) / d;
//계산된 s 및 t 값을 사용하여 각 선상에서 가장 가까운 점을 찾아내어 반환합니다.
closestPointLine1 = line1.origin + line1Direction * s;
closestPointLine2 = line2.origin + line2Direction * t;
}
//카메라를 설정하는 메서드
private static void SetupCamera()
{
//만약 m_previewRenderCamera 가 있다면
if( m_previewRenderCamera )
{
//GetSetup 메서드 호출
//m_previewRenderCamera 설정값을 RuntimePreviewGenerator 의 설정값으로 적용
cameraSetup.GetSetup( m_previewRenderCamera );
//모델을 렌더링하는 카메라를 m_previewRenderCamera 로 설정
//모델을 렌더링하는 카메라의 nearClipPlane 을 설정
//모델을 렌더링하는 카메라의 cullingMask 를 설정
renderCamera = m_previewRenderCamera;
renderCamera.nearClipPlane = 0.01f;
renderCamera.cullingMask = 1 << PREVIEW_LAYER;
}
//이외의 경우 모델을 렌더링하는 카메라를 InternalCamera 로 설정
else
renderCamera = InternalCamera;
//모델을 렌더링하는 카메라의 배경색, 직각 투영 여부, 카메라가 배경을 지우는 방법을 설정
renderCamera.backgroundColor = m_backgroundColor;
renderCamera.orthographic = m_orthographicMode;
renderCamera.clearFlags = m_backgroundColor.a < 1f ? CameraClearFlags.Depth : CameraClearFlags.Color;
}
//obj 혹은 obj 의 자식이 정적객체인지 확인하는 메서드
private static bool IsStatic( Transform obj )
{
if( obj.gameObject.isStatic )
return true;
for( int i = 0; i < obj.childCount; i++ )
{
if( IsStatic( obj.GetChild( i ) ) )
return true;
}
return false;
}
///obj 와 obj 의 모든 자식의 layer를 PREVIEW_LAYER 로 설정하는 메서드
private static void SetLayerRecursively( Transform obj )
{
obj.gameObject.layer = PREVIEW_LAYER;
for( int i = 0; i < obj.childCount; i++ )
SetLayerRecursively( obj.GetChild( i ) );
}
//obj 와 obj 의 모든 자식의 layer를 layerList 에 추가하는 메서드
private static void GetLayerRecursively( Transform obj )
{
layersList.Add( obj.gameObject.layer );
for( int i = 0; i < obj.childCount; i++ )
GetLayerRecursively( obj.GetChild( i ) );
}
//obj 와 obj 의 모든 자식의 layer를 layerList[index++] 에 해당하는 layer 로 설정하는 메서드
private static void SetLayerRecursively( Transform obj, ref int index )
{
obj.gameObject.layer = layersList[index++];
for( int i = 0; i < obj.childCount; i++ )
SetLayerRecursively( obj.GetChild( i ), ref index );
}
}