2011년 12월 6일 화요일

[포프의 쉐이더 입문강좌] 02. 진짜 쉬운 빨강쉐이더 Part 1

이전편 보기

샘플파일 받기

제2장 진짜 쉬운 빨강쉐이더

이 장에서 새로 배우는 HLSL

  • float4: 4개의 성분을 가지는 벡터 데이터형
  • float4x4: 4 X 4 행렬 데이터형
  • mul(): 곱하기 함수. 거의 모든 데이터형을 변수로 받음.
  • POSITION: 정점위치 시맨틱. 정점데이터 중 위치정보만을 불러옴.


이 장에서 새로 사용하는 수학

  • 3D 공간변환 - 행렬 곱을 이용함.



'제1장: 쉐이더란 무엇이죠?'에서 쉐이더란 픽셀의 위치와 색을 계산하는 함수라고 말씀드렸습니다. 그렇다면 이번 장에서는 실제로 픽셀의 위치와 색을 계산하는 쉐이더를 만들어봐야겠죠? 처음 쉐이더를 짜보시는 분들도 쉽게 이해하실 수 있게끔 매우 간단한 쉐이더 프로그램을 만들어 보겠습니다. 우선 렌더몽키에서 빨간색 공을 그리는 쉐이더를 작성해보면서 HLSL 문법을 처음으로 접해보는 게 좋겠군요! (이렇게 단색을 출력하는 쉐이더는 디버깅을 할 때도 유용하게 쓰입니다.) 렌더몽키에서 쉐이더를 작성하면 그 결과를 .fx 파일로 익스포트(export)해서 이걸 DirectX 프레임워크에 그대로 가져다 쓸 수도 있습니다.

기초설정
다음의 단계를 따라서 기초적인 설정을 마무리합시다.


  1. 렌더몽키를 시작합니다. 무서운(?) 원숭이 얼굴이 잠시 스쳐 지나간 뒤에 빈 작업공간(workspace)가 등장할 것입니다. 
  2. Workspace 패널 안에서 Effect Workspace위에 마우스 오른쪽 버튼을 누릅니다. 팝업 메뉴가 등장할 겁니다.
  3. 팝업메뉴에서 Add Default Effect > DirectX > DirectX를 선택합니다. 이제 미리 보기(preview) 창에 빨간색 공 하나가 보이죠?
  4. Workspace패널에 Deafult_DirectX_Effect라는 새로운 쉐이더도 추가되었을 것입니다. 쉐이더의 이름을 ColorShader로 바꿉니다.
  5. 이제 화면이 아래와 같을 것입니다.


그림 2.1. 기초설정을 마친 렌더몽키 프로젝트


정점쉐이더
이제 ColorShader옆에 있는 더하기(+) 표시를 누릅니다. 제일 아래쪽에 Pass 0이 보이시죠? 그 옆에 있는 더하기 표시를 다시 누르세요. 이제 Vertex Shader를 더블클릭하시면 오른쪽 쉐이더 편집기 안에 Vertex Shader코드가 등장할 겁니다. 사실 여기에 들어있는 코드가 이미 빨간 공을 그리고 있지만 저희는 한 줄씩 연습을 해봐야 하니 이 속에 있는 코드를 모두 지우겠습니다.

코드를 다 지우셨나요? 그렇다면 이제 본격적으로 시작해보죠! 우선 한 눈에 보실 수 있게끔 정점쉐이더 코드를 전부 보여드린 뒤 한 줄씩 설명해 나가도록 하겠습니다.

struct VS_INPUT 
{
   float4 mPosition : POSITION;
};

struct VS_OUTPUT 
{
   float4 mPosition : POSITION;
};

float4x4 gWorldMatrix;         
float4x4 gViewMatrix;          
float4x4 gProjectionMatrix;    


VS_OUTPUT vs_main( VS_INPUT Input )
{
   VS_OUTPUT Output;

   Output.mPosition = mul( Input.mPosition, gWorldMatrix );
   Output.mPosition = mul( Output.mPosition, gViewMatrix );
   Output.mPosition = mul( Output.mPosition, gProjectionMatrix );
   
   return Output;
}



전역변수 vs 정점데이터
쉐이더에서 사용할 수 있는 입력 값으로는 전역변수와 정점데이터가 있습니다. 이 둘을 구분 짓는 기준은 한 물체를 구성하는 모든 정점이 동일한 값을 사용하느냐의 여부입니다. 만약 동일한 값을 사용한다면 이것은 전역변수가 될 수 있지만 각 정점마다 다른 값을 사용한다면 당연히 전역변수는 안되겠지요. 그 대신 정점 버퍼, 즉 정점 데이터의 일부로 이 값을 받아들여야 합니다.

전역변수의 예로는 월드행렬, 카메라의 위치 등이 있고, 정점데이터 변수의 예로는 정점의 위치, UV좌표 등이 있습니다.

정점쉐이더 입력데이터
우선 정점쉐이더에서 입력으로 받을 데이터들을 VS_INPUT이라는 구조체로 선언해보겠습니다.

struct VS_INPUT
{
    float4 mPosition : POSITION;
};

'제1장: 쉐이더란 무엇이죠?'에서 정점쉐이더의 가장 중요한 임무는 각 정점의 위치를 공간 변환하는 것이라고 했던 거 기억하시나요? 그러기 위해서는 정점의 위치를 입력으로 받아야 하죠? 그게 바로 위 구조체가 멤버변수 mPosition을 통해 정점의 위치를 얻어오는 이유입니다. 이 변수가 DirectX의 정점버퍼(버텍스 버퍼(vertex buffer)라고도 합니다.)로부터 위치정보를 구해올 수 있는 이유는 POSITION이라는 시맨틱(semantic, 태그(tag) 정도로 이해하시는 게 편할 겁니다.)  때문입니다. 정점버퍼에는 정점의 위치, UV좌표, 법선 등을 비롯한 다양한 정보가 담겨 있을 수 있는데 이 중에서 필요한 정보만을 쏙쏙 빼오는 것을 시맨틱이라고 해두죠.

따라서 float4 mPosition : POSITION; 이라는 코드는 '정점데이터에서 위치(POSITION) 정보를 가져와서 mPosition에 대입해라!'라는 명령입니다.

아차! 그렇다면 float4는 뭘까요? 이건 변수의 데이터형입니다. float4는 HLSL자체에서 지원하는 데이터형의 하나로 4개의 성분(x ,y, z, w)을 가지는 벡터입니다. 각 성분은 부동소수점(floating-point)형 입니다. HLSL은 float4외에도 float, float2, float3 등의 데이터형을 지원합니다. (참고로 GPU는 부동소수점 벡터를 처리하는데 최적화된 장치입니다. 따라서 쉐이더에서 사용하는 기본적인 데이터형은 정수가 아닌 부동소수점입니다. 정수는 오히려 쉐이더의 성능을 저하시키는 요인입니다.)

정점쉐이더 출력데이터
정점쉐이더의 입력데이터를 선언해봤으니 이제 출력데이터를 살펴봐야겠죠? '제1장: 쉐이더란 무엇이죠?'에서 보여드렸던 초 간단 GPU 파이프라인의 그림을 기억하시나요? 각 픽셀의 위치를 찾아내려면 정점쉐이더가 위치변환 결과를 래스터라이저에 전달해줘야만 했습니다. 따라서 정점쉐이더는 반드시 위치변환 결과를 반환해야 합니다. 자, 그렇다면 정점쉐이더 출력데이터 구조체를 VS_OUTPUT이란 이름으로 선언해보지요!

struct VS_OUTPUT 
{
    float4 mPosition : POSITION;
};

float4형으로 위치데이터를 반환하면서 '이것은 위치(POSITION)요!'라는 시맨틱을 붙여준 거 보이시죠?

전역변수
정점쉐이더에서 공간 변환을 할 때, 사용해야 할 전역변수들이 몇 있는데 그 전에 공간 변환이 무언지부터 설명해 드려야 할 듯 싶군요.

3D 공간변환
3D물체를 모니터에 그리려면 정점들의 위치를 공간 변환해야 한다고 말씀드렸습니다. 그렇다면 과연 어떤 공간들을 거쳐야 3D물체를 모니터에 보여줄 수 있을까요? 사과를 예로 들어보죠.

물체공간
자, 일단 사과를 손에 쥐어봅시다. 사과의 중앙을 원점으로 삼고 그 점을 시작으로 오른쪽(+x), 위쪽(+y), 앞쪽(+z)으로 3개의 축을 만들어 볼까요? 이제 원점으로부터 사과의 표면까지의 거리를 이리저리 재보면 각 점들을 (x, y, z) 좌표로 표현할 수 있겠죠? 그리고 이 정점들을 3개씩 묶어 삼각형들을 만들면 폴리곤으로 사과모델을 만들 수 있겠네요.

이제 사과를 손에 쥔 채 팔을 이리저리 움직여봅시다. 사과를 어디로 움직이던 간에 원점으로부터 각 정점까지의 거리는 변하지 않죠? 이것이 바로 물체공간(object space) 또는 지역공간(local space)입니다. 물체공간에서는 각 물체(3D 모델)가 자신만의 좌표계를 가지므로 다수의 물체를 통일적으로 처리하기 어렵습니다.

그림 2.2. 물체공간의 예



월드공간
이제 사과를 모니터 옆에 놓아볼까요? 모니터도 물체니까 자신만의 물체공간을 가지고 있겠군요. 이 둘을 통일적으로 처리하고 싶은데 그러려면 어떻게 해야 할까요? 이 두 물체를 같은 공간으로 옮겨오면 될 거 같은데요? 그러면 새로운 공간을 하나 만들어야겠군요. 현재 계신 방의 입구를 원점으로 삼고 오른쪽, 위쪽, 앞쪽으로 +x, +y, +z인 3개의 축을 만들어보죠. 이제 그 원점에서부터 모니터를 구성하는 정점들까지의 거리를 재면 새로운 (x, y, z) 좌표로 정점들을 표현할 수 있겠죠? 사과도 똑같은 방법으로 표현할 수 있겠네요. 이 새로운 공간을 월드공간(world space) 또는 세계공간이라고 합니다.

그림 2.3. 월드공간의 예



뷰공간
자, 그렇다면 이제 카메라를 가져다가 사진을 좀 찍어볼까요? 일단 위 두 물체들이 모두 사진 속에 들어오도록 사진을 찍고, 다음에는 이들이 전혀 보이지 않도록 전혀 엉뚱한 곳을 찍어봅시다. 이 두 사진은 확연히 다르죠? 처음 사진에서는 두 물체를 볼 수 있는데, 다른 사진에서는 흔적도 찾아볼 수 없군요. 그렇다면 이 두 사진 간에 뭔가 위치 변화가 있어야 한단 이야긴데 월드공간에서는 그 두 물체들의 위치가 전혀 변하지 않았는걸요? 아하! 그렇다면 이 카메라가 다른 공간을 사용하는 거로군요! 이렇게 카메라가 사용하는 공간을 뷰공간(view space)이라고 부릅니다. 뷰공간의 원점은 카메라 렌즈의 정 중앙이고 역시 그로부터 오른쪽, 위쪽, 앞쪽으로 3개의 축을 만들 수 있습니다.

그림 2.4 뷰공간의 예. 물체들이 카메라 안에 있음.


그림 2.5 뷰공간의 예. 물체들이 카메라 밖에 있음.


투영공간
일반 카메라로 사진을 찍으면 인간의 눈을 통해 보는 것과 마찬가지로 멀리 있는 물체는 조그맣게 보입니다. 근데 왜 우리 눈이 이렇게 작동하는지 아세요? 이건 인간의 시야가 좌우로 각각 100도 정도, 상하로 각각 75도 정도 되어서 그렇습니다. 따라서 멀리를 바라볼 수록 눈에 들어오는 범위가 넓어지는데 이 늘어난 범위를 일정한 크기의 망막에 담으려다 보니 멀리 있는 물체가 작게 보이는 거지요. 일반 카메라도 사람의 눈을 흉내 내는데 이와는 달리 직교카메라란 것도 있습니다. 직교카메라는 상하좌우로 퍼지는 시야를 가지지 않습니다. 무조건 앞쪽만 바라보지요. 따라서 직교카메라를 사용하면 거리에 상관없이 물체의 크기가 변하지 않습니다.

그러면 결국 카메라로 사진을 찍는 과정을 두 단계로 나눌 수 있는 것 같네요. 첫째는 월드공간에 있는 물체들을 카메라 공간으로 이동, 회전, 확대/축소시키는 단계고요, 둘째는 이렇게 새로운 공간에 위치된 물체들을 2D 이미지 위에 투영하는 것입니다. 이러면 첫 번째 단계를 뷰공간, 두 번째 단계를 투영공간이라고 확실히 구분할 수 있겠죠? 이제 직각투시법(orthogonal projection)을 사용하던 원근투시법을 사용하던 간에 뷰공간은 아무 영향을 받지 않겠네요. 그 대신 투영공간에서 이 투시법을 적용하겠죠.

이렇게 투영까지 마친 결과가 바로 화면에 보여지는 최종 이미지입니다.

정리
3D 그래픽에서 정점위치의 공간을 변환할 때 흔히 사용하는 방법이 정점의 위치벡터에 공간행렬을 곱하는 것입니다. 물체를 지역공간에서 화면공간까지 옮겨올 때 거치는 공간이 총 셋(월드공간, 뷰공간, 투영공간)이므로 행렬도 3개를 구해야 합니다. 참고로 각 공간의 원점과 세 축을 알면 그 공간을 나타내는 행렬을 쉽게 만들 수 있습니다.  (이 행렬을 직접 만드는 방법은 3D 수학책을 참조하시기 바랍니다. 이 책에서는 Direct3D에서 제공하는 함수를 사용해서 이 행렬들을 구성합니다.)

자, 그럼 여태까지 논한 모든 공간변환들을 정리해서 보여드리면 다음과 같습니다.

물체공간 ----------> 월드공간 --------> 뷰공간 ---------> 투영공간
              ⅹ월드행렬                   ⅹ뷰행렬                ⅹ투영행렬

위의 모든 행렬들은 각 정점마다 값이 변하지 않으니 전역변수로 선언하기에 적합하군요.

전역변수 선언
그럼, 이제 어떤 전역변수들이 필요한지 아시겠죠? 그렇습니다. 공간변환을 할 때 사용할 월드행렬, 뷰행렬, 투영행렬이 필요합니다. 정점쉐이더 코드에 다음의 세 라인을 삽입합시다.

float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;

float4x4라는 새로운 데이터형이 나왔군요? 이것도 역시 HLSL에서 지원하는 데이터형 중에 하나입니다. 4 X 4 행렬이라는 거 쉽게 아시겠죠? 이 외에도 float2x2, float3x3 등의 데이터형이 있습니다.

자, 그럼 이제 행렬들도 선언했는데 과연 누가 이 변수들에 값을 전달해줄까요? 보통 게임에서는 그래픽 엔진에서 전역변수들의 값을 대입해주는 코드가 있습니다. 렌더몽키에서는 변수시맨틱(variable semantic)을 통해 변수 값을 대입해줍니다. 그럼 변수시맨틱을 사용해보죠.


  1. Workspace 패널 안에서 ColorShader를 찾아 마우스 오른쪽 버튼을 누릅니다.
  2. 팝업메뉴에서 Add Variable > Matrix > Float(4x4)를 선택합니다. f4x4Matrix란 이름의 새로운 변수가 추가될 겁니다.
  3. gWorldMatrix로 변수의 이름을 변경합니다.
  4. 이제 gWorldMatrix 위에 마우스 오른쪽 버튼을 눌러, Variable Semantic > World를 선택합니다. 이게 바로 렌더몽키에서 변수 시맨틱을 통해 변수 값을 전달하는 방법입니다.
  5. 이제 위의 과정을 반복하여 뷰 행렬과 투영행렬을 만듭니다. 변수 명을 각각 gViewMatrix와 gProjectionMatrix를 만들고 View와 Projection 변수시맨틱을 대입합니다.
  6. 마지막으로 matViewProjection이란 변수를 지웁니다. 처음 이펙트를 만들 때 같이 딸려온 변수인데 저희는 이 대신 gViewMatrix와 gProjectionMatrix를 씁니다.


이 과정을 마치셨다면 Workspace 패널이 아래 그림처럼 보일 겁니다.

그림 2.6. 변수시맨틱을 대입한 뒤의 Workspace 패널


정점쉐이더 함수
이제 모든 준비작업이 끝났습니다. 드디어 정점쉐이더 함수를 작성할 때가 왔군요. 우선 함수헤더부터 볼까요?

VS_OUTPUT vs_main( VS_INPUT Input )
{

이 함수헤더가 의미하는 바는 이와 같습니다.

  • 이 함수의 이름은 vs_main이다.
  • 이 함수의 인수는 VS_INPUT 데이터형의 Input이다.
  • 이 함수의 반환값은 VS_OUPUT 데이터형이다.


C에서 함수를 정의하는 것과 별 차이가 없죠? HLSL은 C와 비슷한 문법을 사용한다고 전에 말씀드렸습니다. 자, 그럼 다음 라인을 보죠.

   VS_OUTPUT Output;

이건 그냥 함수의 끝에서 반환할 구조체를 선언한 것 뿐입니다. 함수헤더에서 선언했다시피 데이터형이 VS_OUTPUT인 거 보이시죠? VS_OUTPUT의 멤버로는 무엇이 있었죠? 투영공간으로 변환된 mPosition이 있었죠? 그럼 이제 공간변환을 해볼 차례군요! 우선, Input.mPosition에 담긴 모델공간 위치를 월드공간으로 변환합시다. 공간변환을 어떻게 한다고 했었죠? 정점위치에 행렬을 곱하는 거였네요. 그러면 float4형의 위치벡터와 float4x4 행렬을 곱해야겠네요? 행렬과 벡터를 곱하는 법을 찾기 위해 수학책을 뒤지실 필요는 없습니다. HLSL은 이미 여러 데이터형 간의 곱셈을 처리해주는 내장함수 mul()을 가지고 있습니다. 이 함수를 사용하면 공간변환이 이렇게 간단해집니다.

   Output.mPosition = mul( Input.mPosition, gWorldMatrix );

위의 코드는 모델공간에 존재하는 정점위치(Input.mPosition)에 월드행렬(gWorldMatrix)를 곱해서 그 결과(월드공간에서의 위치)를 Output.mPosition에 대입합니다. 이제 똑같은 방식으로 뷰공간과 투영공간으로 변환하면 됩니다.

   Output.mPosition = mul( Output.mPosition, gViewMatrix );
   Output.mPosition = mul( Output.mPosition, gProjectionMatrix );

복잡한 거 하나도 없죠? 자, 이제 무슨 일이 남았을까요? 정점쉐이더의 가장 중요한 임무는 모델공간에 있는 정점의 위치를 투영공간까지 변환하는 것이었으니까... 음.... 그 중요한 임무를 방금 막 마친 듯 한데요? 자, 그럼 이 결과를 반환하는 걸로 정점쉐이더를 마치겠습니다.

   return Output;
}

이제 F5를 눌러서 정점쉐이더를 한 번 컴파일 해보면 여전히 빨간 공이 보이죠? 그럼 일단 정점쉐이더는 잘 마무리가 된 듯 하네요. 혹시라도 컴파일 에러가 보이면 뭔가 잘못했단 이야기니 한번 코드를 다시 검토해보세요.

팁: 쉐이더 컴파일에 실패한 경우
오타나 문법적 오류 때문에 쉐이더 컴파일에 실패한 경우, 미리 보기 창에 컴파일에 실패했다는 오류메시지가 등장할 것입니다. 이 때, 정확히 어떤 코드에 문제가 있는지 알고 싶으시다면 렌더몽키의 젤 아래쪽에 위치한 출력(output)창을 보세요. 자세한 오류메시지와 더불어 문제가 있는 코드의 행과 열 번호까지도 보여줍니다.

댓글 27개:

  1. 감사합니다..

    답글삭제
  2. 투영공간 설명에, 눈 모양 옆 단면도 그림이 있으면 더욱 좋겠어요! ㅎㅎㅎ

    답글삭제
  3. 좋은 강좌 공개 감사합니다.

    답글삭제
  4. 잘 봤습니다~

    5.이제 위의 과정을 반복하여 뷰 행렬과 투영행렬을 만듭니다. 변수 명을 각각 gViewMatrix와 gProjectionMatrix를 만들고 View와 Project 변수시맨틱을 대입합니다.

    에서 오타가 있네요 Project -> Projection

    그리고 렌더 몽키를 처음 써봐서 어쩌다 이런건지 모르겠는데 월드,뷰,프로젝션 변수 설정하고 컴파일 다시 했을때 변수에 현재 카메라 세팅이 제데로 안들어 가서 Preview에 검은 화면만 뜰 때가 있는데 껏다 켜니 되는군요;;; 저 처럼 처음 하시는 분께 도움이 되길 바람니다.

    답글삭제
  5. 오타 고쳤습니다! 감사합니다~ ^^

    답글삭제
  6. 안녕하세요 강좌보고 열심히 공부중인 그래픽리소스 개발자입니다.

    열심히 따라하며 공부하고있습니다... 일단 이런강좌 올려주셔서 넘넘 감사합니다.

    질문이 있는데요
    Output.mPosition = mul (Input.mPosition, gWorldMatrix);
    Output.mPosition = mul (Output.mPosition, gViewMatrix);
    Output.mPosition = mul (Output.mPosition, gProjectionMatrix);

    world부분은 input을 받는건 알겠는다..
    나머지view와 projection은 왜 아웃풋을 에 연산을 하는지 잘이해가 안되서 그러는데 설명좀 해주실수 있으까요?^^;;
    제가 플그래밍은 기초공부하고있는 수준이라..ㅎㅎㅎ;; OTL

    답글삭제
  7. input.mPosition을 gWorldMatrix와 곱해서 그 결과를 "Output.mPosition"에 저장해 놔서 그래요. 그 결과를 가져다 다시 View와 Projection하고 곱해줘야하니까요.. 위의 코드를 한줄로 쓰면 이것과 같죠.

    Output.mPosition = mul( mul( mul (Input.mPosition, gWorldMatrix), gViewMatrix), gProjectionMatrix);

    답글삭제
  8. 아...빠른 답변 감사합니다 (_ _)꾸벅~

    답글삭제
  9. 개념정리하고있습니다. 감사해요. ^^
    따라하고있는데
    저는 계속 검은 화면만 나오네요 Output창에는
    Compiling vertex shader API(D3D) /../Default_DirectX_Effect/Pass 0/Vertex Shader/ ...success
    라고만 뜨구요.
    렌더몽키 문제일까요?

    답글삭제
    답글
    1. 자문자답이 되었네요. 오늘 다시 해보니 Add Variable > Matrix > Predefined 가 있고 거기안에 있는 월드, 뷰, 프로젝션을 만드니 잘 실행되네요. 렌더몽키 버전업되면서 생긴 문제인듯도하구요. 예전버전을 써봣으면 확실히 알텐데 ㅎㅎ 암튼 빨간구를 보니 반갑네요 ㅎ

      삭제
    2. 아~ 완전 뻘짓을 했네요. 시맨틱부분이 중요하다고 그렇게 써놓으셧는데 그걸 그냥 넘겨서 이런사태가... 죄송~ㅠㅠ
      저같은 실수 하시는 분이 있을까봐 부끄럽지만 댓글을 지우진 않겠습니다 엉엉

      삭제
    3. 뭘요.. 사람은 누구나 다 놓치고 봐요.. 놓치고 보는 분야가 다 다를 뿐이지. 당연한 거니 너무 자책하시지 마시길.. 차라리 동환님이 이렇게 답글을 남겨주셔서 나중에 글 보실 분들이 똑같은 실수 저지르면 어떻게 고칠지 바로 아실수 있으니 좋죠.

      제가 오늘 하루 좀 아파서 답글 빨리 못단게 오히려 죄송스러울뿐 ^^

      삭제
  10. 안녕하세요. 포프님. 쉐이더 관련해서 좋은 강좌 써주셔서 감사드립니다.

    프로그래밍에 대한 이해가 없는 상태에서 RenderMonkey 설치해서 따라해 보고 있습니다.
    우선 첫번째 막히는 부분이 있는데요.
    semantic 이란 용어가 나오는데 정확하게 프로그래밍에서 바라보는 시맨틱이란 용어가 무엇을 의미하는지 모르겠습니다. 사전적인 의미와 잘 연결되지 않네요.

    알려주시면 감사드리겠습니다.

    답글삭제
    답글
    1. 안녕하세요. 사실 셰이더 쪽에서 쓰는 semantic이란 용어는 프로그래밍에서 쓰는 semantic이란 용어의 뜻과는 많이 다릅니다. 괄호안에도 설명해두었듯이 그냥 "tag"정도로 이해해주시는게 좋습니다. 저희가 정점버퍼에 넣는 데이터를 GPU 하드웨어에(또는 셰이더 안에서 쓰는 구조체에) 매핑하기 위해 쓰는 태그입니다. "이 데이터는 정점 위치니 POSITION태그를 쓰겠다!" 정도로요.

      셰이더에서 말하는 시맨틱은 말그대로 프로그래밍쪽에서는 별 의미가 없다고 보이네요~

      삭제
  11. 오래 지났는데, 글을 읽으실 수 있으려나 모르겠네요. 3D에 대해서는 잘 모릅니다. 궁금해서 여쭤봐요. 맥스, 마야, 시포디 등 다양한 3D 프로그램들이 있잖아요? 그런데, 한 프로그램에서 제작한 어떤 걸 export해서 다른 프로그램에서 import하면 좌표가 많이 틀어지는 경우가 있다고 하더라고요, 혹시, 그게 각 프로그램별로 최종 랜더링(?)을 하기 위해 사용하는 공간이 달라서 그런 건가요? 아니면, 단순히 export나 import할 때 프로그램 상에서 설정을 잘못했기 때문일까요? 특정 솔루션에 대한 질문이라기 보다는, 그냥 이유가 궁금해서 여쭤봅니다^^;;

    답글삭제
    답글
    1. 좌표계 문제죠... 맥스는 +z가 위를 가르키는 반면 마야는 +y가 위를 가르키니까요.. 그리고 맥스는 왼손좌표계고 마야는 오른손좌표계인가? (이건 기억이) 그럴거에요.. 즉 화면의 안이나 밖을 가리키는 좌표축(맥스의 y하고 마야의 z가 가리키는 방향)이 반대.

      중간에 행렬한 번 곱해주는걸로 물론 다 고칠수 있긴 합니다 -_-; export할떄 exporter에도 그런 옵션이 있는 경우도 있구요...

      최종렌더링 공간하고 상관없고 모델링 하는 공간부터가 문제에요 .사실 뭐가 맞다 틀리다 할 수 없는거지만.. 맥스 혼자 유독 튀고 있다는 게 문제긴 하죠 -_-

      삭제
  12. 왜 제가 구축한 DX에는 float4 랑 float4x4가 언더라인이 생길까요..
    식별자가 정의되지않았다는데 뭐 따로 정의해야하는 부분이있을까요?

    답글삭제
    답글
    1. 신택스 하이라이팅 말인가요? 전 IDE에서 안하고 그냥 notepad++따위에서 세이더 코드 짜서 별로 신경을 안써봤어요..

      VS2013부터는 신택스 하이라이팅이 잘된단 이야기도 들은거 같고요.. 버전 몇쓰세요?

      삭제
  13. 안녕하세요~ 궁금한 사항이 있으서 질문 드립니다.
    투영좌표를 구하는 과정에서 뷰스페이스의 z 값으로 최종적으로 나누어 주어야 하잖아요?
    그럼 투영행렬을 곱하고 나서, 뷰스페이스의 z 로 나누어 주는 과정은 어느부분에서 이루어 지는 건가요?
    mul 함수 내부에서 진행되는 건가요?

    답글삭제
    답글
    1. 네. 투영행렬을 곱할 때 그렇게 되요. MSDN가보시면 투영행렬 각요소의 값이 나와있는데 그거와 벡터를 곱해보시면 알거에요.

      삭제
    2. 작성자가 댓글을 삭제했습니다.

      삭제
    3. 답변 감사합니다!!
      한가지 더 질문 드려도 될까요?

      MSDN 에 나와있는 투영행렬 요소를 곱하게 되면 벡터에 각각 뷰스페이스 z 값이
      곱해진 상태지 않나요?

      깊이버퍼 그림자 등등 투영행렬 좌표를 알아내야 할때 보면, mul 함수로 최종 투영행렬까지
      곱한 후에 직접 w 로 나누어서 0~1 사이 투영좌표계 변환을 하는 거 같던데,

      막상 정점쉐이더에서는 굳이 이 작업을 해주지 않고 mul 로 투영행렬만 곱하는걸로 봐서
      도대체 이 작업이 어디서 이루어지는지 헷갈리네요...

      삭제
    4. 아 w로 나누는건 하드웨어 에서 해줍니다.. rasterizer가 중간에서 해줘요. 그러는 이유는... 이렇게 생각하면 편해요....

      정점쉐이더에서 z를 미리 나누면 부동소수점 오차가 너무 나기에 z와 w로 넣어서 하드웨어에 전해주면 하드웨어가 픽셀별로 보간을 한뒤에 나중에 나눠주는 겁니다.

      삭제
    5. 이것때문에 정말 몇일 새 고민이었는데 속시원하게 대답해주셔서 감사합니다!!

      삭제
  14. 와.. 진짜 대박이다 ㅠㅠㅠㅠㅠㅠㅠ
    정말 감사합니다 ㅠㅠ

    답글삭제
  15. 안녕하세요! 강좌 보다가 월드공간에 대해 의문이 생겨 질문드립니다.
    우선 월드공간의 개념이 안잡힙니다.
    뷰공간은 카메라가 보는 화면이고 그 화면을 투영하여 투영공간에 표현하는 것에 대한 개념은 알겠습니다. 그런데 어차피 우리가 눈에 보일때는 카메라에 잡힌 뷰공간만 보게 되는것인데 카메라에 안잡히는 월드공간은 왜 필요한 것 인가요?
    제가 생각 할 때에는 위의 예시에서 볼때 월드공간도 어떤 물체를 기준으로 잡고 거기에서 해당물체에 떨어진 거리를 표현하는 것 으로 똑같은것 같은데 제가 한참 착각하고 있는건가요?? ㅠㅠ

    답글삭제
  16. float4가 x.y.z.w라고 하셨는데 w는 뭔가요?
    완전 초보라서질문 드려봤습니다.

    답글삭제