성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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 - unity_CameraWorldClipPlanes 내장 변수 의미</h1> <p> <a target='tab' href='https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html'>Unity 도움말 문서</a>를 보면, 내장 변수에서 left, right, bottom, top, near, far에 관한 정보를 담고 있는 unity_CameraWorldClipPlanes가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 변수: unity_CameraWorldClipPlanes[6] 형식: float4 설명: Camera frustum plane world space equations, in this order: left, right, bottom, top, near, far. </pre> <br /> 그런데, 이 값이 C# 스크립트에서 구할 수 있는 Projection Matrix의 decomposeProjection 값은 아닙니다. 다시 도움말 문서를 자세히 보면, 이 값들은 "Camera frustum plane world space <span style='color: blue; font-weight: bold'>equations</span>"라고 해서 뷰 프러스텀(View frustum)의 각 면들을 나타내는 평면 방정식(참고: <a target='tab' href='http://wonjayk.tistory.com/189?category=535169'>http://wonjayk.tistory.com/189?category=535169</a>)인 것입니다. <br /> <br /> 그럼 어떻게 값이 채워져 있는지 확인해 볼까요? ^^<br /> <br /> 가령, C# 스크립트에서 투영 행렬의 decomposeProjection을 shader에 전달해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void Update () { Camera camera = Camera.main; Shader.SetGlobalFloat("_top1", camera.projectionMatrix.decomposeProjection.top); Shader.SetGlobalFloat("_bottom1", camera.projectionMatrix.decomposeProjection.bottom); Shader.SetGlobalFloat("_left1", camera.projectionMatrix.decomposeProjection.left); Shader.SetGlobalFloat("_right1", camera.projectionMatrix.decomposeProjection.right); Shader.SetGlobalFloat("_near1", camera.projectionMatrix.decomposeProjection.zNear); Shader.SetGlobalFloat("_far1", camera.projectionMatrix.decomposeProjection.zFar); Matrix4x4 projection = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true); Shader.SetGlobalFloat("_top2", projection.decomposeProjection.top); Shader.SetGlobalFloat("_bottom2", projection.decomposeProjection.bottom); Shader.SetGlobalFloat("_left2", projection.decomposeProjection.left); Shader.SetGlobalFloat("_right2", projection.decomposeProjection.right); Shader.SetGlobalFloat("_near2", projection.decomposeProjection.zNear); Shader.SetGlobalFloat("_far2", projection.decomposeProjection.zFar) } </pre> <br /> "<a target='tab' href='Visual Studio Graphics Analyzer'>Visual Studio Graphics Analyzer</a>" 디버깅 환경으로 보면 다음과 같은 결과가 나오고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [초기 Scene 화면의 기본값] FOV = 60 Near = 0.3 Far = 1000 카메라 위치 = (0, 1, -10) [camera.projectionMatrix.decomposeProjection] _top1 0.173205100 _bottom1 -0.173205100 _left1 -0.308696200 _right1 0.308696200 _near1 0.300000000 _far1 1000.134000000 [GL.GetGPUProjectionMatrix(...).decomposeProjection] _top2 0.173309100 _bottom2 -0.173309100 _left2 0.308881500 _right2 -0.308881500 _near2 -0.300180100 _far2 0.300000000 </pre> <br /> 이때의 unity_CameraWorldClipPlanes 값은 이렇게 출력됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > unity_CameraWorldClipPlanes[0] x = 0.696933500, y = 0.000000000, z = 0.717135700, w = 7.171357000 == left unity_CameraWorldClipPlanes[1] x = -0.696933500, y = 0.000000000, z = 0.717135700, w = 7.171357000 == right unity_CameraWorldClipPlanes[2] x = 0.000000000, y = -0.866025400, z = 0.500000000, w = 5.866025000 == bottom unity_CameraWorldClipPlanes[3] x = 0.000000000, y = 0.866025400, z = 0.500000000, w = 4.133975000 == top unity_CameraWorldClipPlanes[4] x = 0.000000000, y = 0.000000000, z = 1.000000000, w = 10.300180000 == near unity_CameraWorldClipPlanes[5] x = 0.000000000, y = 0.000000000, z = 1.000000000, w = 9.700000000 == far </pre> <br /> camera.projectionMatrix.decomposeProjection의 값을 담고 있는 _top1, _bottom1, _left1, _right1, _near1, _far1 멤버는 월드 좌표계의 값이므로 이를 unity_CameraWorldClipPlanes 평면 방정식에 대입하면 (당연히 평면 위에 있는 점이므로) 0이 나와야 합니다.<br /> <br /> 가령 _top1 좌표는 (0.0, 0.173205100, 0.0) 값이 되는데, 근 평면이 카메라의 앞에 위치하므로 실제 _top1의 좌표는 카메라의 y 축 방향과 z 축 방향의 영향을 받아 월드 좌표계 상에서 (0, 1.173205100, -9.7) 위치에 해당합니다. _bottom1 좌표 역시 (0.0, -0.173205100, 0.0) 값인데, 카메라의 위치로 인해 y-축으로 +1, z-축으로 (-10 + 0.3)의 영향을 받아 (0.0 0.8267949 -9.7 1.0) 위치가 됩니다. 그리고 이것을 각각 unity_CameraWorldClipPlanes의 평면 방정식에 대입하면 결과가 0이 나와야 합니다. 이를 octave를 이용해 계산해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > format long g more off ucPlanes = [ 0.696933500 0.0 0.71713570, 7.1713570; -0.69693350, 0.0, 0.71713570, 7.1713570; 0.0 -0.86602540 0.5 5.8660250; 0.0 0.86602540 0.5 4.1339750; 0.0 0.0 1.0 10.300180; 0.0 0.0 1.0 9.700000] topPlane = ucPlanes(3,[1: 4]); top = [0.0 1.173205100 -9.7 1.0]; printf("topPlane * top' == %.6f\n", topPlane * top') bottomPlane = ucPlanes(4,[1: 4]); bottom = [0.0 0.8267949 -9.7 1.0]; printf("bottomPlane * bottom' == %.6f\n", bottomPlane * bottom') /* 출력 결과: topPlane * top' == -0.000000 bottomPlane * bottom' == 0.000000 */ </pre> <br /> 이렇게 0.0이 나옵니다. (정밀도를 높이면 0.0에 가깝긴 해도 소수점 7자리부터 유효 숫자가 나오긴 합니다.)<br /> <br /> 마찬가지로 left와 right에 대해서도,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > left = (-0.308696200, 0.0, .0.0) [카메라 위치 반영] => (-0.3086962 0 -9.7) right = (0.308696200, 0.0, .0.0) [카메라 위치 반영] => (0.3086962 0 -9.7) </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;' > left = [-0.3086962 0 -9.7 1.0] leftPlane = ucPlanes(1,[1:4]); printf("leftPlane * left' == %.6f\n", leftPlane * left') rightPlane = ucPlanes(2,[1: 4]); right = [0.30869620 0.0 -9.7 1.0]; printf("rightPlane * right' == %.6f\n", rightPlane * right') /* 출력 결과: leftPlane * left' == -0.000000 rightPlane * right' == -0.000000 */ </pre> <br /> 의도한 값이 나옵니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 문제는 near와 far입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > unity_CameraWorldClipPlanes[4] x = 0.000000000, y = 0.000000000, z = 1.000000000, w = 10.300180000 == near unity_CameraWorldClipPlanes[5] x = 0.000000000, y = 0.000000000, z = 1.000000000, w = 9.700000000 == far </pre> <br /> 단적으로 far의 월드 좌표계 위치가 (0.0, 1.0, 990)이 될 텐데 "z + 9.7 = 0"의 평면 방정식에 대입해 봐야 절대 0이 나올 수 없습니다. 그렇다면 아마도 저건 평면 방정식이라기보다는 다른 의미를 나타내는 것 같은데 딱히 그에 대한 도움말을 찾을 수가 없습니다.<br /> <br /> unity_CameraWorldClipPlanes을 사용하는 shader 코드를 검색해 봤는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > VoxelGame / Assets / Shaders / CGIncludes / Tessellation.cginc ; <a target='tab' href='https://github.com/joetex/VoxelGame/blob/master/Assets/Shaders/CGIncludes/Tessellation.cginc'>https://github.com/joetex/VoxelGame/blob/master/Assets/Shaders/CGIncludes/Tessellation.cginc</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float UnityDistanceFromPlane (float3 pos, float4 plane) { float d = dot (float4(pos,1.0f), plane); return d; } bool UnityWorldViewFrustumCull (float3 wpos0, float3 wpos1, float3 wpos2, float cullEps) { float4 planeTest; // left planeTest.x = (( UnityDistanceFromPlane(<span style='color: blue; font-weight: bold'>wpos0, unity_CameraWorldClipPlanes[0]</span>) > -cullEps) ? 1.0f : 0.0f ) + (( UnityDistanceFromPlane(wpos1, unity_CameraWorldClipPlanes[0]) > -cullEps) ? 1.0f : 0.0f ) + (( UnityDistanceFromPlane(wpos2, unity_CameraWorldClipPlanes[0]) > -cullEps) ? 1.0f : 0.0f ); ...[생략]... // has to pass all 4 plane tests to be visible return !all (planeTest); } float4 UnityEdgeLengthBasedTessCull (float4 v0, float4 v1, float4 v2, float edgeLength, float maxDisplacement) { <span style='color: blue; font-weight: bold'>float3 pos0 = mul(_Object2World,v0).xyz; float3 pos1 = mul(_Object2World,v1).xyz; float3 pos2 = mul(_Object2World,v2).xyz;</span> float4 tess; if (UnityWorldViewFrustumCull(pos0, pos1, pos2, maxDisplacement)) ...[생략]... } </pre> <br /> 보는 바와 같이 _Object2World로 변환한 3개의 vertex를 절두체 평면 중 left, right, top, bottom까지만 비교하고 있습니다. 즉, unity_CameraWorldClipPlanes[4]와 unity_CameraWorldClipPlanes[5]에 대한 계산은 하지 않고 있는 것입니다.<br /> <br /> 한 가지 재미있는 점은, camera.projectionMatrix와 GL.GetGPUProjectionMatrix로부터 넘겨받은 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] _top1 0.173205100 _bottom1 -0.173205100 _left1 -0.308696200 _right1 0.308696200 <span style='color: blue; font-weight: bold'>_near1 0.300000000 _far1 1000.134000000 </span> [GL.GetGPUProjectionMatrix(...).decomposeProjection] _top2 0.173309100 _bottom2 -0.173309100 _left2 0.308881500 _right2 -0.308881500 <span style='color: blue; font-weight: bold'>_near2 -0.300180100 _far2 0.300000000 </span> </pre> <br /> _near1, _far1의 값이 _near2와 _far2로 변경된 것을 unity_CameraWorldClipPlanes로도 구할 수 있다는 점입니다. unity_CameraWorldClipPlanes의 near와 far에 해당하는 평면 방정식을 그냥 좌표라고 보고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > near (x = 0.000000000, y = 0.000000000, z = 1.000000000, w = 10.300180000) far (x = 0.000000000, y = 0.000000000, z = 1.000000000, w = 9.700000000) </pre> <br /> View matrix, 즉 shader 내에서 UNITY_MATRIX_V(unity_MatrixV)와 곱하면 (부호가 반대지만) _near2와 _far2의 값을 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [이때의 view matrix] matV = [1.0 0.0 0.0 0.0; 0.0 1.0 0.0 -1.0; 0.0 0.0 -1.0 -10.0; 0.0 0.0 0.0 1.0]; ucPlanes(5, [1:4]) * matV(:,4) == <span style='color: blue; font-weight: bold'>0.300180000</span> ucPlanes(6, [1:4]) * matV(:,4) == <span style='color: blue; font-weight: bold'>-0.300000000</span> </pre> <br /> 혹시 ^^ unity_CameraWorldClipPlanes[4], unity_CameraWorldClipPlanes[5]에 대한 정체를 아시는 분은 덧글 부탁드립니다.<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;' > GeometryUtility.CalculateFrustumPlanes ; <a target='tab' href='https://docs.unity3d.com/ScriptReference/GeometryUtility.CalculateFrustumPlanes.html'>https://docs.unity3d.com/ScriptReference/GeometryUtility.CalculateFrustumPlanes.html</a> </pre> <br /> 이 메서드가 반환하는 값은 unity_CameraWorldClipPlanes 값과는 다르게 Unity 월드에서 바로 사용할 수 있는 Plane 3D 객체입니다. 그래서 도움말의 예제 코드를 보면 이를 이용해 평면을 보이도록 하는 코드가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void Start () { Plane[] planes = GeometryUtility.CalculateFrustumPlanes(Camera.main); for (int i = 0; i < 6; ++i) { GameObject p = GameObject.CreatePrimitive(PrimitiveType.Plane); p.name = "Plane " + i.ToString(); p.transform.position = -planes[i].normal * planes[i].distance; p.transform.rotation = Quaternion.FromToRotation(Vector3.up, planes[i].normal); } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로 shader에서 디버깅할 때 변숫값을 알아내기 편하도록 하는 팁을 하나 적겠습니다. ^^ 가령, 이번 예제에서처럼 C# 스크립트에서 shader에 값을 전달할 때 다음과 같이만 처리하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float _top1; float _bottom1; float _left1; float _right1; float _near1; float _far1; float _top2; float _bottom2; float _left2; float _right2; float _near2; float _far2; v2f vert (appdata v) { float4 pos; v2f o; pos = mul(unity_ObjectToWorld, v.vertex); pos = mul(UNITY_MATRIX_V, pos); pos = mul(UNITY_MATRIX_P, pos); o.vertex = pos; return o; } </pre> <br /> shader 빌드 과정에서 모두 사라져 버려 디버깅 단계에서는 _top1, ... , _far2의 값들을 전혀 볼 수 없습니다. 그나마 변수를 사용하겠다고 해서 다음과 같은 식으로만 값을 추출한 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float n = _ProjectionParams.y; </pre> <br /> n을 사용하는 코드가, 다시 말해 실질적으로 vertex shader의 수행 결과에 반영되지 않는 값이면 shader 빌드 과정 중에 저 값들도 모두 없애 버리므로 역시 디버깅 중에 값을 확인할 수 없습니다. 그래서 제 경우에 다음과 같은 식으로 vertex output 인자에 임의 변수를 추가하고 그 값의 출력에 기여를 하도록 일부러 코드를 넣어 둡니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct v2f { float4 vertex : SV_POSITION; float p : TEXCOORD0; float temp : TEXCOORD1; }; float _top1; float _bottom1; float _left1; float _right1; float _near1; float _far1; float _top2; float _bottom2; float _left2; float _right2; float _near2; float _far2; float vectorTest(float m1, float4 m2) { if (m1 > m2.x || m1 > m2.y || m1 > m2.z || m1 > m2.w) { return m1; } return m2.x; } float floatTest(float m1, float m2) { if (m1 > m2) { return m1; } return m2; } v2f vert (appdata v) { float4 pos; v2f o; o.p = floatTest(o.p, _top1); o.p = floatTest(o.p, _bottom1); o.p = floatTest(o.p, _left1); o.p = floatTest(o.p, _right1); o.p = floatTest(o.p, _near1); o.p = floatTest(o.p, _far1); o.p = floatTest(o.p, _top2); o.p = floatTest(o.p, _bottom2); o.p = floatTest(o.p, _left2); o.p = floatTest(o.p, _right2); o.p = floatTest(o.p, _near2); o.p = floatTest(o.p, _far2); o.p = vectorTest(o.p, unity_CameraWorldClipPlanes[0]); o.p = vectorTest(o.p, unity_CameraWorldClipPlanes[1]); o.p = vectorTest(o.p, unity_CameraWorldClipPlanes[2]); o.p = vectorTest(o.p, unity_CameraWorldClipPlanes[3]); o.p = vectorTest(o.p, unity_CameraWorldClipPlanes[4]); o.p = vectorTest(o.p, unity_CameraWorldClipPlanes[5]); pos = mul(unity_ObjectToWorld, v.vertex); pos = mul(UNITY_MATRIX_V, pos); pos = mul(UNITY_MATRIX_P, pos); o.vertex = pos; o.temp = unity_CameraWorldClipPlanes[1].x; return o; } </pre> <br /> 이런 식으로 해주면 디버깅 중에 다음과 같이 "Locals" 창에서 값을 잘 확인할 수 있습니다.<br /> <br /> <img alt='shader_debug_helper_code.png' src='/SysWebRes/bbs/shader_debug_helper_code.png' /><br /> <br /> 그렇습니다, shader의 값을 눈으로 확인하는 것은 굉장히 번거로운 절차를 요구하지만 그래도 공부하는 용도로는 딱입니다. ^^<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
4054
(왼쪽의 숫자를 입력해야 합니다.)