성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
글쓰기
제목
이름
암호
전자우편
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 (10) - 빌보드 구현</h1> <p> 지난번에 만든 shader에 이어서 살펴보겠습니다.<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 (9) - 투명 배경이 있는 텍스처 입히기 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11631'>http://www.sysnet.pe.kr/2/0/11631</a> </pre> <br /> 위의 작업에 따라 Unity 세계에 배치한 나무 한 그루가 있을 때 카메라를 움직여 나무가 위치한 곳의 옆으로 이동해 보겠습니다. 이를 위해 Unity에서 화살표 키를 이용해 카메라를 움직일 수 있도록 다음과 같은 작업을 해둡니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - Unity에서 캐릭터가 바라보는 방향을 기준으로 카메라의 위치 이동 및 회전하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11632'>http://www.sysnet.pe.kr/2/0/11632</a> </pre> <br /> 따라서, 카메라를 적절하게 나무 옆으로 이동해 바라보면,<br /> <br /> <img alt='billboard_0.png' src='/SysWebRes/bbs/billboard_0.png' /><br /> <br /> 나무가 얇은 판 위에 그려진 것에 불과하기 때문에 다음과 같이 보이게 됩니다.<br /> <br /> <img alt='billboard_1.png' src='/SysWebRes/bbs/billboard_1.png' /><br /> <br /> 이럴 때, 만약 Plane 3D 객체를 카메라 방향으로 회전시켜주면 어떨까요? <br /> <br /> <img alt='billboard_2.png' src='/SysWebRes/bbs/billboard_2.png' /><br /> <br /> 그럼, 사용자가 y-축을 기준으로 x-z 평면에서 카메라를 이리저리 돌리며 봐도 다음과 같이 나무로 볼 수 있게 됩니다.<br /> <br /> <img alt='billboard_3.png' src='/SysWebRes/bbs/billboard_3.png' /><br /> <br /> 바로 이렇게 구현한 객체를 "빌보드(billboard)"라고 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그럼, 이 빌보드를 shader로 구현해 볼까요? ^^<br /> <br /> 어차피, x-z 평면에서 돌아다니는 거라면 y-축에 대해 카메라와 물체가 이루는 각을 구하고 그만큼 회전을 시켜주면 됩니다. 3차원 상에서의 두 점이 이루는 각은 이미 다음의 글에서 아크탄젠트를 이용해 구하는 방법을 설명했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 3D 공간에서 두 점이 이루는 각도 구하기 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11636'>http://www.sysnet.pe.kr/2/0/11636</a> </pre> <br /> 그리고 shader 내에서 World Matrix를 구하는 것도 설명했으니,<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 - World matrix(unity_ObjectToWorld)로부터 TRS(이동/회전/크기) 행렬로 복원하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11637'>http://www.sysnet.pe.kr/2/0/11637</a> Unity - World matrix(unity_ObjectToWorld)로부터 Position, Rotation, Scale 값을 복원하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11640'>http://www.sysnet.pe.kr/2/0/11640</a> </pre> <br /> 이것을 이용하면 다음과 같이 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) { v2f o; float4x4 scaleMatrix; // Scale 행렬 vector sx = vector(unity_ObjectToWorld._m00, unity_ObjectToWorld._m10, unity_ObjectToWorld._m20, 0); vector sy = vector(unity_ObjectToWorld._m01, unity_ObjectToWorld._m11, unity_ObjectToWorld._m21, 0); vector sz = vector(unity_ObjectToWorld._m02, unity_ObjectToWorld._m12, unity_ObjectToWorld._m22, 0); float scaleX = length(sx); float scaleY = length(sy); float scaleZ = length(sz); scaleMatrix[0] = float4(scaleX, 0, 0, 0); scaleMatrix[1] = float4(0, scaleY, 0, 0); scaleMatrix[2] = float4(0, 0, scaleZ, 0); scaleMatrix[3] = float4(0, 0, 0, 1); float4 pos = v.vertex; float4 worldPos = float4(mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).xyz, 0); float4 cameraPos = float4(_WorldSpaceCameraPos.xyz, 0); vector cameraDir = cameraPos - worldPos; <span style='color: blue; font-weight: bold'>float xAngle = atan2(cameraDir.z, cameraDir.y); float yAngle = atan2(cameraDir.z, cameraDir.x); float zAngle = atan2(cameraDir.x, cameraDir.y); xAngle = -radians(90); yAngle = -(radians(90) + yAngle); zAngle = 0; float4x4 rotationMatrix = GetRotationMatrix(xAngle, yAngle, 0);</span> float4x4 moveMatrix; moveMatrix[0] = float4(1, 0, 0, unity_ObjectToWorld._m03); moveMatrix[1] = float4(0, 1, 0, unity_ObjectToWorld._m13); moveMatrix[2] = float4(0, 0, 1, unity_ObjectToWorld._m23); moveMatrix[3] = float4(0, 0, 0, 1); float4x4 transformMatrix = mul(mul(moveMatrix, rotationMatrix), scaleMatrix); pos = mul(transformMatrix, pos); o.pos = mul(UNITY_MATRIX_VP, pos); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } </pre> <br /> 지난 글과 달라진 점은, rotationMatrix를 Unity 에디터의 Transform에서 설정한 값을 쓰지 않고 직접 설정했다는 점입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float4 pos = v.vertex; float4 worldPos = float4(mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).xyz, 0); float4 cameraPos = float4(_WorldSpaceCameraPos.xyz, 0); vector cameraDir = cameraPos - worldPos; float xAngle = atan2(cameraDir.z, cameraDir.y); float yAngle = atan2(cameraDir.z, cameraDir.x); float zAngle = atan2(cameraDir.x, cameraDir.y); xAngle = -radians(90); yAngle = -(radians(90) + yAngle); zAngle = 0; float4x4 rotationMatrix = GetRotationMatrix(xAngle, yAngle, 0); </pre> <br /> 왜냐하면, 보통 빌보드로 사용될 mesh는 Plane 객체이기 때문이고 이 객체는 생성 시 x-z 평면 상에 누워있기 때문입니다. 따라서, 이 Plane 객체를 세우기 위해 xAngle은 무조건 -90도를 설정했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > xAngle = -radians(90); </pre> <br /> y-축의 변화는 카메라와 물체가 이루는 각에 따라 달라지므로 <a target='tab' href='http://www.sysnet.pe.kr/2/0/11636'>두 점 간의 각도를 구하고</a> 이에 따라 반응하도록 yAngle을 설정합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > yAngle = -(radians(90) + yAngle); </pre> <br /> 마지막으로 z-축은 빌보드가 어차피 3차원 이미지를 렌더링하는 용도는 아니므로 x-z 평면 상에서만 잘 동작하면 되기 때문에 0으로 설정합니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1328&boardid=331301885'>첨부 파일은 빌보드 구현 shader 전체 코드</a>입니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> shader뿐만 아니라 C# 스크립트로도 빌보드를 구현할 수 있습니다. 나무 객체에 스크립트를 얹고 카메라가 있는 방향으로 회전을 시켜주면 되는데, 다음의 글은 이에 대해 설명하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 유니티3D, 게임 오브젝트를 특정 대상을 바라보게 하기 ; <a target='tab' href='http://legacy.tistory.com/81'>http://legacy.tistory.com/81</a> </pre> <br /> 처음부터 만들어 볼까요? ^^<br /> <br /> 우선, Plane 3D 객체를 놓고 texture를 보여줄 수 있는 shader를 얹으면 <a target='tab' href='http://www.sysnet.pe.kr/2/0/11631'>지난 글에 설명한 것처럼 바닥에 누워있는 평면</a>이 생깁니다. 어차피 스크립트를 통해 회전시킬 것이기 때문에 이번에는 지난번처럼 material의 Texture 설정이나 x-축을 기준으로 한 회전을 미리 해둘 필요가 없습니다. 그다음 아래의 스크립트를 적용하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System.Collections; using System.Collections.Generic; using UnityEngine; public class rotatePlane : MonoBehaviour { public Transform Camera; void Start() { } void Update() { if (Camera != null) { this.transform.LookAt(Camera); } } } </pre> <br /> 여전히 누워 있는 상태는 변함이 없지만 이번에는 지난 글과는 다르게 누워 있는 나무의 방향이 반대입니다.<br /> <br /> [그림: 좌측 - LookAt 적용 후, 우측 - LookAt 적용 전]<br /> <img alt='billboard_4.png' src='/SysWebRes/bbs/billboard_4.png' /><br /> <br /> 이 때문에 나무를 세우기 위해서 전에는 -90도를 했지만, 이번에는 +90도를 하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System.Collections; using System.Collections.Generic; using UnityEngine; public class rotatePlane : MonoBehaviour { public Transform Camera; void Start() { } void Update() { if (Camera != null) { this.transform.LookAt(Camera); <span style='color: blue; font-weight: bold'>this.transform.Rotate(Vector3.right * 90);</span> } } } </pre> <br /> 이렇게 하고 실행하면 마치 달이 지구를 바라보듯이 해당 물체는 카메라의 움직임에 따라 언제나 보이는 위치로 회전을 하게 됩니다. 위의 방법에서는 z-축을 기반으로 xy 평면 상에서 카메라를 움직여도 나무가 서 있는 효과가 납니다. 하지만 그렇다고 해서 이것이 2차원 평면이 가진 빌보드의 제약을 해결한 것은 아닙니다. 일례로, y-축으로 카메라를 이동해 하늘에서 바닥을 내려다본다고 했을 때 나무가 도로와 수직으로 서 있는 모습이 아닌, 도로와 평행으로 깔려 있는 모습으로 렌더링되기 때문입니다. 게다가, y-축의 정점을 지나는 경우 나무가 거꾸로 회전하는 변화도 거슬립니다. 결국, 빌보드는 모든 3차원 공간에서의 카메라 이동 용으로는 부적합하다는 것을 인정해야 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 검색해 보면, 빌보드를 색다른 방식으로 구현한 shader 소스 코드를 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > kaiware007/billboard.shader ; <a target='tab' href='https://gist.github.com/kaiware007/8ebad2d28638ff83b6b74970a4f70c9a'>https://gist.github.com/kaiware007/8ebad2d28638ff83b6b74970a4f70c9a</a> </pre> <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) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.uv; // billboard mesh towards camera float3 vpos = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz); float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23, 1); float4 viewPos = mul(UNITY_MATRIX_V, worldCoord) + float4(vpos, 0); float4 outPos = mul(UNITY_MATRIX_P, viewPos); o.pos = outPos; UNITY_TRANSFER_FOG(o,o.vertex); return o; } </pre> <br /> 분석해 보면 이렇습니다. 아래의 연산을 통해 Unity Editor의 Inspector 창에 설정된 Rotation, Scale 관련한 행렬을 분리해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float3x3 rsMatrix = (float3x3)unity_ObjectToWorld; </pre> <br /> 현재 vertex의 xyz 값을 곱하면서 위치 이동 없이 회전과 크기만 변한 상태의 vertex 위치를 구해둡니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float3 vpos = mul(rsMatrix, v.vertex.xyz); </pre> <br /> 그다음 unity_ObjectToWorld로부터 Position 좌표를 가져오고 있지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23, 1); </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;' > float4x4 worldMatrix; worldMatrix[0] = float4(1, 0, 0, unity_ObjectToWorld._m03); worldMatrix[1] = float4(0, 1, 0, unity_ObjectToWorld._m13); worldMatrix[2] = float4(0, 0, 1, unity_ObjectToWorld._m23); worldMatrix[3] = float4(0, 0, 0, 1); float4 objectCenterPosition = float4(0, 0, 0, 1); // 모델의 중심 값을 기준으로, float4 worldCoord = mul(worldMatrix, objectCenterPosition); // 위치 이동한 좌표를 구하고, </pre> <br /> 즉, 모델의 중심 좌표에 대해 World 좌표계에 이동한 위치를 구한 것입니다. 이렇게 위치 이동한 것을 카메라 좌표계 기준으로 옮겨 준 다음,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float4 viewCenterPos = mul(UNITY_MATRIX_V, worldCoord); </pre> <br /> 카메라 좌표계에서의 중심점을 기준으로 회전/크기 변경된 vertex의 위치를 오프셋 연산합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float4 viewPos = viewCenterPos + float4(vpos, 0); </pre> <br /> 따라서, 원래 Unity Editor의 Scene 화면에서 보이는 모습 그대로 카메라의 이동에 관계없이 그대로 보이게 되는 것입니다. 마지막으로 2차원 화면에 투영해 주는 것으로 끝!<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float4 outPos = mul(UNITY_MATRIX_P, viewPos); </pre> <br /> 따라서 이 방법은 카메라의 좌표에 따라 회전하지 않기 때문에 스크립트로 LookAt + Rotate한 것과 비교해 다른 동작을 합니다. 아래의 화면을 보면,<br /> <br /> <img alt='billboard_5.png' src='/SysWebRes/bbs/billboard_5.png' /><br /> <br /> 좌측의 나무는 shader에서 한 것으로 카메라 위치가 바뀌어도 한결같이 정면을 바라보며 있습니다. 하지만 우측의 나무는 LookAt + Rotate한 것이기 때문에 회전을 하게 되고 나무의 밑동을 보면 방향을 틀은 것을 볼 수 있습니다. 실제로 2개의 방식을 혼용하면 이질적으로 동작하므로 하나로 통일하는 것이 좋습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, shader의 내장 변수 중 UNITY_MATRIX_IT_MV의 구성이 재미있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Built-in shader variables ; <a target='tab' href='https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html'>https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > UNITY_MATRIX_IT_MV - Inverse transpose of model * view matrix. </pre> <br /> 이 행렬로부터 카메라의 Up 벡터와 카메라의 시선이 향하는 방향 벡터를 다음과 같이 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Camera Up vector in vertex shader. ; <a target='tab' href='https://forum.unity.com/threads/camera-up-vector-in-vertex-shader.144635/'>https://forum.unity.com/threads/camera-up-vector-in-vertex-shader.144635/</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > float3 upvec = UNITY_MATRIX_IT_MV[1].xyz; float3 viewDir = UNITY_MATRIX_IT_MV[2].xyz; </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1951
(왼쪽의 숫자를 입력해야 합니다.)