2025-02-19 17:24:26 +09:00
//#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 const int PREVIEW_LAYER = 22 ;
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 ) ; }
}
2025-05-28 19:45:22 +09:00
private static Color m_backgroundColor = Color . white ;
2025-02-19 17:24:26 +09:00
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 ) ;
}
}