성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Unity - shader의 원근 투영(Perspective projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성</h1> <p> 지난 글에서 월드 행렬과 카메라 행렬을 수작업으로 구성해 봤으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unity - shader의 World matrix(unity_ObjectToWorld)를 수작업으로 구성 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11633'>http://www.sysnet.pe.kr/2/0/11633</a> Unity - shader의 Camera matrix(UNITY_MATRIX_V)를 수작업으로 구성 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11692'>http://www.sysnet.pe.kr/2/0/11692</a> </pre> <br /> 이제 마지막으로 투영 행렬을 구성해 볼 차례입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 투영 행렬을 구성하는 요소는 Unity Inspector 창에 보이는 "Field of View", "Clipping Planes"와 게임 화면이 실행되는 윈도우의 "가로/세로" 비율이 됩니다. C# 스크립트에서 이에 대한 값은 각각 다음과 같이 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Camera camera = Camera.main; float aspect = camera.aspect; // 또는 (float)camera.pixelWidth / camera.pixelHeight; float fov = camera.fieldOfView; float near = camera.nearClipPlane; float far = camera.farClipPlane; </pre> <br /> 여기서 Field of View는 다음과 같이 Unity에서 카메라로부터 상하로 퍼져 나가는 시야각을 의미하며 화각 또는 FOV라고 줄여서 말하기도 합니다. (Unity 에디터의 기본값은 60도)<br /> <br /> <img alt='projection_matrix_1.png' src='/SysWebRes/bbs/projection_matrix_1.png' /><br /> <br /> near는 기본 값이 0.3인데 월드 좌표계 기준으로 카메라로부터 0.3 만큼 떨어진 "근-평면"의 위치를 의미합니다. Scene을 새로 생성하면 카메라의 기본 위치값이 (0, 1, -10) 좌표이므로 근-평면은 (0, 1, -9.7) 좌표에 위치하게 됩니다.<br /> <br /> far는 기본 값이 1000이고 월드 좌표계 기준으로 카메라로부터 1000 만큼 떨어진 "원-평면"의 위치를 의미합니다. 마찬가지로 Scene의 기본 값 상태일 때 원-평면은 (0, 1, 990) 좌표에 위치하게 됩니다. 아래는 유니티 편집 화면에서의 근-평면과 원-평면의 모습을 보여줍니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='projection_matrix_2.png' src='/SysWebRes/bbs/projection_matrix_2.png' /><br /> <br /> FOV와 Near, Far 값은 개발자가 제어할 수 있지만 화면 비율(aspect 값)은 딱히 조정할 수 없습니다. 이건 사용자가 게임을 실행할 때 보통 전체 화면으로 실행하기 때문에 윈도우의 width, height가 고정되거나 "창 모드"로 게임을 실행했을지라도 사용자가 임의로 변경하는 것이기 때문에 개발자 입장에서는 그때마다 aspect 비율을 투영 행렬에 잘 반영하면 됩니다.<br /> <br /> 이 값들로부터 투영 행렬을 만드는 것은 다음의 책을 보면 잘 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 유니티로 배우는 게임 수학 기초 개념부터 모바일까지, 게임 개발에 필요한 수학 원리 설명서 ; <a target='tab' href='http://www.yes24.com/24/goods/30119802'>http://www.yes24.com/24/goods/30119802</a> </pre> <br /> 자세하게 수학적인 설명과 곁들여 설명하고 있으므로 그 부분에 대해서는 책을 참고하시고, 이 글에서는 해당 투영 행렬을 Unity shader에서 어떻게 구성할 수 있는지에 대한 내용만 알아보겠습니다.<br /> <br /> 우선, 책에 있는 프로젝션 변환 행렬 P는 다음과 같이 이뤄진다고 소개하고 있습니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> ${<br /> P = \begin{bmatrix} \frac {2n} {r - l} & 0 & \frac {r + l} {r - l} & 0 \\ 0 & \frac {2n} {t - b} & \frac {t + b} {t - b} & 0 \\ 0 & 0 & \frac {-(f + n)} {f - n} & \frac {-2fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}<br /> }$<br /> </div><br /> <br /> 하지만, 저 투영 행렬은 OpenGL의 관례를 따라 나타낸 것이고 각각의 플랫폼에 따른 shader 상의 투영 행렬은 다르다고 합니다. 일례로 (DirectX를 사용하는) 윈도우 데스크톱 환경의 경우에는 다음과 같은 투영 행렬이 사용된다고 설명합니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> ${<br /> P = \begin{bmatrix} \frac {2n} {r - l} & 0 & \frac {r + l} {r - l} & 0 \\ 0 & \frac {-2n} {t - b} & -\frac {t + b} {t - b} & 0 \\ 0 & 0 & \frac {-f} {f - n} & \frac {-fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}<br /> }$<br /> </div><br /> <br /> 수식에 사용된 변수의 의미는 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > l == 근-평면의 좌측 끝의 x 좌표 r == 근-평면의 우측 끝의 x 좌표 b == 근-평면의 하단 끝의 y 좌표 t == 근-평면의 상단 끝의 y 좌표 n == 원점으로부터 근-평면까지의 거리 f == 원점으로부터 원-평면까지의 거리 </pre> <br /> 여기서 n과 f는 이미 Unity Inspector 창에서 Camera 객체의 값으로부터 설정된 바로 그 값입니다. 그리고 나머지 left, right, top, bottom의 값은 FOV에 지정된 각을 이용해 탄젠트 삼각함수로 구할 수 있습니다. 가령 top 값은 근-평면 하단 끝의 y 좌표이므로 x축 기준으로 y-z 평면으로 봤을 때,<br /> <br /> <img alt='projection_matrix_3.png' src='/SysWebRes/bbs/projection_matrix_3.png' /><br /> <br /> 직각 삼각형의 빗변과 밑변의 각도는 Field of View 60도에서 절반인 30도이고 밑변의 길이가 0.3임을 알고 있으므로 top과 bottom은 다음의 공식으로 알아낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > top = tan(30°) * near = tan(DegreeToRadian(30°)) * near bottom = -top; </pre> <br /> top과 bottom은 쌍을 이루니 당연히 bottom은 -top이 됩니다. 반면 left와 right의 경우에는 FOV를 이용할 수 없습니다. FOV는 시야의 상하각이기 때문인데, 대신 aspect 값이 있으므로 이를 이용해 top을 aspect와 곱해 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float right = (top * aspect); float left = -right; </pre> <br /> 실제로 Unity C# 스크립트로부터 camera.projectionMatrix와 GL.GetGPUProjectionMatrix(camera.projectionMatrix, true)로 구한 행렬의 값은 다음과 같은 식으로 나옵니다. (Unity 기본 Scene 상태를 가정합니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > aspect 1.323475 // 사용자의 환경에 따라 변경 tangentFov 0.5773503 // 기본 Scene 상태인 경우 near 0.3 // 기본 Scene 상태인 경우 far 1000 // 기본 Scene 상태인 경우 top 0.1732051 bottom -0.1732051 right 0.2292326 left -0.2292326 </pre> <br /> 이렇게 해서 소스가 준비되었군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이제 Unity C# 스크립트에서 실제 사용하고 있는 행렬을 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > camera.projectionMatrix 1.30871 0.00000 0.00000 0.00000 0.00000 1.73205 0.00000 0.00000 0.00000 0.00000 -1.00060 -0.60018 0.00000 0.00000 -1.00000 0.00000 GL.GetGPUProjectionMatrix(camera.projectionMatrix, true) 1.30871 0.00000 0.00000 0.00000 0.00000 -1.73205 0.00000 0.00000 0.00000 0.00000 0.00030 0.30009 0.00000 0.00000 -1.00000 0.00000 </pre> <br /> 그런데 대충 봐도, (r+l) / (r-l)을 나타내는 projectionMatrix[0,2]의 값이 0인 것을 보면 완전히 똑같은 것은 아닌 것 같습니다. shader에서도 이렇게 쓰고 있는지 다음과 같은 코드로 테스트할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > v2f vert(appdata v) { float4 pos; v2f o; <span style='color: blue; font-weight: bold'>float4x4 m = UNITY_MATRIX_P;</span> float4x4 projectionMatrix; projectionMatrix[0] = float4<span style='color: blue; font-weight: bold'>(m[0].x</span>, 0, 0, 0); projectionMatrix[1] = float4(0, <span style='color: blue; font-weight: bold'>m[1].y</span>, 0, 0); projectionMatrix[2] = float4(0, 0, <span style='color: blue; font-weight: bold'>m[2].z, m[2].w</span>); projectionMatrix[3] = float4(0, 0, -1, 0); pos = mul(unity_ObjectToWorld, v.vertex); pos = mul(UNITY_MATRIX_V, pos); pos = mul(projectionMatrix, pos); o.vertex = pos; return o; } </pre> <br /> camera.projectionMatrix 및 GL.GetGPUProjectionMatrix의 구조와 동일한 위치의 값만 설정했는데 정상적으로 투영 행렬로 동작하는 것을 확인할 수 있으며 즉, Unity shader에서 사용하는 투영 행렬은 다음의 2개로 정해집니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> [OpenGL을 따르는 투영 행렬]<br /> <br /> ${<br /> P = \begin{bmatrix} \frac {2n} {r - l} & 0 & 0 & 0 \\ 0 & \frac {2n} {t - b} & 0 & 0 \\ 0 & 0 & \frac {-(f + n)} {f - n} & \frac {-2fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}<br /> }$<br /> <br /> [실행 환경에 맞는 투영 행렬 - 아래는 DirectX를 사용하는 윈도우 환경에서의 투영 행렬]<br /> <br /> ${<br /> P = \begin{bmatrix} \frac {2n} {r - l} & 0 & 0 & 0 \\ 0 & \frac {-2n} {t - b} & 0 & 0 \\ 0 & 0 & \frac {-f} {f - n} & \frac {-fn} {f - n} \\ 0 & 0 & -1 & 0 \end{bmatrix}<br /> }$<br /> </div><br /> <br /> 그럼 끝났군요. ^^ 재료를 이용해 다음과 같이 투영 행렬을 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Matrix4x4 myProjection1 = new Matrix4x4(); myProjection1[0, 0] = (2 * near) / (right - left); myProjection1[1, 1] = (2 * near) / (top - bottom); myProjection1[2, 2] = -(far + near) / (far - near); myProjection1[2, 3] = -(2 * far * near) / (far - near); myProjection1[3, 2] = -1; Matrix4x4 myProjection2 = new Matrix4x4(); myProjection2[0, 0] = (2 * near) / (right - left); myProjection2[1, 1] = -(2 * near) / (top - bottom); myProjection2[2, 2] = -far / (far - near); myProjection2[2, 3] = -(far * near) / (far - near); myProjection2[3, 2] = -1; </pre> <br /> 바로 저 2개의 행렬들은 Unity C# 스크립트에서 다음의 코드로 대응합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > myProjection1 == camera.worldToCameraMatrix myProjection2 == GL.GetGPUProjectionMatrix(camera.projectionMatrix, true); </pre> <br /> 또한 우리가 구한 l, r, b, t, n, f의 값들이 실제로 camera.worldToCameraMatrix.decomposeProjection 값들과 동일합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > camera.projectionMatrix.decomposeProjection left -0.2292326 right 0.2292326 bottom -0.1732051 top 0.1732051 zNear 0.3 zFar 1000.134 GL.GetGPUProjectionMatrix(camera.projectionMatrix, true).decomposeProjection left 0.2293702 right -0.2293702 bottom -0.1733091 top 0.1733091 zNear -0.3001801 zFar 0.3 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 다음 단계로 Unity shader에서 사용하는 투영 행렬이 camera.projectionMatrix 인지, GL.GetGPUProjectionMatrix 반환 값인지는 다음과 같은 코드로 쉽게 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using UnityEngine; [ExecuteInEditMode] public class SetMatrix : MonoBehaviour { void Start () { } void Update () { Camera camera = Camera.main; Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true); Shader.SetGlobalMatrix("_projectionMatrix1", projectionMatrix); Shader.SetGlobalMatrix("_projectionMatrix2", camera.projectionMatrix); } } </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float4x4 _projectionMatrix; float4x4 _projectionMatrix2; v2f vert(appdata v) { float4 pos; v2f o; pos = mul(unity_ObjectToWorld, v.vertex); pos = mul(UNITY_MATRIX_V, pos); pos = mul(_projectionMatrix1, pos); // pos = mul(_projectionMatrix2, pos); o.vertex = pos; return o; } </pre> <br /> 실제로 실행해 보면 GL.GetGPUProjectionMatrix가 반환한 투영 행렬이 정상적으로 동작하는 것을 볼 수 있습니다. 약간 혼란스러운 점이 있다면, C# 스크립트에서 넘겨줄 때의 행렬 값이 다음과 같은 반면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > GL.GetGPUProjectionMatrix 0.97183 0.00000 0.00000 0.00000 0.00000 -1.73205 0.00000 0.00000 0.00000 0.00000 0.00030 0.30009 0.00000 0.00000 <span style='color: blue; font-weight: bold'>-1.00000</span> 0.00000 </pre> <br /> <a target='tab' href='"http://www.sysnet.pe.kr/2/0/11693'>"Visual Studio Graphics Analyzer"로 디버깅</a> 환경의 Watch 창에서 GL.GetGPUProjectionMatrix의 값을 보면 다음과 같다는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > _projectionMatrix1 float4x4 _projectionMatrix1[0] x = 0.971829400, y = 0.000000000, z = 0.000000000, w = 0.000000000 _projectionMatrix1[1] x = 0.000000000, y = -1.732051000, z = 0.000000000, w = 0.000000000 _projectionMatrix1[2] x = 0.000000000, y = 0.000000000, z = 0.000300050, <span style='color: blue; font-weight: bold'>w = -1.000000000</span> _projectionMatrix1[3] x = 0.000000000, y = 0.000000000, z = 0.300090000, w = 0.000000000 </pre> <br /> 즉, 전치가 되어 있는데 이것은 아마도 Visual Studio의 디버거 창이 자동으로 전치를 해주는 것인지? 아니면 메모리 상의 값을 읽을 때 열/행우선을 잘못 판단한 것인지는 알 수 없으나 C# 스크립트 상에서의 디버거 값이 올바른 형식입니다.<br /> <br /> 정리해 보면, Unity에서 제공하는 투영 행렬은 2가지가 있습니다.<br /> <br /> <ul> <li>camera.projectionMatrix == OpenGL을 따르는 형식</li> <li>GL.GetGPUProjectionMatrix == Unity 프로그램이 실행되는 환경에 부합하는 투영 행렬(일례로 위에서의 _projectionMatrix1 값은 DirectX 11을 사용하는 윈도우 환경에서 유효한 투영 행렬)</li> </ul> <br /> 휴~~~ 이것으로 대충 정리가 되었군요. ^^ 행렬의 생성 규칙과 그 값의 decomposeProjection을 알게 되었으니 이제 나머지 단계는 unity shader에서 decomposeProjection에 해당하는 값을 어떻게 구하느냐에 달려 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> unity shader에서 근-평면/원-평면은 내장 변수를 통해 값을 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <a target='tab' href='https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html'>_ProjectionParams</a> float4 x is 1.0 (or ?1.0 if currently rendering with a flipped projection matrix) y is the camera’s near plane z is the camera’s far plane w is 1/FarPlane </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float nearPlane = _ProjectionParams.y; float farPlane = _ProjectionParams.z; // 기본값인 경우 nearPlane == 0.3, farPlane == 1000 </pre> <br /> 또한 aspect 비율도 _ScreenParams 내장 변수를 통해 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float width = _ScreenParams.x; float height = _ScreenParams.y; float aspect = width / height; </pre> <br /> 문제는, FOV 값인데 이것은 unity가 내장 변수로 제공하지 않습니다. 대신 기존 UNITY_MATRIX_P로부터 구해올 수는 있습니다. 이에 대한 계산이 재미있는데요. ^^ 이전에 행렬을 구하는 것에서 (0,0)의 값은 다음과 같이 이뤄집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [0, 0] = (2 * near) / (right - left); </pre> <br /> 그런데 검색해 보면 다음의 소스 코드를 통해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <a target='tab' href='https://github.com/g-truc/glm/blob/master/glm/ext/matrix_clip_space.inl'>https://github.com/g-truc/glm/blob/master/glm/ext/matrix_clip_space.inl</a> template<typename T> GLM_FUNC_QUALIFIER mat<4, 4, T, defaultp> perspectiveLH_NO(T fovy, T aspect, T zNear, T zFar) { assert(abs(aspect - std::numeric_limits<T>::epsilon()) > static_cast<T>(0)); T const tanHalfFovy = tan(fovy / static_cast<T>(2)); mat<4, 4, T, defaultp> Result(static_cast<T>(0)); Result[0][0] = static_cast<T>(1) / (aspect * tanHalfFovy); Result[1][1] = static_cast<T>(1) / (tanHalfFovy); Result[2][2] = (zFar + zNear) / (zFar - zNear); Result[2][3] = static_cast<T>(1); Result[3][2] = - (static_cast<T>(2) * zFar * zNear) / (zFar - zNear); return Result; } </pre> <br /> "1 / (aspect * tanHalfFovy) = UNITY_MATRIX_P[0].x"와 같다는 것을 알 수 있습니다. 따라서 각도는 다음과 같이 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > aspect * tanHalfFovy * UNITY_MATRIX_P[0].x = 1 tanHalfFovy = 1 / (aspect * UNITY_MATRIX_P[0].x) radian_FOV = arctan(1 / (aspect * UNITY_MATRIX_P[0].x)) </pre> <br /> 자, 그럼 다 끝났군요. ^^ 이제 다음과 같이 UNITY_MATRIX_P를 재조립해 적용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Shader "Unlit/NewUnlitShader" { Properties { } SubShader { Tags{ "LightMode" = "ForwardBase" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma enable_d3d11_debug_symbols #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4x4 _projectionMatrix; v2f vert(appdata v) { float4 pos; v2f o; float nearPlane = _ProjectionParams.y; float farPlane = _ProjectionParams.z; float width = _ScreenParams.x; float height = _ScreenParams.y; float aspect = width / height; float4x4 m = UNITY_MATRIX_P; float halfFov = atan2(1, aspect * m[0].x); float top = tan(halfFov) * nearPlane; float bottom = -top; float right = (top * aspect); float left = -right; <span style='color: blue; font-weight: bold'>// DirectX를 사용하는 윈도우 환경에서의 투영 행렬 float p00 = (2 * nearPlane) / (right - left); float p11 = -(2 * nearPlane) / (top - bottom); float p22 = -farPlane / (farPlane - nearPlane); float p23 = -(farPlane * nearPlane) / (farPlane - nearPlane);</span> float4x4 projectionMatrix; projectionMatrix[0] = float4(p00, 0, 0, 0); projectionMatrix[1] = float4(0, p11, 0, 0); projectionMatrix[2] = float4(0, 0, p22, p23); projectionMatrix[3] = float4(0, 0, -1, 0); pos = mul(unity_ObjectToWorld, v.vertex); pos = mul(UNITY_MATRIX_V, pos); pos = mul(projectionMatrix, pos); // pos = mul(UNITY_MATRIX_P, pos); o.vertex = pos; return o; } fixed4 frag(v2f i) : SV_Target { return fixed4(1, 0, 0, 0); } ENDCG } } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 참고로 <a target='tab' href='https://github.com/g-truc/glm/blob/master/glm/ext/matrix_clip_space.inl'>https://github.com/g-truc/glm/blob/master/glm/ext/matrix_clip_space.inl</a> 소스 코드를 보면 알 수 있지만 동일한 값을 다음과 같이 다르게 구하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > projectionMatrix[0, 0] = (2 * near) / (right - left); = <span style='color: blue; font-weight: bold'>1.0 / (aspect * halfFov)</span> projectionMatrix[1, 1] = -(2 * near) / (top - bottom); = -<span style='color: blue; font-weight: bold'>1.0 / halfFov;</span> projectionMatrix[2, 2] = -far / (far - near); projectionMatrix[2, 3] = -(far * near) / (far - near); projectionMatrix[3, 2] = -1; </pre> <br /> 2개의 공식이 왜 같은지는 다음과 같이 풀어 보면 쉽게 이해할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > (2 * near) / (right - left) = (2 * near) / (right * 2) // 어차피 left == -right이므로. = near / right // 공통 인수 2 약분 = near / (top * aspect) // right = top * aspect이므로. = near / (halfFov * near * aspect) // top = halfFov * near = 1 / (halfFov * aspect) // 공통 인수 near 약분 -(2 * near) / (top - bottom) = -(2 * near) / (2 * top) // 어차피 bottom == -top이므로. = -near / top // 공통 인수 2 약분 = -near / (halfFov * near) // top = halfFov * near = -1 / halfFov // 공통 인수 near 약분 </pre> <br /> 그러니까 결국, left, right, bottom, top을 구할 필요가 없었던 것입니다. 따라서 shader에서의 소스 코드는 다음과 같이 더 간단해집니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > v2f vert(appdata v) { float4 pos; v2f o; float nearPlane = _ProjectionParams.y; float farPlane = _ProjectionParams.z; float width = _ScreenParams.x; float height = _ScreenParams.y; float aspect = width / height; float4x4 m = UNITY_MATRIX_P; float halfFov = atan2(1, aspect * m[0].x); // DirectX를 사용하는 윈도우 환경에서의 투영 행렬 <span style='color: blue; font-weight: bold'>float p00 = 1 / (tan(halfFov) * aspect); float p11 = -1 / tan(halfFov);</span> float p22 = -farPlane / (farPlane - nearPlane); float p23 = -(farPlane * nearPlane) / (farPlane - nearPlane); float4x4 projectionMatrix; projectionMatrix[0] = float4(p00, 0, 0, 0); projectionMatrix[1] = float4(0, p11, 0, 0); projectionMatrix[2] = float4(0, 0, p22, p23); projectionMatrix[3] = float4(0, 0, -1, 0); pos = mul(unity_ObjectToWorld, v.vertex); pos = mul(UNITY_MATRIX_V, pos); pos = mul(projectionMatrix, pos); // pos = mul(UNITY_MATRIX_P, pos); o.vertex = pos; return o; } </pre> <br /> 그 외에 여유가 되시면 Unity가 아닌 실제 카메라 영상을 기준으로 한 다음의 설명도 읽어보시고. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 카메라 캘리브레이션 (Camera Calibration) ; <a target='tab' href='http://darkpgmr.tistory.com/32?category=460965'>http://darkpgmr.tistory.com/32?category=460965</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
5583
(왼쪽의 숫자를 입력해야 합니다.)