주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



지구는 태양 주위를 약 365 일 주기로 원운동을 한다. 지구에서 태양까지의 거리는 149.6 X 106 km 이다. 지구가 공전하는 가속도 크기를 구하고, 그로부터 태양의 질량을 계산하라.


어떤 원에서 각 θ 일때의 위치 함수 P 를 생각해 봅시다. 그러면 위치함수는 x 와 y 성분으로 이루어진 벡터를 반환합니다.


그림 1.


그림 1 에서 P 함수의 값은 삼각함수를 통해 구해졌습니다.


그런데 우리가 주어진 문제를 풀기 위해서는 θ 를 시간에 대한 함수로 나타낼 필요가 있습니다. 왜냐하면 지구가 태양 주위를 회전하는 데 걸리는 시간을 의미하는 주기를 고려해야 하기 때문입니다.


그림 1 의 원점이 태양의 위치라 가정하면, 지구는 그것을 중심으로 원운동을 합니다. 문제에서 365 일이라고 지정된 주기를 T 라 합시다. 단위시간( 여기에서는 1일 )에 회전하는 각도를 각속도라고 하고 그것을 ω 라 표현하면, 식 1 이 성립합니다. T 일 동안 한 바퀴를 돌면 1 일 동안 ω 를 돌게 된다는 것이죠.


식 1.


자, 이렇게 되면 현재 각 θ 는 시간 t 에 대해 다음과 같이 표현될 수 있습니다.


식 2.


만약 t 가 T 라면 결과는 2π 이고 한바퀴의 각도를 의미하게 됩니다.


이제 위치함수 P 는 다음과 같이 표현될 수 있습니다.


식 3.


위치 함수를 적분하면 속도함수가 나옵니다. 문제는 이 위치함수가 합성함수라는 점입니다. 여기에서 합성함수 미분법을 증명하는 것은 주제에서 벗어나기 때문에 "합성함수 미분" 이나 "미분 연쇄법칙" 등을 키워드로 해서 검색해 보시기 바랍니다.


어쨌든 P 함수를 성분별로 미분하면 속도 함수가 나옵니다.


식 4.


식 5.


속도 함수를 미분하면 가속도 함수가 나옵니다.


식 6.


식 7.


지금까지의 식들을 정리해 보도록 하겠습니다.


식 8.


속도 벡터와 가속도 벡터의 크기는 다음과 같습니다.


식 9.


식 10.


이제 식 10 에 주어진 값들을 대입해 보겠습니다.


식 11.


해답은 5.939 X 10-3 입니다. 반올림을 한 거니 답이 맞다고 가정합니다.


여기에서 구한 가속도는 태양과 지구 사이에서 작용하는 원심력인 중력과 같습니다.


중력은 만유인력 상수 및 두 물체의 질량에 비례하며, 거리에 반비례합니다. 그러므로 다음과 같은 식을 세울 수 있습니다.


식 12.


해답은 1.992 X 1030 인데 첫번째 문제에서 반올림하지 않은 것의 영향이라고 보기에는 값이 너무 차이가 납니다. 


5.939 X 10-3 를 넣어 봤는데 1.9756 X 1030 이 나왔습니다.


해답이 오답인 것으로 보입니다.

주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



높이가 H 인 나무 위에 있는 원숭이를 향해 사냥꾼이 총을 발사했다. 총알의 빠르기는 v0 로 일정하고, 사냥꾼에서부터 나무까지의 수평거리는 R 이다.



(a) 총알이 나무 위치에 도달하는데 설린 시간은 얼마인가?


(b) 그 시각 총알의 높이는 얼마인가?


(c) 총알이 발사되는 순간 원숭이가 떨어지기 시작했다면, 그 시각 원숭이가 있는 높이는 얼마인가?


처음에는 답이 틀렸다고 생각했는데, 물잘알 햄이 풀어 보더니 답 맞다고 합니다. 삼각함수의 정의를 이용해서 풀면 맞더군요. 아직 저는 응용력이 많이 부족한 것 같습니다.


중력가속도를 g 라 합니다.


먼저 H 와 R 로 이루어진 직각삼각형의 빗변을 L 이라 하겠습니다.


식 1.


그러면 이제 사인과 코사인을 구할 수 있습니다.


식 2.


총알을 발사하면 총알 속도의 수직성분은 중력에 의해서 감속됩니다. 수평성분은 그대로입니다.  그러므로 시간 t 일 때의 수평성분과 수직성분은 다음과 같이 나타낼 수 있습니다.


식 3.


시간 t 일 때 위치 함수는 식 3 을 부정적분함으로써 구할 수 있습니다.


식 4.


식 5.


식 4 와 식 5 를 각각 정적분하게 되면 [0, t] 까지의 이동거리가 나옵니다.


식 6.


식 7.


( a )


총알이 나무에 도달하는 시간을 th 라 하겠습니다. 이 때까지의 수평성분 이동거리는 R 이 됩니다. 식 6 을 사용해 이동거리를 구하면 도달하는 시간을 구할 수 있습니다.


식 8.


( b )


총알의 높이는 식 5 를 사용해서 구할 수 있습니다.


식 9.


( c )


원숭이는 자유낙하를 합니다. 시간 t 에 원숭이의 속도는 다음과 같습니다.


식 10.


그러면 식 10 을 부정적분해서 원숭이의 위치 함수를 구할 수 있습니다.


식 11.


식 11 에  th 를 대입하면 원숭이의 위치가 나옵니다.


식 12.

주의 : 초심자 튜토리얼은 아닙니다. 그러므로, 실제 API 호출 용례를 알고자 한다면, 샘플이나 튜토리얼을 찾아서 확인해 보세요.

주의 : 완전히 이해하고 작성한 글이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 확인하세요.



일단 LunarG 나 Khronos group 의 문서들을 보면 셰이더 언어로 GLSL 을 리플렉션 라이브러리로 spirv-cross 를 소개하는 경우가 많습니다. 


그런데 HLSL 을 SPIR-V 로 변환하는 것과 관련해 설명하는 문서들에서는 리플렉션을 위해 spirv-reflect 를 소개하는 경우가 많습니다. 보통 이런 문서는 구글의 "Lei Zhang" 이라는 사람이 주로 발표하는데요, 이 분이 dxc 와 spirv-relfect 구현에 참여를 한 것으로 보입니다. 이 분은 github 에서 "antiagainst" 라는 계정을 사용하고 있습니다. 구글에서는 dxc 를 통해서 spirv-reflect 를 사용하도록 권장하는 모양인 것 같습니다. MS 도 아니고 구글이 그런 작업을 한다는 사실이 참 아이러니합니다.


spirv-reflect 는 매우 가볍습니다. 헤더 하나에 소스 하나입니다. [ SPIRV-Reflect ] 에 올라와 있으니 자유롭게 받으실 수 있습니다.


사용방법은 매우 간단한데요, 위의 링크를 참고하시면 쉽게 따라할 수 있습니다. 


단지 문제가 하나 있다면 해당 페이지의 코드 조각 중에서 input_vars 가 SpvReflectInterfaceVariable* 타입이 아니라 SpvReflectInterfaceVariable** 타입이어야 한다는 것입니다. 무작정 따라 하시면 나중에 크래시납니다. 게다가 input_vars 에 대한 메모리 해제도 안 하고 있으니 주의하셔야 합니다.


예제는 spvReflectEnumerateInputVariables() 함수만 소개하고 있는데요, 여러 종류의 열거 함수들이 있습니다. 자신의 필요에 맞게 함수를 호출해서 사용하면 됩니다.



위의 함수와 동일한 기능을 하는 ShaderModule 클래스의 래퍼 메서드들도 존재합니다. 어떤 스타일을 선호하느냐는 사용자가 결정하면 됩니다.



그런데 이건 좀 사용하기에 불편한 점이 있습니다. 예를 들면 아래와 같은 HLSL 코드를 살펴 봅시다.



여기에 대해 spvReflectEnumerateInputVariables() 를 호출하면 main() 함수의 인자인 PSInput 의 내용을 출력해 줍니다. 하지만 이름이 맹글링되어 있고 제대로 유지되지 않는 것을 알 수 있습니다.  Color, Alpha, Scaling 등의 정보가 시맨틱과 비슷하면서도 다릅니다.


Name = in.var.NORMAL

Name = in.var.COLOR_1

Name = in.var.OPACITY_512

Name = in.var.SCALE_987654321

Name = in.var.TEXCOORD0

Name = in.var.TEXCOORD1

Name = in.var.TEXCOORD2


그런데 dxc 의 옵션으로 "-fspv-reflect" 를 지정해서 컴파일했다면 정확한 이름/시맨틱 쌍을 얻을 수 있습니다.


Name = in.var.NORMAL

Semantic = NORMAL

Name = in.var.COLOR_1

Semantic = COLOR_00001

Name = in.var.OPACITY_512

Semantic = OPACITY_512

Name = in.var.SCALE_987654321

Semantic = SCALE_987654321

Name = in.var.TEXCOORD0

Semantic = TEXCOORD0

Name = in.var.TEXCOORD1

Semantic = TEXCOORD1

Name = in.var.TEXCOORD2

Semantic = TEXCOORD2


시맨틱을 얻게 되면 다음과 같은 식으로 시맨틱을 사용해서 리플렉션 정보를 획득하는 것도 가능합니다.



사용자들은 맹글링된 이름이 아니라 원래 이름으로 검색하고 싶겠지만, 안타깝게도 그런 방법은 존재하지 않습니다. dxc 에 이런 저런 옵션을 다 넣어서 확인해 봤지만 정보가 안 나오더군요. 만약 필요하다면 dxc 를 커스터마이징하는 수밖에 없는 것 같습니다.


그래도 확실하게 정보가 유지되는 것은 시맨틱이기 때문에 시맨틱을 사용해서 리플렉션을 하는 것이 현명해 보입니다.

주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

주의 : 특별한 경우가 아니라면, 귀찮아서 문제는 안 옮깁니다.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



  • 탄환 최대 도달거리 : 1 km

  • 중력 가속도 : 10 m/s2

  • 대포 사정거리 : 100 km


총탄 초기속도와 포탄 초기 속도를 구하는 문제입니다. 최대 도달거리라는 조건이 주어졌으므로, 45 도 각도로 포물선 운동하는 물체여야 합니다.


처음에는 H 에서 수평으로 던져졌다고 생각하고 문제를 풀었는데, 아주 괴랄한 식이 나오더군요. 그런데 물잘알 친구 햄이 "최대" 라는 전제조건을 깔았기 때문에 45 도로 던져져야 한다고 지적해 줘서 다시 문제를 풀었습니다.


사실 왜 45 도에서 최대 거리가 나오는지 증명부터 해야 하지만 귀찮으니 그냥 넘어 가도록 하겠습니다. 나중에 시간나면 추가하겠습니다.


각도 Θ 로 던져진 공이 있다고 했을 때 그것의 초기속도 v0 는 중력방향에 대한 수직성분 및 수평성분 속도로 나뉩니다.


식 1.


그리고 시간 t 가 지났을 때 속도는 다음과 같습니다.


식 2.


만약 시간 t 에서 수직성분 속도가 0 이 된다면, t 일 때 최대 높이가 됩니다.


식 3.


포물선 운동은 최대 높이에서 중력축에 대해서 좌우대칭인 모양을 보여 주기 때문에 땅에 떨어졌을 때의 시간은 2t 가 됩니다. 그러므로 시간 2t 까지의 이동거리를 구하면 됩니다.


수평성분 이동거리는 다음과 같습니다.


식 4.

식 4 에 의해 총탄의 초기속도는 다음과 같습니다.


식 5.



식 4 에 의해 포탄의 초기속도는 다음과 같습니다.


식 6.

주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



다음은 포물선 운동에 관한 문제다. 각각 물음에 답하라.



( a ) 공중으로 던져진 공은 y = ax2 + bx + c 의 형태의 포물선 모양을 그리며 운동하는 것을 보여라.


( b ) 경사면 각도가 30 도인 언덕방향으로 v0 의 빠르기로 수평과 θ 의 각도로 던진 공이 있다. 공을 던진 점으로부터 수평거리를 x 라 할 때 이 경우 언덕 경사면 높이는 y = (1/2)x 로 나타낼 수 있다. 공이 도달한 언덕 높이는 얼마인가?



( a )


어떤 각도로 공중으로 던져진 공은 중력가속도 g 를 받습니다. 그래서 항상 속도와 위치가 변화합니다.


시간 t 에서 y 성분 속도 함수는 다음과 같습니다. 초기 속도가 중력가속도에 의해서 상쇠되면서 올라갔다가 떨어지는거죠.


식 1.


식 1 을 부정적분해서 위치함수( y 값 )를 구할 수 있습니다.


식 2.


이제 x 의 관점에서도 식 1 과 식 2 처럼 풀어 보도록 하겠습니다. 차이가 있다면 x 축 성분은 시간의 변화에도 영향을 받지 않는 상수라는 것입니다.


식 3.


이제 이를 x 와 y 의 관계로 나타내야 합니다.


식 3 에서 t 값을 구할 수 있습니다.


식 4.


t 값을 식 2 에 대입합니다.


식 5.


그러므로 던져진 공은 계수가 다음과 같은 포물선 운동을 한다고 할 수 있습니다.


식 6.


삼각함수를 사용하면 초기 속도의 수평성분과 수직성분을 구할 수 있습니다.


식 7.


우리는 최종적으로 식을 다음과 같이 정리할 수 있습니다.


식 8.

( b )


경사면과 공이 만났다는 이야기는 포물선과 경사면을 표현하는 그래프의 y 값이 동일하다는 의미입니다( 두 그래프가 만남 ). 그러므로 다음과 같은 식을 세울 수 있습니다.


식 7.


이 x 값 중에 첫 번째 해는 시작위치를 의미하므로 두 번째 해를 사용하면 됩니다. 두 번째 해를 y = (1/2)x 에다가 대입하도록 하겠습니다.


식 8.


해답은 다음과 같습니다.


식 11.


아무래도 해답이 오답인 것 같습니다.

주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

주의 : 특별한 경우가 아니라면, 귀찮아서 문제는 안 옮깁니다.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



지표면으로부터 높이 H 인 곳에서, 공을 v0 의 빠르기로 수평과 θ 의 각도로 위로 향해 던졌다. 공이 지표면에 닿기까지 공이 수평방향으로 이동한 거리는 얼마인가?


  • 중력 가속도 : g

  • 최고점에 도달하는 시간 : th

  • 최고점에서 땅에 떨어지는 시간 : tl


그림 1.


일단 이 문제를 해결하기 위해서는 공이 땅에 떨어지는 시간을 구해야 합니다.


던진 공의 초기 속도 분해


공은 θ 의 각도로 던져지고 있으므로 이것을 수평성분( x )과 수직성분( y )으로 나누면 다음과 같습니다.


식 1.


공의 수직 성분 속도


공에는 지속적으로 중력 가속도가 가해지므로 시간 t 에서의 속도는 다음과 같습니다.


식 2.


최고점에 도달하는 시간


그런데 최고점에 도달하는 경우는 속도의 크기가 0 이 되는 경우입니다. 그러므로 최고점에 도달하는 시간 th 는 다음과 같이 구할 수 있습니다.


식 3.

최고점까지의 이동 거리



먼저 식 2 의 속도함수를 부정적분해 이동거리함수의 원함수를 구합니다.


식 4.


 [ 0, th ] 의 범위로 정적분하면 최고점까지의 이동거리가 됩니다.


식 5.


최고점의 높이


그런데 이것은 시간 th 까지의 이동거리이므로 최고점에서의 높이는 공을 던진 초기 높이인 H 에서 이동거리를 더한 값이 되어야 합니다. 여기에서 P 는 위치 함수입니다.


식 6.


자유낙하 운동 속도


이제 최고점의 높이를 구했으니, 시간 th 부터는 자유낙하 운동을 하게 됩니다.


자유낙하 운동을 하고 있을 때 속도 함수는 다음과 같습니다.


식 7.


자유낙하 운동 이동 거리


자유낙하 속도 함수를 부정적분해서 이동거리 함수의 원함수를 구합니다.


식 8.


자유낙하 시간


자유낙하 이동 거리는 최고점의 높이와 같아야 합니다. 그러므로 다음이 성립해야 합니다.


식 9.


그런데 t 는 자유낙하 시간만을 의미하기 때문에 실제 tl 은 다음과 같습니다.


식 10.


수평 성분 이동거리


문제에서는 공기저항에 대해서 언급하지 않고 있기 때문에, 관성에 의해서 수평이동 운동은 등속도 운동이 됩니다. 그러므로 다음이 성립합니다.


식 11.


식 11 에 공이 바닥에 도달하기까지의 시간  tl  을 대입하면 다음과 같습니다.


식 12.


우리는 식 1 에서 초기속도를 계산했으므로, 이를 식 12 에 대입해서 최종 결과를 구할 수 있습니다.


식 13.


그런데 해답에서는 루트 안의 뒤쪽 항이 4gH 군요. 몇 번 검증해 봤는데, 해답이 오답인 것 같습니다.

주의 : 초심자 튜토리얼은 아닙니다. 그러므로, 실제 API 호출 용례를 알고자 한다면, 샘플이나 튜토리얼을 찾아서 확인해 보세요.

주의 : 완전히 이해하고 작성한 글이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 확인하세요.



HLSL to SPIR-V Feature Mapping Manual ] 에 커맨드라인 옵션들에 대한 설명들이 나와 있기는 하지만, 정확하게 어떤 것을 이야기하는지 이해할 수 없는 것들도 있습니다.


그래서 소스 코드를 분석해서 정확하게 각 옵션들의 의미를 파악해 보기로 했습니다. 아래 블록은 "dxc -help" 를 실행했을 때 나오는 설명들입니다. SPIR-V 전용 옵션들은 여기에서는 배제했습니다. 나중에 다른 챕터에서 다룰 계획입니다.


Common Options:

  -help              Display available options

  -nologo            Suppress copyright message

  -Qunused-arguments Don't emit warning for unused driver arguments


Compilation Options:

  -all_resources_bound    Enables agressive flattening

  -auto-binding-space <value>

                          Set auto binding space - enables auto resource binding in libraries

  -Cc                     Output color coded assembly listings

  -default-linkage <value>

                          Set default linkage for non-shader functions when compiling or linking to a library target (internal, external)

  -denorm <value>         select denormal value options (any, preserve, ftz). any is the default.

  -D <value>              Define macro

  -enable-16bit-types     Enable 16bit types and disable min precision types. Available in HLSL 2018 and shader model 6.2

  -export-shaders-only    Only export shaders when compiling a library

  -exports <value>        Specify exports when compiling a library: export1[[,export1_clone,...]=internal_name][;...]

  -E <value>              Entry point name

  -Fc <file>              Output assembly code listing file

  -Fd <file>              Write debug information to the given file, or automatically named file in directory when ending in '\'

  -Fe <file>              Output warnings and errors to the given file

  -Fh <file>              Output header file containing object code

  -flegacy-macro-expansion

                          Expand the operands before performing token-pasting operation (fxc behavior)

  -flegacy-resource-reservation

                          Reserve unused explicit register assignments for compatibility with shader model 5.0 and below

  -force_rootsig_ver <profile>

                          force root signature version (rootsig_1_1 if omitted)

  -Fo <file>              Output object file

  -Gec                    Enable backward compatibility mode

  -Ges                    Enable strict mode

  -Gfa                    Avoid flow control constructs

  -Gfp                    Prefer flow control constructs

  -Gis                    Force IEEE strictness

  -HV <value>             HLSL version (2016, 2017, 2018). Default is 2018

  -H                      Show header includes and nesting depth

  -ignore-line-directives Ignore line directives

  -I <value>              Add directory to include search path

  -Lx                     Output hexadecimal literals

  -Ni                     Output instruction numbers in assembly listings

  -no-warnings            Suppress warnings

  -not_use_legacy_cbuf_load

                          Do not use legacy cbuffer load

  -No                     Output instruction byte offsets in assembly listings

  -Odump                  Print the optimizer commands.

  -Od                     Disable optimizations

  -pack_optimized         Optimize signature packing assuming identical signature provided for each connecting stage

  -pack_prefix_stable     (default) Pack signatures preserving prefix-stable property - appended elements will not disturb placement of prior elements

  -recompile              recompile from DXIL container with Debug Info or Debug Info bitcode file

  -res_may_alias          Assume that UAVs/SRVs may alias

  -rootsig-define <value> Read root signature from a #define

  -T <profile>            Set target profile.

        <profile>: ps_6_0, ps_6_1, ps_6_2, ps_6_3, ps_6_4, ps_6_5,

                 vs_6_0, vs_6_1, vs_6_2, vs_6_3, vs_6_4, vs_6_5,

                 cs_6_0, cs_6_1, cs_6_2, cs_6_3, cs_6_4, cs_6_5,

                 gs_6_0, gs_6_1, gs_6_2, gs_6_3, gs_6_4, gs_6_5,

                 ds_6_0, ds_6_1, ds_6_2, ds_6_3, ds_6_4, ds_6_5,

                 hs_6_0, hs_6_1, hs_6_2, hs_6_3, hs_6_4, hs_6_5,

                 lib_6_3, lib_6_4, lib_6_5, ms_6_5, as_6_5

  -Vd                     Disable validation

  -Vi                     Display details about the include process.

  -Vn <name>              Use <name> as variable name in header file

  -WX                     Treat warnings as errors

  -Zi                     Enable debug information

  -Zpc                    Pack matrices in column-major order

  -Zpr                    Pack matrices in row-major order

  -Zsb                    Build debug name considering only output binary

  -Zss                    Build debug name considering source information


Optimization Options:

  -O0 Optimization Level 0

  -O1 Optimization Level 1

  -O2 Optimization Level 2

  -O3 Optimization Level 3 (Default)


SPIR-V CodeGen Options:

  -fspv-debug=<value>     Specify whitelist of debug info category (file -> source -> line, tool)

  -fspv-extension=<value> Specify SPIR-V extension permitted to use

  -fspv-reflect           Emit additional SPIR-V instructions to aid reflection

  -fspv-target-env=<value>

                          Specify the target environment: vulkan1.0 (default) or vulkan1.1

  -fvk-b-shift <shift> <space>

                          Specify Vulkan binding number shift for b-type register

  -fvk-bind-globals <binding> <set>

                          Specify Vulkan binding number and set number for the $Globals cbuffer

  -fvk-bind-register <type-number> <space> <binding> <set>

                          Specify Vulkan descriptor set and binding for a specific register

  -fvk-invert-y           Negate SV_Position.y before writing to stage output in VS/DS/GS to accommodate Vulkan's coordinate system

  -fvk-s-shift <shift> <space>

                          Specify Vulkan binding number shift for s-type register

  -fvk-t-shift <shift> <space>

                          Specify Vulkan binding number shift for t-type register

  -fvk-u-shift <shift> <space>

                          Specify Vulkan binding number shift for u-type register

  -fvk-use-dx-layout      Use DirectX memory layout for Vulkan resources

  -fvk-use-dx-position-w  Reciprocate SV_Position.w after reading from stage input in PS to accommodate the difference between Vulkan and DirectX

  -fvk-use-gl-layout      Use strict OpenGL std140/std430 memory layout for Vulkan resources

  -fvk-use-scalar-layout  Use scalar memory layout for Vulkan resources

  -Oconfig=<value>        Specify a comma-separated list of SPIRV-Tools passes to customize optimization configuration (see http://khr.io/hlsl2spirv#optimization)

  -spirv                  Generate SPIR-V code


Utility Options:

  -dumpbin              Load a binary file rather than compiling

  -extractrootsignature Extract root signature from shader bytecode (must be used with /Fo <file>)

  -getprivate <file>    Save private data from shader blob

  -P <value>            Preprocess to file (must be used alone)

  -Qembed_debug         Embed PDB in shader container (must be used with /Zi)

  -Qstrip_debug         Strip debug information from 4_0+ shader bytecode  (must be used with /Fo <file>)

  -Qstrip_priv          Strip private data from shader bytecode  (must be used with /Fo <file>)

  -Qstrip_reflect       Strip reflection data from shader bytecode  (must be used with /Fo <file>)

  -Qstrip_rootsignature Strip root signature data from shader bytecode  (must be used with /Fo <file>)

  -setprivate <file>    Private data to add to compiled shader blob

  -setrootsignature <file>

                        Attach root signature to shader bytecode

  -verifyrootsignature <file>

                        Verify shader bytecode with root signature


이 옵션들을 모두 살펴 볼 것은 아니구요, 그냥 -help 만 봐도 쉽게 이해할 수 있는 것들은 배제하겠습니다. 그리고 별로 빈도가 높아 보이지 않는 것들도 배제했습니다. 댓글로 특정 옵션에 대한 정확한 동작에 대해서 문의하신다면 내용을 추가하도록 하겠습니다.


기본 형식


다음과 같이 옵션 다음에 컴파일할 소스 파일 경로를 넣습니다.


dxc [ options... ] $(SourceFilePath)


$(SourceFilePath) 는 커맨드 실행 디렉토리에 대한 상대 경로일 수도 있고 절대 경로일 수도 있습니다.


옵션을 위한 플래그들은 '-' 접두어로 시작하며, 값을 받는 옵션이라면 "-O" 를 제외하고는 모두 한 칸을 띄고 값을 입력합니다.


-spriv


가장 먼저 지정해야 할 값이 없는 옵션입니다. SPIR-V 파일을 생성하라는 의미입니다. 벌칸을 사용한다는 가정하에서지만, 이것을 빼먹어서는 안 됩니다.


dxc -spirv


-T


셰이더 프로우파일 문자열입니다.


dxc -T $(shader_name)_$(major_version)_$(minor_version)


$(shader_name) 은 각 셰이더 타입의 머리글자 혹은 약자입니다.


  • vs : Vertex Shader.

  • hs : Hull Shader.

  • ds : Domain Shader.

  • gs : Geometry Shader.

  • ps : Pixel Shader.

  • cs : Compute Shader.

  • lib : shader LIBrary. 한 번도 써 본 적이 없어서 모르겠는데, [ Using shader linking ] 에서 정보를 얻을 수 있습니다.


문제는 셰이더의 버전인데요, 이것은 메이저와 마이너로 나뉩니다. 이것을 임의로 지정하는 것은 아니구요, [ Shader Models vs Shader Profiles ] 에 가면 셰이더 모델에서 이용가능한 셰이더 프로우파일들의 목록을 확인하실 수 있습니다.


-E


셰이더 프로우파일에 대한 진입 함수에 대한 문자열입니다. 


dxc -E mainVS


그냥 함수 이름을 기입하면 됩니다. 만약 이를 지정하지 않으면 기본적으로는 "main" 이 사용됩니다.


-D


디파인 매크로를 설정하는 문자열입니다.


만약 여러 개의 매크로를 설정해야 한다면, 여러 개의 -D 플래그를 사용해야 합니다. 예를 들면 아래 블록과 같이 할 수 있습니다.


dxc -D USE_POSITION -D USE_NORMAL=1


다들 아실거라 생각하지만 부연하자면, '=' 을 사용하지 않으면 "#ifdef" 를 사용해서 조건을 검사하고, 사용하면 "#if" 를 사용해서 조건을 검사합니다.


-I


HLSL 파일을 작성하다가 보면 인클루딩을 해야 하는 경우가 있습니다. 인클루딩해야 할 파일이 존재하는 폴더 경로에 대한 문자열입니다.



공용 HLSL 파일을 위한 폴더를 나누거나 종류별로 HLSL 파일을 나눠서 관리하다가 보면, 인클루드해야 하는 파일이 다른 디렉토리에 존재할 수 있습니다. 만약 같은 폴더에 대상 파일이 존재한다면 아무런 문제가 없지만, 그렇지 않다면 아래처럼 경로를 지정해야 합니다.


dxc -I C:\OtherFolder1 -I C:\OtherFolder2 -I ..\


여러 개의 폴더를 지정하려면 여러 개의 -I 플래그를 사용합니다. 경로는 절대 경로여도 되고 커맨드를 실행한 폴더에 대한 상대 경로여도 됩니다.


-O


최적화 수준을 지정하는 문자열입니다. 


이것은 특이하게 한칸 띄고 값을 지정하는 것이 아니라 바로 붙여서 지정합니다( 예를 들어 -O1 ).


dxc -O{0|1|2|3}


0 ~ 3 까지 지정할 수 있고 숫자가 낮을 수록 최적화를 덜 한다는 이야기입니다. 값의 크기는 코드를 생성하는 속도와 반비례하며 코드 실행 속도와 비례합니다.


-Fo


SPIR-V 를 생성할 경로에 대한 문자열입니다.


절대경로여도 되고 커맨드를 실행한 폴더에 대한 상대 경로여도 됩니다( 폴더를 지정하지 않으면 커맨드를 실행한 폴더에 생성됩니다 ).


이름은 마음대로 지정하시면 됩니다. 보통은 "[$(Identifier)_]$(StageName).spv" 라고 지정하는듯 합니다.


dxc -Fo my_vertex.spv


만약 이 옵션을 지정하지 않으면 콘솔창에다가 디스어셈블리 결과를 출력합니다.


-Fc


-Fo 에서 지정한 SPIR-V 파일에 대한 디스어셈블리 파일을 생성할 경로에 대한 문자열입니다. 


절대경로여도 되고 커맨드를 실행한 폴더에 대한 상대 경로여도 됩니다.


이름과 확장자는 마음대로 지정하시면 됩니다. 텍스트 파일로 생성되기 때문에 txt 인 것이 좋을 것 같습니다.


dxc -Fc my_vertex_disassembly.txt


-Fh


코드를 포함하고 있는 헤더 파일의 경로에 대한 문자열입니다.


dxc -Fh my_vertex_header.txt


헤더라고 하니, 엄청나게 헷갈리는데요, C/C++ 에서 사용할 수 있는 이진코드 배열을 정의하는 파일입니다. 열어 보니 디스어셈블리 내용도 포함하고 있더군요.


만약 코드에다가 SPIR-V 를 하드코딩하고 싶다면 이것을 사용할 수 있을 것 같네요.



-Fe


경고나 에러를 출력할 파일의 경로에 대한 문자열입니다. 만약 이 옵션을 지정하지 않으면 콘솔창에다가 결과를 출력합니다.


dxc -Fe my_vertex_output.txt


커맨드라인 프로그램을 사용하면 그 결과를 확인하기 위해서 stderr, stdout 에 대한 파이프를 만들어야 하는 경우가 있습니다. 매우 귀찮은 작업이죠.


그럴 경우에 텍스트로부터 한 번에 출력 결과를 읽어들일 수 있다면 매우 유용합니다.




주의할 점은 다음과 같습니다.


  • 성공시에는 아무런 로그도 남지 않습니다.

  • 표준 출력창의 내용을 redirect 한 것이므로 실패했을 경우에도 콘솔창에는 로그가 남지 않습니다.


주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 번역이 개판이므로 이상하면 원문을 참조하세요.

원문 : Static single assignment form, Wikipedia.


컴퓨터 디자인에서, static single assignment form( 보통 SSA form 이나 단순하게 SSA 로 발음됨 ) 은 중간 표현( intermediate representation, IR )의 속성( property )입니다. 그것은 각 변수들이 정확히 한 번만 할당되고 모든 변수들은 그것이 사용되기 전에 정의될 것을 요구합니다. 원래의 IR 에서 현재 존재하는 변수들은 여러 버전들로 나뉩니다. 새로운 변수들은 일반적으로 텍스트북에서의 서브스크립트( subscript, 아래첨자 )를 원래 이름에 붙여서 식별됩니다. 그래서 모든 정의들은 자신만의 원래 버전을 획득하게 됩니다. SSA 폼에서 use-def chain 들은 명시적( explicit )이며, 각각은 단일 요소를 포함합니다.

SSA 는 Barry K.Rosen, Kark N. Wegman, F. Kenneth Zadeck 에 의해 1988 년에 제안되었습니다. IBM 에서 근무하던 Ron Crytron 과 Jeanne Ferrante 및 이전의 세 연구자들은 SSA 폼을 효율적으로 계산할 수 있는 알고리즘을 개발했습니다.

사람들은 Fortran 이나 C 를 위한 컴파일러에서 SSA 를 검색하는 것을 기대할 수 있습니다. 반면에 Scheme, ML, Haskell 과 같은 함수형 언어( functional language ) 컴파일러들에서는 continuation-passing style 을 일반적으로 사용했습니다. SSA 는 공식적으로는, 중간 표현으로서 사용될 때는 발생하지 않는, non-local control flow 를 제외하고는 잘 동작하는( well-behaved ) CPS 의 서브셋과 동일합니다. So optimizations and transformations formulated in terms of one immediately apply to the other.

Benefits

SSA 의 주요한 유용성은 다양한 컴파일러에서의 최적화의 결과를 동시에 단순화하고 증진시킨다는 것입니다. 이는 변수의 속성들을 단순화함으로써 수행됩니다. 예를 들어, 다음 코드 조각을 살펴 봅시다.

y := 1

y := 2

x := y

인간들은 첫 번째 할당이 불필요하며 세 번째 라인에서 사용되고 있는 y 의 값이 두 번째 y 할당에서 온다고 생각할 수 있습니다. 프로그램은 reaching definition analysis 를 수행해야만 이를 결정할 수 있을 겁니다. 하지만 프로그램이 SSA 폼이라면, 이것들은 둘 다 임시적( intermediate )입니다:

y1 := 1

y2 := 2

x1 = y2

SSA 를 사용하거나 매우 강화된 컴파일러 최적화 알고리즘들은 다음과 같습니다 :

Converting to SSA

일반 코드를 SSA 폼으로 변환하는 것은 주로 각각의 할당의 대상을 새로운 변수로 치환하고 각각의 변수의 사용을 그 지점에 도달하는 "버전"의 변수로 치환하는 것입니다. 예를 들어, 다음과 같은 제어 흐름 그래프( control flow graph )를 살펴 봅시다:

"x <-- x - 3" 의 왼쪽에 있는 이름을 변경하고 그 다음에 x 를 사용하는 곳을 그 새로운 이름으로 변경하는 것은 프로그램을 수정하지 않고 종료될 수 있게 할 것입니다. 이것은 SSA 에서 새로운 두 개의 변수들을 생성함으로써 이용될 수 있습니다 : x1, x2. 각각의 변수들은 한 번만 할당됩니다. 이런식으로 차이가 나는 서브스크립트를 제공함으로써 다른 변수들이 만들어집니다:

각각이 사용하는 정의가 무엇을 참조하고 있는지는 명확합니다. 한 가지 경우만을 제외하고는 말이죠: 바닥에 있는 블락에서 y 의 사용은 y1 이나 y2 를 가리킬 수 있으며, 이는 제어 흐름이 타고 왔느냐에 의존합니다.

이를 해결하기 위해서, 마지막 블락에 특별한 구문이 삽입되는데, 이를 파이( Φ, Phi ) 함수라고 부릅니다. 이 구문은 y3 라 불리는 새로운 y 에 대한 정의를 생성하는데, 이는 y1 이나 y2 에 의해 선택됨으로써 생성되고, 과거의 제어 흐름에 의존됩니다.

이제 마지막 블락에서는 단순히 y3 를 사용하며 정확한 값은 두 가지 방식으로 획득될 것입니다. x 에 대한 Φ  함수는 불필요합니다: 한 가지 버전의 x 만이 존재하며, 여기에는 x2 가 도달하고 있습니다. 그래서 문제가 없습니다( 다시 말해, Φ ( x2, x2 ) = x2 입니다 ).

주어진 임의의 제어 흐름 그래프에서, 어디에 Φ  함수가 삽입되어야 하며 그것을 위한 변수가 무엇인지를 이야기하는 것은 어려울 수 있습니다. 이러한 범용적인 문제는 ( 아래에 나오는 ) dominance frontiers 라 불리는 개념을 사용해서 계산될 수 있는 효율적인 해를 가지고 있습니다.

Φ  함수는 거의 대부분의 머신에서 머신 연산으로서 구현되지는 않습니다. 컴파일러가, Φ  함수를 메모리의 같은 위치( 혹은 같은 레지스터 )를 Φ  함수의 입력으로 산출하는 모든 연산을 위한 목적지로서 사용함으로써, Φ  함수를 간단하게 구현할 수 있습니다. 그러나 이러한 접근법은, wide-issue 머신에서 발생할 수 있는 것처럼, 동시적 연산들이 추측에 근거해( speculatively ) Φ  함수의 입력을 산출하고 있을 때는 사용할 수 없습니다. 일반적으로 wide-issue 머신은, Φ  함수를 구현하기 위해서, 그러한 상황들에서 사용되는 컴파일러에 의해 제공되는 선택 명령을 가지고 있습니다.

Kenny Zadeck 에 의하면 Φ  함수들은 SSA 가 IBM 연구소에서 1980 년대에 개발되는 도중에는 원래 phony 함수들이었다고 합니다. Φ  함수의 공식적인 이름은 학술 논문에서 처음 출판될 때 적용되었다고 합니다.

[ 하략 ... 원문( Static single assignment form, Wikipedia. ) 을 참조하세요 ].

주의 : 초심자 튜토리얼은 아닙니다. 그러므로, 실제 API 호출 용례를 알고자 한다면, 샘플이나 튜토리얼을 찾아서 확인해 보세요.

주의 : 완전히 이해하고 작성한 글이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 확인하세요.



DirectX Shader Compiler 프로젝트를 빌드하고 나면 엄청나게 많은 executable 및 library 들이 생깁니다. 


하지만 우리가 그것을 다 사용할 것이 아니기 때문에 필요한 것만 가져다가 사용해야 합니다. 


일단 "dxc.exe" 프로젝트만 빌드해서 executable 을 뽑아 봤는데, LLVM table 관련 executable 을 배제하면, 필수적인 바이너리의 목록은 다음과 같습니다.


  • dxc.exe : 커맨드라인 프로그램.

  • dxc.pdf : 디버깅할 거라면 필요함.

  • dxcompiler.dll : 실제 구현이 포함되어 있는 동적 라이브러리.

  • dxcompiler.pdb : 디버깅할 거라면 필요함.


저는 이것만 복사해서 제가 원하는 디렉토리에 복사했습니다.



이제 vertex shader 를 하나 만들어서 테스트해 보겠습니다. 옵션에 대한 자세한 정보는 [ Supported Command-line Options ] 에서 확인하실 수 있습니다.



다음과 같이 파워셸에서 테스트해 봤습니다.


PS C:\GIt\HairDresser\Distribution\Binaries\ThirdParty\dxc> .\dxc.exe -spirv -Fc -T vs_5_0 -E mainVS C:\GIt\HairDresser\Distribution\Binaries\ThirdParty\dxc\test.hlsl -Fo test.vertex


  • -spirv : SPIR-V 를 생성.
  • -E : 엔트리 포인트.
  • -Fo : 아웃풋( SPIR-V )


혹시나 해서 dxcompiler.dll 을 지워 봤는데 에러가 발생하더군요.


dxc failed : error code 0x8007007e


다른 spir-v 관련 옵션들도 잘 동작하는지 테스트해 봤습니다.


PS C:\GIt\HairDresser\Distribution\Binaries\ThirdParty\dxc> .\dxc.exe -spirv -T vs_5_0 -E mainVS C:\GIt\HairDresser\Distribution\Binaries\ThirdParty\dxc\test.hlsl -Fo test.vertex -Fc test_dis.vertex


  • -Fc : SPIR-V 디스어셈블리 생성.


다음과 같이 정상적으로 디스어셈블리가 생성되었습니다. 내용이 정확한지는 아직 모릅니다. 시리즈를 진행하면서 의미를 파악해 볼 계획입니다.



단지 "Location" 과 같은 키워드가 있는 것으로 봐서는 따로 리플렉션 도구를 이용하지 않아도 이것을 파싱함으로써 정보를 얻을 수도 있겠다는 생각이 들긴 합니다.


참고 자료


[ 1 ] HLSL to SPIR-V Feature Mapping Manual, DirectXShaderCompiler.

주의 : 초심자 튜토리얼은 아닙니다. 그러므로, 실제 API 호출 용례를 알고자 한다면, 샘플이나 튜토리얼을 찾아서 확인해 보세요.

주의 : 완전히 이해하고 작성한 글이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 확인하세요.


 

문제점

 

[ 2. DXC 빌드 ] 에서 "dxc.exe" 를 빌드하는 과정에 대해서 살펴 봤습니다.

 

그 이후에 어떤 옵션들이 있는지 확인하기 위해서 코드를 한 번 살펴 봤습니다. 그런데 ENABLE_SPIRV_CODEGEN 이라는 preprocess definition 이 하나 보이더군요.

 

이게 비활성화되어 있었습니다.

 

프로젝트 구성에서 추가를 할까 했는데 그냥 [ 1 ] 의 가이드라인을 따라 정상적으로 빌드해 보기로 했습니다. 

 

CMAKE 솔루션 재생성

 

일단 [ 2. DXC 빌드 ] 과정까지 거쳤기 때문에 TAEF 를 다시 설치하거나 할 필요는 없습니다. 단지 프로젝트를 다시 만들어줘야 할 뿐입니다.

 

HLSL console 을 띄운 다음에 다음과 같이 "-spirv" 옵션만 추가해서 "hctbuild.cmd" 를 실행해 주면 됩니다.

 

 

이러면 열심히 CMAKE 프로젝트를 만들고 빌드합니다.

 

"LLVM.sln" 솔루션을 열어 봤더니 새로운 프로젝트들이 추가된 것 같더군요.

 

 

Trouble Shooting

 

역시나 이것도 한 번에 빌드가 안 되는군요.

 

 

에러가 4 개인 것처럼 보이지만 사실 SPIRV-Tools.lib 를 못 찾는 건 그냥 결과입니다. 근본적인 문제는 첫 번째 에러에 있습니다.

 

18>Traceback (most recent call last):

18>  File "C:/GIt/DirectXShaderCompiler/external/SPIRV-Tools/utils/update_build_version.py", line 148, in <module>

18>    main()

18>  File "C:/GIt/DirectXShaderCompiler/external/SPIRV-Tools/utils/update_build_version.py", line 134, in main

18>    software_version = deduce_software_version(sys.argv[1])

18>  File "C:/GIt/DirectXShaderCompiler/external/SPIRV-Tools/utils/update_build_version.py", line 90, in deduce_software_version

18>    for line in f.readlines():

18>UnicodeDecodeError: 'cp949' codec can't decode byte 0xe2 in position 846: illegal multibyte sequence

18>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\VC\VCTargets\Microsoft.CppCommon.targets(209,5): error MSB6006: "cmd.exe" exited with code 1.

 

원인을 찾아 봤는데 "update_build_version.py" 파일의 90 라인에서 읽고 있는 뭔가가 "cp949" 코덱으로 인코딩되어 있었기 때문입니다. 

 

 

그러므로 89 라인의 open() 함수에서 "UTF-8" 로 인코딩을 바꿔주고 이제 사용하지 않는 옵션인 "U" 를 제거하고 "t" 로 바꿔 줍니다.

 

 

이제 빌드가 잘 됩니다.

 

참고자료

 

[ 1 ] SPIR-V CodeGen, DirectXShaderCompiler.

주의 : 초심자 튜토리얼은 아닙니다. 그러므로, 실제 API 호출 용례를 알고자 한다면, 샘플이나 튜토리얼을 찾아서 확인해 보세요.

주의 : 완전히 이해하고 작성한 글이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 확인하세요.



DirectX Shader Compiler( DXC ) 는 High-Level Shader Language( HLSL ) 와 DirectX Intermdiate Language( DXIL ) 관련 컴파일러 및 툴체인을 포함하고 있는 프로젝트입니다.


예전에는 DXSDK 와 Windows 10 SDK 에 "fxc.exe" 라는 실행파일을 배포했었는데, 이제 따로 DXC 라는 이름으로 컴파일러를 배포하고 있더군요. [  DXC 프로젝트 위키 ] 를 보면 다중 플랫폼에 대응하고 개발자들의 열정페이를 촉진하기 위해서 만들어진 것 같습니다.


어쨌든 개발자의 요청 때문인지 HLSL 을 표준 셰이더 언어처럼 만들고자 하는 MS 의 야심때문인지는 모르겠지만, 2018 년 초부터 [ SPIR-V CodeGen ] 이라는 툴을 지원하기 시작했습니다. 따로 툴이 존재하는 것은 아니고 "dxc.exe" 의 파라미터를 통해서 실행됩니다.



이 "dxc.exe" 는 Windows 10 SDK 에 포함되어 있습니다.



즉 배포한 프로그램이 설치된 OS 에는 이 바이너리가 없을 수도 있습니다.


만약 개발자 머신에서만 dxc 를 사용하고, 배포된 프로그램에서는 dxc 가 필요하지 않다면 상관이 없습니다만, 배포된 프로그램에서도 이를 사용해 컴파일을 할 필요가 있다면 같이 배포해 줘야 합니다.


게다가 이 바이너리가 Windows SDK 와 함께 배포되므로 버그 수정과 같은 최신 변경사항에는 대응할 수 없습니다. 디버깅도 불가능하죠. 그러므로 직접 빌드해서 사용하는 것이 여러모로 좋다고 생각합니다.


아래에서는 Visual Studio 2017 을 사용해서 프로젝트를 빌드하는 과정에 대해서 살펴 보도록 하겠습니다. 다른 환경을 사용해서 빌드할 수도 있는데, 그것에 대해 궁금한 점이 있다면 [ 2 ] 를 참조하시기 바랍니다.


GitHub 클론


GitHub 나 GitHub Desktop 에 대한 기본 이해는 있다고 가정하고 진행하겠습니다.


먼저 [ 프로젝트 사이트 ] 로 가서 주소를 복사합니다.




바로 "Open in Desktop" 을 해도 되지만 가끔 실패하는 경우가 있어서 저는 그냥 GitHub Desktop 을 열어서 클론을 했습니다.



파이썬 설치


[ 파이썬 3.x ] 이상을 설치해야 합니다. 중요한 건 반드시 Path 에 파이썬 경로가 포함되어야 한다는 겁니다. 아래 그림에 빨간색 네모로 표시했습니다. 잊지 말아 주세요.



HLSL console 만들기


뭔지는 모르겠지만 HCT 라는 유틸이 있습니다. 제 생각에는 [ Hardware Certification Tool ] 의 머리글자인 것 같습니다.


어쨌든 "$(GitProjectRoot)/utils/hct/hctshortcut.js" 를 윈도우즈 탐색기에서 더블클릭해서 실행하면 바탕화면에 "HLSL console" 이라는 바로가기가 만들어집니다.


이것의 정체를 까 보면 다음과 같습니다.


C:\Windows\System32\cmd.exe /k C:\GIt\DirectXShaderCompiler\utils\hct\hctstart.cmd C:\GIt\DirectXShaderCompiler C:\GIt\hlsl.bin


hctstart 라는 커맨드를 실행하는데, 소스가 있는 폴더인 "DirectXShaderCompiler" 와 "hlsl.bin" 라는 폴더를 인자로 넣습니다.


실행해 보면 "HLSL_SRC_DIR" 과 "HLSL_BLD_DIR" 로 설정됨을 알 수 있습니다.



TAEF 설치

이제 TAEF 라는 것을 설치해야 합니다.


[ Test Authoring and Execution Framework( TAEF ) ] 는 MS 에서 하드웨어 관련 자동화 테스트를 하는 프레임워크인 것 같은데 이것을 설치해야 합니다. 


"$(GitProjectRoot)/utils/hct/hctgettaef.py" 를 실행해서 설치할 수가 있습니다.


아까 만든 "HLSL console" 을 실행해서 다음과 같이 입력합니다. 별다른 반응없이 종료되더군요. 이 작업은 처음 실행했을 때 1 번만 수행하면 된다고 합니다.



방법 1 : hctbuild 를 사용해 CMAKE 솔루션 생성


이제 그 상태에서 바로 "hctbuild" 명령을 실행합니다. 추가 : 이렇게 하면 단순하게 DX 만을 위한 컴파일러가 됩니다. "hctbuild -spirv" 를 입력해야 합니다. 자세한 사항은 [ 3. SPIR-V CodeGen 빌드 ] 에서 확인하시기 바랍니다.



한참을 기다리면, CMAKE 솔루션을 생성하고 빌드까지 합니다. 빌드 경로는 위의 "HLSL_BLD_DIR" 경로입니다. 이것을 고치려면 프로젝트 폴더의 "CMakeSettings.json" 에서 "buildRoot" 항목을 수정하시면 됩니다. 물론 구성별로 전부 수정해야 합니다. 


저같은 경우에는 "x86-Debug" 구성과 "x86-Release" 구성은 아예 지워버렸습니다. 필요하지 않기 때문입니다.



방법 2 : 직접 CMAKE 솔루션 생성


만약 "hctbuild" 를 사용하지 않겠다면, 직접 솔루션을 생성할 수도 있습니다( 직접 해 봤는데 추천하지는 않습니다. CMake 를 여는 데 사용했던 솔루션과 실제 CMAKE 솔루션 사이의 관계에 대한 혼동이 오기 때문입니다. ).


 Visual Studio 2017 은 CMake 에 대한 지원을 내장하고 있습니다. 그러므로 단순하게 프로젝트 폴더의 CMakeList.txt 를 읽어 들임으로써 솔루션이 생성됩니다.




이렇게 솔루션을 생성하면 "hctbuild" 를 생성한 것과 크게 다를 건 없습니다. 이 역시 "CMakeSettings.json" 의 "buildRoot" 에 프로젝트를 빌드합니다.


솔루션 열기


앞에서 언급했듯이 "buildRoot" 에 솔루션이 생성됩니다. 



"LLVM.sln" 을 더블 클릭해서 솔루션을 열 수도 있고, HLSL console 에서 "hctvs" 명령을 입력해 솔루션을 열 수도 있습니다. 일단 솔루션이 생성되고 나면 일반 Visual Studio 솔루션들과 다를 것이 없습니다.


Trouble Shooting


안타깝게도 현재 버전에서는 hctbuild 로 빌드에 성공하지 못하더군요. 솔루션을 열어서 다시 빌드해야 합니다.


86>c:\git\directxshadercompiler\tools\clang\lib\sema\semaexpr.cpp(12689): error C2220: warning treated as error - no 'object' file generated

86>c:\git\directxshadercompiler\tools\clang\lib\sema\semaexpr.cpp(12689): warning C4819: The file contains a character that cannot be represented in the current code page (949). Save the file in Unicode format to prevent data loss


코드페이지가 맞지 않아서라는데... 한글이 있는 것도 아니고 난감하네요.


"File >> Save As" 를 한 다음에 "Save" 옆의 드롭다운을 클릭해서 "Save with encoding" 을 클릭한 다음, "Advanced Save Option" 에서 "Unicode ( UTF-8 with signature) - Codepage 65001" 을 선택하시면 됩니다. 처음에 기본값으로 "without signature" 가 선택되어 있는데, 스크롤을 제일 위쪽으로 올리면 "with signature" 가 나옵니다.




빌드에 성공하면 "$(buildRoot)\$(Configuration)\bin" 에 "dxc.exe" 및 관련 dll 들이 생성되어 있는 것을 확인하실 수 있습니다.



참고 자료


[ 1 ] SPIR-V CodeGen, DirectXShaderCompiler.


[ 2 ] Building Sources, DirectXShaderCompiler.

주의 : 초심자 튜토리얼은 아닙니다. 그러므로, 실제 API 호출 용례를 알고자 한다면, 샘플이나 튜토리얼을 찾아서 확인해 보세요.

주의 : 완전히 이해하고 작성한 글이 아니므로 잘못된 내용이 포함되어 있을 수 있습니다.

주의 : 이상하면 참고자료를 확인하세요.


 

Descriptor

 

벌칸은 SPIR-V 라는 플랫폼 독립적인 중간언어를 사용합니다. 하지만 이것은 바이너리 파일이므로 사용자가 직접 작성할 수 없습니다. 

그래서 다른 셰이딩/컴퓨팅 언어들을 통해 프로그램을 작성한 다음에 이를 SPIR-V 로 컨버팅하게 됩니다. 이를 위한 언어들은 glsl, hlsl, opencl, cg 등이 될 수 있습니다. 예전부터 널리 사용해 오던 언어들이죠.

그런데 크로노스 그룹에서 정식으로 제공하고 있는 "glslangValidator.exe" 라는 프로그램이 glsl 과 hlsl 만을 지원합니다. 하지만 hlsl 에 대한 지원은 미비합니다( 나중에 언급하겠습니다 ). 크로노스 그룹에서 만든 언어이니 당연한 거겠죠.

벌칸에서는 셰이더에 뭔가를 바인딩하기 위해서 디스크립터( Descriptor )라는 것을 사용합니다. 그 중 버텍스 관련 디스크립터들에 대해서 살펴 보겠습니다.

출처 : [ Vertex input description ], Vulkan Tutorial.

버텍스 셰이더에서 사용하는 입력에 레이아웃이라는 것을 지정하는 것을 보실 수 있습니다. 버텍스 버퍼의 내부 메모리 구성이 어떻게 되어 있는지를 알려주는 거죠.

그러므로 네이티브 코드 단에서는 버텍스 입력을 제공하기 위해서 다음과 같이 클래스를 선언합니다.

출처 : [ Vertex input description ], Vulkan Tutorial.

하나의 버텍스 데이터 구조를 만들기 위해서 두 가지 작업을 하고 있는 것을 알 수 있습니다.

  • 버텍스 버퍼의 메모리 정보( 각 버텍스의 크기( stride ) )를 알려주기 위해 바인딩 디스크립터( VkVertexInputBindingDescription )를 생성.
  • 각 버텍스를 구성하고 있는 속성( attribute )들에 대한 정보( VkVertexInputAttributeDescription )를 생성.

 

SPIR-V Toolchain

 

위의 예제는 기본적으로 셰이더의 내용을 프로그램 작성자가 다 알고 있다는 가정하에서 이루어졌습니다. 하지만 실제 개발과정에서는, 저런 레이아웃의 순서가 바뀔수도 있고, 레이아웃의 순서가 어쨌든지 간에 자동화해서 데이터를 입력해 줘야 할 수도 있습니다. 셰이더 작성자와 코드 작성자가 완전히 다를 수도 있고, 셰이더 언어가 다양할 수도 있습니다.

맘 편하게 position = 0, normal = 1, texcoord = 2 라는 식으로 정할 수는 없다는 겁니다. 물론 하려면 할수도 있겠지만, 그 만큼 셰이더가 프로그램에 종속적이 될 수밖에 없겠죠.

그렇기 때문에 이런 것들( spir-v 변환부터 c++ 리플렉션까지 )을 자동으로 처리해 줄 수 있는 툴들이 필요합니다. 다행히도 크로노스에서는 이와 관련한 기본 툴체인을 제공하고 있습니다[ 1 ].

glsl 을 사용하고 있다고 가정했을 때 spir-v 로의 변환 및 리플렉션 과정은 다음과 같습니다.

  • Code Generation( glslangValidator ) : glsl 에서 spir-v 로 변환합니다. 
  • Optimization( spirv-opt ) : spir-v 파일의 크기를 줄입니다.
  • Validation( spirv-val ) : spir-v 코드가 올바른지 검사합니다. 이 과정에서 인간이 읽어들일 수 있는 포맷으로 변경하기 위해 디스어셈블러나 어셈블러를 사용해서 디버깅하기도 합니다( spirv-dis, spirv-as ).
  • Reflection ( spirv-cross ) : spir-v 파일로부터 리플렉션 정보를 추출합니다.

 

HLSL to SPIR-V

 

glsl 을 사용하고 있다면 그냥 기본 툴체인을 사용하면 되겠지만, hlsl 을 사용하고 있다면 일단 hlsl 에서 glsl 로 변환하는 과정부터 거쳐야 합니다. 그리고 layout 이라는 키워드 자체가 glsl 에서만 지원하는 것이므로 이와 관련한 변환 작업도 필요합니다.

사실 glslang 으로도 HLSL 을 SPIR-V 로 변환하는 것은 가능하지만 지원하지 않는 기능들이 많습니다. Khronos 의 [ HLSL FAQ ] 에서는 glslang 의 한계에 대해서 이야기하고 있습니다. 

일반적으로 어떤 툴체인을 사용하는지 검색해 봤는데, 다음과 같은 식으로 하고 있는 걸 발견했습니다.

  • Unity 3D 같은 경우에는 HLSLcc( hlslcc( James-Hones ) 라는 오픈소스의 변형 )를 사용해 spir-v 를 바로 생성하는 것으로 보입니다.
  • UE4 같은 경우에는 hlslcc( 원본인지 자체 변종인지는 모르겠습니다 )를 사용하여 hlsl 을 glsl 로 변환한 다음에 glslangValidator 를 사용해 spir-v 를 생성합니다.
  • DXC( DirectX Shader Compiler )는 hlsl 을 직접 spir-v 로 변환하고 있는 것( SPIR-V CodeGen )으로 보입니다.

 

나가며

 

저같은 경우에는 HLSL 을 선호하고 툴체인이 길어지는 것을 별로 좋아하지 않기 때문에 dxc 를 사용해 보려고 합니다. 

[ HLSL to SPIR-V ] 시리즈에서는 DXC 를 사용해서 hlsl 파일로부터 spir-v 를 생성하고 C++ 에서 리플렉션하는 과정까지를 다뤄 볼 계획입니다.

추가 : 참고로 위키에 따르면 SPIR-V CodeGen 은 MS 가 아니라 google/shaderc 팀에서 주로 관리한다고 하는군요.

 

참고자료

 

[ 1 ] SPIR-V Toolchain, LUNARG.

주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

주의 : 특별한 경우가 아니라면, 귀찮아서 문제는 안 옮깁니다.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



( a )


m 에 대해 작용하는 중력 가속도는 g 이며 이를 힘으로 나타내면 F = mg 입니다.


그러므로 다음과 같이 g 를 계산할 수 있습니다.


식 1.

( b )


gG 를 이용해서 지구의 질량을 구해야 한다면 식 1 을 사용할 수 있습니다.


식 2.

주의 : 잘못된 내용이 포함되어 있을 수 있으므로 이상하면 참고자료를 확인하세요.


[ PBR 이란 무엇인가 ] 21. PBR Shader 구성


챕터 20 까지 하고 글을 마무리하려고 했는데, 궁금해 하시는 분이 계셔서 글을 추가합니다.

아마도 프로그래머나 TA 라면 PBR 을 실제로 어떻게 구성해야 하는지에 대한 의문이 들 겁니다.

각각의 의미는 알겠는데 셰이더의 결과에는 무엇이 반영되어야 하나요?

우리는 [ 9. Global Illumination & Indirect Lighting ] 에서 조명은 Direction Lighting 과 Indirect Lighting 으로 구분된다는 것을 알게 되었습니다. 이러한 조명들 중에서 눈에 도달하게 되는 성분들이 셰이더 계산의 결과가 됩니다.


조명 분석


눈에 들어 오는 빛들을 정리해 보죠. 여기에서는 대기 산란( atmospheric scattering )과 같은 복잡한 현상에 대해서는 언급하지 않고 기본적인 것들에 대해서만 다루도록 하겠습니다. 그림 1 에 눈에 들어 오는 빛의 몇 가지 샘플들을 그려 봤습니다.

그림 1.

여러분은 그림 1 에 있는 빛들의 카테고리를 규정할 수 있어야 합니다. 반사나 굴절을 통해 눈으로 직접 들어 온 빛은 직접 조명( direct lighting )이며, 다른 물체에 튕겨졌다가 온 빛은 간접 조명( indirect lighting )입니다.

  • 1 : [ direct ] diffuse + specular.
  • 2 : [ direct ] diffuse + specular.
  • 3 : [ direct ] emissive( 방출 ). 물체 자체가 발광하고 있음.
  • 4 : [ indirect ] diffuse + specular.
  • 5 : [ direct ] diffuse + specular.
  • 6 : [ direct ] diffuse + specular.
  • 7 : [ indirect ] transmission( 투과 ).
  • 8 : [ indirect ] diffuse + specular.
  • 9 : [ direct ] transmission.

여기에서 혼란에 빠지는 분들이 있을 겁니다.

제 눈에는 정반사 성분은 안 보이는데요, 왜 "diffuse + specular" 가 들어 오는 건가요?

스펙큘러 반사는 정반사( 입사각과 반사각이 동일한 반사 )만을 의미하는 것이 아닙니다. 특정 방향을 중심으로 로브( lobe )를 형성합니다. 스펙큘러 반사가 얼마나 퍼지냐는 BRDF 에 의해서 결정되죠. 이것은 표면이 완전히 평평하지 않기 때문에 발생하는 현상입니다. 그림 2 에서 여러 형태의 로브를 보여 주고 있습니다.

그림 2. 매질의 거칠기에 따른 스펙큘러 로브. 출처 : [Specular IBL].

그러므로 정반사 각도가 아니더라도 스펙큘러가 눈에 들어 올 수 있는 것입니다.

자, 이제 눈에 들어 올 수 있는 빛의 성분들을 나눠 보죠. 빛이라는 것은 눈에 들어 오면 누적되기 때문에 다음과 같은 식을 세울 수 있습니다.

식 1.

식 1 이 기본적인 셰이더의 함수입니다.

간단하게 머리글자로 표기하도록 하겠습니다.

식 2.

그런데 식 2 를 보고 있으면 의문이 생길 겁니다. 셰이더라는 것이 화면상의 한 픽셀에 대해서 처리됩니다. 그런데 어떻게 여러 광원에서 오는 빛을 처리할 수 있을까요?

그래서 실제로 결과는 다음과 같이 처리되어야 합니다.

식 3.

식 3 에서 Sigma 연산자는 괄호 안의 요소를 모두 더한 결과를 누적한다는 의미입니다. 광원이 1 부터 n 까지 있을 때 각각의 diffuse, specular, transmission 를 더한 결과를 모두 누적한다는 겁니다.

여기에서 하나의 의문을 가질 수 있습니다.

왜 E 는 광원별로 처리하지 않나요?

방출( Emissive ) 은 광원에서 발생하는게 아니라 재질의 특성입니다. 그러므로 한 번만 처리됩니다. 

이런 계산을 셰이더에서 수행하는 방법은 여러가지가 있는데, 크게 두 가지 방식으로 처리됩니다. 여기에서는 누적된 광원을 적용하는 패스가 따로 있다는 가정을 가지고 있습니다. 

  • 한 셰이더에서 한 개의 광원에 대한 결과를 처리하고 광원을 바꿔가면서 여러 패스를 실행할 수 있습니다.
  • 한 셰이더에서 여러 개의 광원의 결과를 한꺼번에 처리합니다.

그림 3. 조명 누적.

그런데 사실 싱글패스를 구현하는 것은 어렵습니다. 셰이더라는 것이 화면의 전체 픽셀에 대해서 적용되므로 불필요한 계산이 수행될 수 있습니다. 게다가 몇 개의 광원을 처리해야 할지 애매하죠. 셰이더에서 무거운( 지금은 안 무거울라나... ) 루프를 돌려야 합니다. 그래서 최적화를 위해서 광원 영역에 대한 stencil 을 찍어 두거나 영역( viewport, scissor-rect )을 따로 잡은 후에 멀티패스로 돌리는 경우가 많습니다.


Direct Lighting vs Indirect Lighting


눈썰미가 좋은 분들은 식 3 에 직접 광원 성분과 간접 광원 성분이 분리되어 있지 않다는 것을 눈치채셨을 겁니다.

간접 광원은 매우 계산하기 힘들기 때문에 보통은 라이트맵( Lightmap )을 사용하거나 IBL( Image-Based Lighting ) 큐브맵을 사용하게 됩니다. 라이트맵과 IBL 큐브맵에 대해서 여기에서 자세하게 설명하지는 않겠습니다. 검색해 보면 관련 자료들이 많으니 그것들을 참고하시기 바랍니다.

여기에서는 그것들의 차이만을 간략하게 설명하도록 하겠습니다. 나중에 기회가 되면 한 번 정리하도록 하겠습니다.

  • 라이트맵은 정적인 라이팅 환경을 대상으로 구워집니다. 그래서 일반적으로 어느 방향에서 봐도 동일한( 뷰 독립적인, view-independent ) 디퓨즈 성분만을 저장합니다. 일반적인 라이트맵을 사용하게 되면 스펙큘러 성분을 알 수 없습니다. 대신 스펙큘러 성분을 어느정도 반영하기 위해서 RNM( Radiosity Normal Map ) 라이트맵이라는 것이 있기는 하지만 이것 역시 정적 환경이라는 점에서는 차이가 없습니다.
  • IBL 큐브맵은 조도( irradiance ) 샘플을 저장합니다. 어떤 기준점을 중심으로 주변의 빛들을 모두 광원으로 처리합니다. 여기에는 디퓨즈 성분과 스펙큘러 성분이 모두 들어 갈 수 있습니다. 실시간에 큐브맵을 생성한다면 동적인 라이팅 환경을 반영하는 것도 가능합니다( 물론 성능 저하는 감수해야 합니다. 큐브맵을 다시 만들어야 하기 때문이죠 ). 동적인 라이팅을 허용하는 IBL 구현은 현재 많지 않습니다. 기존에 Asker 라는 게임 프로젝트에서 프레임별로 각 면을 나눠서 생성한 적은 있는데, 그것만으로도 큰 부하가 듭니다. 단순하게 씬을 렌더링하는 것을 떠나서 임포턴스 샘플링( importance sampling )같은 것도 해야 하기 때문입니다. 게다가 시차때문에 생기는 문제를 해결( 시차 보정, Parallax Correction )해야 합니다. 현재 하드웨어에서는 사용할만 한지는 잘 모르겠네요. 펄 어비스에 다니는 지인의 말로는 실제로 프로젝트에서 사용하고 있다고 하는 것 같더군요. 이 외에도 여러 가지 문제( 캡쳐 범위 등 )가 존재하는데 잡다한 문제를 다 해결해야지 사용할 만 합니다.

어쨌든 라이트맵이나 IBL 큐브맵을 사용해서 간접광 성분을 계산할 수 있습니다.

그러므로 식 3 은 다음과 같이 변경됩니다.

식 4.

"dl" 은 직접광을 의미하며 "il" 은 간접광을 의미합니다.

그런데 사실 투과( Transmission )같은 경우에는 서브 서피스 스캐터링( Sub-Surface Scattering )이 아니라면 일반적으로는 배제합니다. 피부나 초같은 밀랍( Wax )같은 재질이 아니라면 그냥 투과는 처리하지 않습니다. 왜냐하면 그 비용이 상당히 비싸기 때문이죠. 하지만 어쨌든 일반식을 만드는 것이니 식은 그냥 두겠습니다.


에너지 보존 법칙


마지막으로 고려해야 할 부분은 에너지 보존법칙입니다. 

일반적으로 에너지 보존법칙은 디퓨즈와 스펙큘러에만 적용합니다. Emissive( 방출 )은 자체적으로 에너지가 생성된 것이고  에너지 보존 법칙이 성립하려면 다음과 같이 되어야 합니다.

식 5.

그런데 일반적인 BRDF 식에서의 Diffuse, Specular, Transmission 에는 이미 Absorbtion( 흡수 ) 이 포함되어 있습니다. 이런 계산까지 전부 다 하게 되면 엄청나게 복잡해질 겁니다. 물론 이런 계산을 포함한 BRDF 도 어딘가에는 있겠죠. 제가 BRDF 함수들을 모두 완벽하게 알고 있는 게 아니라서 잘은 모르겠습니다.

그러므로 일반적으로는 Diffuse 와 Specular 에 대해서만 에너지 보존 법칙을 적용하게 됩니다. 그게 눈에 가장 잘 보이기 때문입니다.

보통 Specular 의 세기는 금속성( metalic )에 의해서 결정되고 있기 때문에 metalic 을 에너지 보존법칙을 적용하는데 사용합니다. 물론 구현마다 차이는 있을 수 있습니다만, 적어도 UE4 는 metalic 을 기준으로 사용합니다.

그래서 식 4 에 에너지 보존 법칙을 적용하면 다음과 같습니다.

식 6.


BRDF 와 Light Intensity


가끔 BRDF 식을 보면 Light Intensity 를 포함하고 있는 경우가 있습니다. 챕터 20 댓글에 어떤 분이 이와 관련한 질문을 하셨습니다. 요약하자면 "Light Intensity 와 BRDF 의 관계가 어떻냐"는 것이었습니다.

그분이 예로 드신 Oren-Nayar BRDF 식은 다음과 같습니다.

식 7. Oren-Nayar BRDF. 출처 : [ Oren-Nayar reflectance model ], Wikipedia.

해당 문서에서는 E0 를 미세면에 도달한 조도( E0 is irradiance when the facet is illuminated head-on )라고 이야기하고 있습니다. 그리고 Lr 을 휘도( the radiance of the reflected light Lr ) 라고 이야기하고 있습니다. E0 이 일반적으로 Light Intensity 라 할 수 있습니다.

여기에서 두 가지 이슈가 발생합니다.

  • Light Intensity 에는 어떤 값이 들어 가야 하나요?
  • 식 7 에다가 Light Intensity 를 곱해야 하는 것일까요?

첫 번째 질문에 대해서 이야기하자면, 일단 대부분의 BRDF 함수들은 Light Intensity 로 칸델라( Candela, cd )를 받는 것으로 알고 있습니다. 위에서도 미세면에 도달한 빛( 조도 )에 대한 함수이기 때문에 이것은 칸델라여야 합니다. 칸델라는 단위 입체각당 광속을 의미합니다. UE4 에서는 점광원에 대해서 광속( luminous flux, lm )을 받다가 최근에 칸델라를 받도록 수정했죠. 광속이 뭔지 기억이 안 나시는 분들은 [ 4. 광원의 밝기( 광속 ) ] 챕터를 참조하시기 바랍니다.

두 번째 질문에 대해 답하자면, Light Intensity 가 BRDF 공식에 이미 적용되어 있다면 적용하지 말아야 한다는 겁니다. 그러므로 BRDF 셰이더 함수를 만든다면 Light Intensity 가 배제된 순수한 BRDF 를 만드는 것이 좋겠죠. 식 7 의 경우에는 E0 을 배제한 나머지 항들이 순수한 BRDF 식의 항들입니다.


정리


기본적으로 그래픽스에서 휘도( radiance, luminance )를 다룰 때는 눈에 들어 오는 모든 빛은 누적된다고 생각해야 합니다.

단지 해당 빛이 어디에서 오는지를 명확하게 할 필요가 있습니다. 그래야지 조명별로 처리할지 한 번만 처리할지 결정할 수 있기 때문입니다.

하지만 더하기만 하지는 않는 경우도 존재합니다. 예를 들어 차폐같은 것들이 그렇죠. 하지만 차폐도 보통 빛을 아예 누적하지 않거나 감쇠( attenuation ) 비율을 곱하는 식으로 적용하는 경우가 많습니다. Ray-Tracing 기법을 사용하면 무조건 더하기만 해도 아무 문제가 없지만 Rasterization 에서는 이런 저런 꼼수들이 들어 갑니다. 여기에 대해서 자세하게 언급은 안 하겠지만, Ray-Tracing 과 Rasterization 의 차이에 대해서 한 번 찾아 보는 것도 생각을 정리하는데 많은 도움이 될 것이라 생각합니다.

나름대로 정리한다고 하긴 했지만 빠져 있는 성분들이 꽤 많습니다; 대기산란, 차폐( Shadow, Ambient Occlusion 등 ).

하지만 BRDF 공식을 가지고 있을 때 어떻게 이것들을 결합해야 할지에 대해서는 대충 감이 오셨을 것이라 생각합니다.

PS : 반투명 재질을 렌더링한 결과( 우리가 일반적으로 사용하는 알파블렌딩 )는 투과( Transmission ) 성분을 반영합니다. 단지 IOR( Index Of Reflection )을 고려하지 않아서 굴절( Refraction )이 없는 것처럼 보일 뿐입니다. 투과 성분은 광원에서의 빛이 매질 내부에서 산란이 된 후 튀어 나오는 성분이기 때문에 매우 계산이 어렵습니다. 그래서 보통 후처리( Post-Process ) 패스에서 대충 왜곡( distortion )하는 식으로 처리합니다.

주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

주의 : 특별한 경우가 아니라면, 귀찮아서 문제는 안 옮깁니다.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



( a )


지구의 반지름이 6400 km 이므로 지구의 둘레는 다음과 같습니다.


식 1.


지표 근처에서 인공위성이 90 분마다 지구를 한 바퀴 돌기 때문에, 인공위성의 선속력은 다음과 같습니다.


식 2.


이때 가속도의 크기는 다음과 같습니다.


식 3.


( b )


지구에서 달 까지의 거리가 지구 반지름의 60 배이므로 회전 반경은 384000 km 입니다.


그러므로 회전반경이 만드는 원의 둘레는 다음과 같습니다.


식 4.


28 일마다 지구를 한 반퀴 돌기 때문에, 달의 선속력은 다음과 같습니다.


식 5.


이 때 가속도의 크기는 다음과 같습니다.


식 6.

주의 : 답이 틀릴 수도 있습니다. 그냥 정리하는 용도로 올립니다. 혹시라도 도움이 필요한 분이 있다면 도움이 되었으면 좋겠네요.

주의 : 특별한 경우가 아니라면, 귀찮아서 문제는 안 옮깁니다.

경고 : 숙제하려고 베끼는 데 사용하지 마십시오. 본인의 미래를 망칠 뿐입니다. 나중에 저를 원망하지 마세요.

부탁 : 문제 풀이가 잘못되었으면 지적해 주셨으면 좋겠습니다.



그림 1.


용수철 길이가 원래 L 이고 m 인 물체를 매달고 운동했을 때 L 만큼 늘었났다고 했습니다. 이 때 용수철 상수는 k 라 했으므로 용수철이 받고 있는 힘의 크기는 다음과 같습니다.


식 1.


일단 구심력의 크기는 다음과 같습니다.


식 2.


용수철이 늘어난 것은 원심력 때문입니다. 그런데 원심력과 구심력의 크기는 동일합니다. 그러므로 다음과 같이 속력을 구할 수 있습니다.


식 3

+ Recent posts