Post Lists

2018년 12월 10일 월요일

원통Cylinder(or 원뿔cone) 렌더링하기

http://paulbourke.net/geometry/circlesphere/

다음은 어떻게 "이상적인" cylinder (or cone)을 discrete facets들로 나타내는지를 설명한다. 이것을 하길 원하는 이유들은 대개 cylinder primitive를 지원하지 않는 환경, 예를들어 OpenGL, DXF 그리고 STL같은 환경으로부터 기인한다. cylinder의 매우 일반적인 정의가 사용될 것인데, 그것은 두 개의 끝점과 각 끝에서의 한 반지름으로 정의될 것이다. 전통적인 cylinder는 두 개의 반지름이 같을 것이고, tapered cylinder는 다른 반지름을 가질 것이고, cone은 한 끝에서 zero 반지름을 가질 것이다.

다음의 이미지는 4개의 vertex faces 또는 전체 3개의 vertex facets가 있는 cylinder들을 보여준다. 4 vertex polygons는 coplanar이기 때문에, 그것들을 두 개의 3 vertex facets로 분리하는 것은 resolution을 개선시키지 않는다는 것에 주목해라. End caps는 일반적으로 optional인데, 그것들이 필요하든지 말든지는 프로그램 의존적이다.

facets의 정점들을 명시하기 위해서, cylinder를 구성하는 것은 처음에 cylinder의 축에 수직하고, 서로에 수직인 두 개의 벡터를 필요로한다. 이러한 것들은 오른쪽 그림에서 빨간색과 파란색으로 보여진다. 이러한 두 개의 벡터들을 만드는 많은 방법들이 있는데, 그것들은 보통 cylinder axis와 collinear하지 않는 어떤 벡터의 형태를 요구한다. cylinder axis(P2 - P1)와 함꼐 그 벡터의 외적은 그 벡터들 중 하나를 주고 (가령 A), 그리고 그 축과 함꼐 이 새로운 벡터의 외적은 다른 벡터를 준다 (B). 이러한 두 게의 수직인 벡터들은 그러고나서 표준화된다.

두 개의 수직인 벡터들 A와 b가 주어진다면, cylinder의 rim(테, 가장자리) 주변을 둘러싸는 정점들을 만들 수 있다. 그래서, 4개의 vertex facet에 대해, 그 정점들은 다음에 의해서 주어질지도 모른다, 거기에서 theta2 - theta - 1은 어떤 적절히 작은 각인데, 근사의 roughness를 결정한다.



P1, P2, A, B가 3차원에 있는 모든 벡터이다. r1과 r2는 양 쪽 끝의 반지름이다.

어떤 슈도 c코드로 쓰여진다면, 그 facets들은 다음처럼 만들어질지도 모른다


create A and B
for (i = 0; i < NFACETS; i ++)
{
      n = 0;
      theta1 = i * TWOPI / N;
      theta2 = (i+1) * TWOPI / N;
      q[n].x = P1.x + r1 * cos(theta1) * A.x + r1 * sin(theta1) * B.x
      q[n].y = P1.y + r1 * cos(theta1) * A.y + r1 * sin(theta1) * B.y
      q[n].z = P1.z + r1 * cos(theta1) * A.z + r1 * sin(theta1) * B.z
      n++;
      q[n].x = P2.x + r2 * cos(theta1) * A.x + r2 * sin(theta1) * B.x
      q[n].y = P2.y + r2 * cos(theta1) * A.y + r2 * sin(theta1) * B.y
      q[n].z = P2.z + r2 * cos(theta1) * A.z + r2 * sin(theta1) * B.z
      n++;
      if (r2 != 0) {
         q[n].x = P2.x + r2 * cos(theta2) * A.x + r2 * sin(theta2) * B.x
         q[n].y = P2.y + r2 * cos(theta2) * A.y + r2 * sin(theta2) * B.y
         q[n].z = P2.z + r2 * cos(theta2) * A.z + r2 * sin(theta2) * B.z
         n++;
      }
      if (r1 != 0) {
         q[n].x = P1.x + r1 * cos(theta2) * A.x + r1 * sin(theta2) * B.x
         q[n].y = P1.y + r1 * cos(theta2) * A.y + r1 * sin(theta2) * B.y
         q[n].z = P1.z + r1 * cos(theta2) * A.z + r1 * sin(theta2) * B.z
         n++;
      }
      do something with q[0..n]
}

Note

  • 여기에 설명된 알고리즘은 음수 반지름도 완벽하게 처리할 것이다. 한 반지름이 음수이고, 다른 것이 양수라면, 그러면 그 cylinder는 단일점을 지나갈 것이고, 효과적으로 두 개의 끝을 가진 cones처럼 보일 것이다. 이것은 항상 허용되지 않는 그것들에서의 twist를 갖는 facets을 이끈다.
  • end caps는 간단히 각 끝에 radius를 체크하도록 하여 형성된다. 만약 그것이 0이 아니라면, 그러면 추가적인 3개의 vertex faces가 정점들 P1, q[0], q[3] 그리고/또는 P2,q[1], q[2]로 만들어진다.
  • 만약 너의 프로그램들이 3개의 정점 facets를 요구한다면, 그러면 위의 4개의 vertex facets은 q[0], q[1], q[2] 그리고 q[0]. q[2]. q[3]이 된다.
  • 너의 프로그램의 어떤 facet orderings requirements에 주의해라. 바깥으로 가리키는 normals를 제외하고 많은 패키지들에서, 정점들의 정확한 순서는 또한 너가 왼손 또는 오른손 좌표계를 쓰는지에 의존한다.


2018년 12월 6일 목요일

Shadows in deferred rendering

https://gamedev.stackexchange.com/questions/25436/shadows-in-deferred-rendering

질문:
나는 deferred rendering에 관한 몇 가지 자료들을 읽었고, 나는 그것의 요지를 이해했다고 생각한다. 그러나 내가 이해하지 못한 것은 그것이 어떻게 쉐도우를 성취하냐 이다. 내가 알기로, G-buffer는 각 light에 대해 shadow map을 만드는 것을 포함하지 안흔다. 그래서 나는 lighting pass가 각 픽셀이 가려져있는지를 인식하는 것에 대해서 혼동이 된다. 결국, 카메라의 관점에서 보이는 주어진 픽셀이 실제로 어떤 주어진 light의 관점으로 부터 보이지 않을지도 모른다, 그리고 그 가려진 도형은 카메라의 관점으로부터 안보일지도 모르고, 그러므로 그것에 대해 G-buffer에 쓸 게 없다.

만약 너가 shadow maps를 렌더링하기 시작한다면, 그러면 그것은 forward rendering과 꽤 같은 것처럼 보인다. 너는 모든 light가 shadow maps를 그리도록 scene에서 모든 기하들을 렌더링한다.

그래서 어떻게 deferred rendering이 forward rendering과 같은 shadows를 해내냐?

답변1:
Deferred shading은 그맂마에 대해 어떤 특별한 것을 하지 않는다. 너는 여전히 그 shadow maps을 보통 렌더링할 필요가 있고, 그러고나서 각 light를 적절한 shadow map이 texture로 바인딩 된 채로 렌더링 할 필요가 있다.

그것은 forward rendering보다 더 좋은데, 왜냐하면 너는 main view에서 lighting을 적용하기 위해 그 scene을 다시 그릴 필요가 없기 때문이다. shadow map을 그리는 것은 main view에서 더 많은 passes를 그리는 것 보다 훨씬 더 싸다. 왜냐하면 너는 어떤 픽셀 쉐이딩도 할 필요가 없기 때문이다. 그리고 shadow maps는 종종 scene보다 덜 포함한다 (너는 많은 것들을 버릴 수 있다).

사람들은 가끔씩 한 개의 light에 대해 deferred shadows를 한다, 일반적으로 main directional light. 이것을 하는 주된 이유는, cascaded shadow maps 또는 같은 light에 대한 다양한 shadow maps를 사용하는 또 다른 접근법을 사용하려고 하는 것이다. 너는 G-buffer에서 shadow mask에 대한 한 채널을 보유할 수 있따 (비춰지면 white, 그림자가 있으면 검은색) 그리고 모든 cascaded shadow maps를 이 G-buffer channel에 적용한다; 그러고나서 그 light에 대한 쉐이더는 그 shadow mask를 일고, 그것을 light color와 곱한다. 이것은 shading으로부터 shadows를 분리시키기 때문에 좋다. 그러나 너는 여전히 모든 같은 shadow maps를 그리고 있다.

답변2:
음, shadow map이 무엇일까? shadow map은 텍스쳐인데, 간단한 질문에 답하는 texels들이다: light로 부터 무슨 거리에서, texel에서 나타내어지는 방향을 따라, 그 light가 가려지는가? 텍스쳐 좌푣르은 다양한 projective texturing 수단ㄷ들을 사용하여 생성되고, 특정한 shadow mapping algorithm에 의존한다.

Projective texturing은 간단히 한 오브젝트를 텍스쳐의 공간으로 변환시키는 방법이다 (그리고 그렇다, 나는 그것이 뒤로 가는 걸로 들린다는 것을 안다, 그러나 그것이 작동하는 방식이다). 쉐오두 매핑 알고리즘은 몇 가지 다른 종류의 transform을 사용한다. 그러나 궁극적으로, 이러한 것들을 한 공간에서 다른 곳으로의 변환이다.

shadow map을 렌더링할 때, 너는 너의 도형들의 정점을 취하고, 그것들을 standard 렌더링 파이프라인을 통해 변환한다. 그러나 카메라와 projecion 행렬들은 너의 light position과 direction을 위해 설계되어져있따, view position과 방향이 아닌.

shadow map으로 forward rendering을 할 때, 너는 그 오브젝트들을 보통 렌더링하는데, 그 정점을 view camera space로 변환시키고, viewing projection matrix로 변환시킨다. 그러나, 너는 그 정점들을 너의 light camera and projection matrices로 변환시킨다, 그리고 그섣르을 fragment shader에 per-vertex data로서 넘긴다. 그것은 projective texturing을 통해 shadow texture에 접근하기 위해 그것들으 사용한다.

여기에 중요한 점이 있다. projective texture access는 그것이 텍스쳐에 접근하는 위치가 그 표면에서 점과 light 사이의 방향을 나타내도록 설게되어져 있다. 그러므로 , 그것은 fragment가 렌더링되었을 때 occlusion이 발생한 깊이를 나타내느 texel를 가져온다.

그러나 이 파이프라인에 특별한 것은 없다. 너는 그 정점 위치들을 shadow texture로 바꿀 필요가 없고, 그것들을 fragment shader로 보낼 필요가 없다. 너는 world-space veretx positions을 fragment shader로 넘기고, 그러고나서 fragment shader가 그것들을 shadow texture의 projective space로 변환하도록 한다. 당연히, 너는 많은 성능을 버릴것이다, 왜냐하면 너는 정확히 같은 텍스쳐 좌표를 쓸 것이기 때뭉니다. 그러나 그것은 수학적으로 실행가능하다.

참으로, 너는 그 view camera-space vertex positions를 fragment shader에 보낼 수 있따. 그러고나서 그것을 그것들을 world로 변환하고, 그러고나서 light camera-space로 바꿀 수 있고, 그러고나서 projective shadow texture로. 너는 모든 그러한 transformation을 한 행렬에 넣을 수 있다 (너의 shadow projection algorithm에 따라). 또 다시, 이것은 너에게 너가 이전에 가졌떤 것을 정확히 준다, 그래서 forward rendering할 떄, 그것을 할 이유가 없다.

그러나 deferred rendering에서, 너느 이미 view camera-space vertex positions을 가지고 있다. 너는 많은 메모리와 bandwidth를 낭비한다, 그것들을 한 buffer에 써넣어서, 또는 너는 똑똑해서, 그것들을 재 계산한다, 그 depth buffe와 다양한 수학을해서 (나는 여기까지 안와봤찌만, 온라인에서 다뤄진다).

어느 방법이든, 너는 view camera-space positions를 가진다. 그리고 위에서 명시되었드싱, 우리는 한 행렬을 적용하여 view camera-space에서 shadow projective texture space로 변환한다. 그러고나서 ㅇㄴㄻㄴㅇ리ㅏ언ㄹ

--------------------------------------------
내가 찾는 것은 shadow map을 할 때 어떻게 draw call을 줄이는 것이다.

그러니까  코드로 하자면

for( all object in world)
    dirLight.renderDepthShadowMap();

for(all object in world)
    deferred.rederGbuffer();

이렇게 될 텐데, 저 두 for문을 합쳐야 draw call을 줄이게 되고, 성능을 아끼지 않을 까 생각이든다.

2018년 12월 5일 수요일

Shadow Mapping하기 in Deferred Rendering

http://www.codinglabs.net/tutorial_opengl_deferred_rendering_shadow_mapping.aspx

Deferred Rendering Shadow Mapping

이 튜토리얼에서, 나는 deferred renderer에서 구현된 shadow mapping 기법을 보여줄 것이다. 이 튜토리얼은 이전의 것인 Simple Deferred Rendering in OpenGL에 기반을 둔다; 나는 강하게 너가 이 튜토리얼을 나가기전에 읽을 것을 추천한다, 대부분의 코드가 공유되기 때문이고, 나는 다른 튜토리얼에서 이미 다뤄진 것을 보여주지 않을 것이다.

Shadow Mapping은 실시간 렌더링 엔진에서 오늘날 가장 널리 사용되는 기법이다. 거의 모든 게임은 그것의 그림자들을 렌더링하기 위해 일종의 shadow mapping technique을 사용한다. 비록 종종 실제 구현은 우리가 이 튜토리얼에서 보게될 basic shadow mapping의 확장에 의존할지라도. 몇 가지 좀 더 고급 구현은 Cascade Shadow Mapping, Soft Shadow Mapping, Parallel Split Shadow Mapping, 등이 있다. 이 튜토리얼에 대해, 우리는 모든 이러한 기법들이 사용하는 기본 아이디어를 이해하려고 할 것이다. 그리고 이 기본 기법은 그림자를 렌더링하는데 유용한 정보를 인코딩하는 texture(map)을 사용할 것이다.

한 픽셀이 그림자에 있는지 또는 만약 그것이 주어진 빛으로 비춰지고 있는지를 결정하는 것은 visibility problem이다. 우리가 테스트하기 원하는 것은 만약 world에 있는 그 특정한 점이 보이거나 또는 light point of view로 부터 보이지 않는다면, 우리는 우리가 스크린에 그리려는 모든 픽셀에 이 아이디어를 적용한다. 따라서 우리가 하려고 하는 것은 각 픽셀이 world space에서 어떤 위치에 매핑되어있는지를 계산하는 것이다. 우리가 position만 가지기만 한다면, 우리는 light로 다시 ray를 날릴 수 있다. 만약 그 ray가 방해되는 거 없이 light에 도달할 수 있다면, 그 pixel은 빛 안에 있고, 그렇지 않다면 그림자 안에 있다. 아래의 사진을 보아라 (Figure 1). 너는 그 두 점이 카메라로부터 둘 다 보이지만, light로부터는 하나만 보인다.

그래서 이제 우리는 그 원리를 알았다. 우리는 연습해볼 수 있다. pixel이 (world에서 다시 매핑되는) light position에서 보이는지 안보이는지를 결정하는가? 많은 방법들이 있지만, 우리가 구현하려는 것은 scene의 depth를 렌더링하는 GPU의 능력을 이용하는 것이다. 그래서 만약 우리가 LIGHT POSITION에서 상상의 카메라를 설정하고 우리가 light로부터 보이듯이 scene의 depth를 렌더링한다면, 우리가 얻는 것은 많은 거리를 포함하는 텍스쳐이다. 이 shadow map의 모든 픽셀은 world space에서 다시 매핑될 수 있고, 너에게 그 light가 무슨 픽셀을 보는지, 그 점이 light 그 자체로부터 얼마나 멀리있는지를 말해줄 것이다. 아래의 그림 2를 보아라. 첫 번째 P1은 카메라와 라이트의 뷰로부터 둘 다 보인다. 그래서 만약 우리가 그것을 렌더링한다면, 우리는 또한 그것의 월드 위치를 계산하고, 그러나서 빛으로부터 거리를 계산한다면, 우리는 shadow map에서 저장된 거리와 비교할 수 있다. 우리가 P1의 거리를 비교한다면, 우리는 우리가 계산한 값이 map에 포함된 같은 값이라는 것을 알 것이다 (map에서 우리가 depth를 저장하는 방식 떄문에 몇 가지 에러가 있다). 이것은 왜냐하면 그 픽셀이 light의 관점으로부터 보이기 떄문이다. ~~ (basic shadow mapping)

이게 shadow mapping을 구현하는데 필요한 모든 것이다. 샘플 코드를 봐보고, 그것을 가능한 명확하게 만들자.

1 나는 처음 부터 마지막까지 코드를 보여주려고 않을 것이지만, 대신에 다양한 기능을 고르고, 무엇을 하는지 설명할 것이다. 그래서 렌더링 함수로부터 시작해보자.

시작해서, 우리는 shadow map을 얻길 원한다. 그러므로, 우리는 카메라를 light 포지션에 위치시키고, 그것을 scene을 바라보도록 방향을 바꾼다. 우리는 또한 shadow mapping을 할 때 CCW에서 CW로 flip을 한다.  이것은 모든 오브젝트가 "closed"되는한 작동하는 좋은 트릭이다. CW로 바꾸는 것은 그 오브젝트를 "inside out으로 렌더링하는데, 이것은 우리가 거리를 비교하는데 back face를 사용할 것을 의미한다. 이것은 일반적으로 shadow mapping에 나타나는 shadow acne를 줄인다. 이것은 반드시 해야하는 것은 아니다. 그것은 대부분의 겨웅에 조금 도움이 된다, 그러나 다른 곳에서 악화된다.

우리는 그러고나서 모든 모델들을 shadow map에 렌더링한다. 우리는 그 shadow map이 한 순간에 어떻게 만들어지는지를 체크할 것이다. 왜냐하면 이제 우리가 알 필요가 있는 모든 것은 여기에서우리가 모든 모델들의 depth를 텍스쳐에 렌더링하는 것이기 때문이다. 일단 우리가 shadow map으로 하는게 끝나기만 하면, 우리는 그 행렬을 저장하고 모든 것을 reset한다.

우리는 어떤 local 변수들에서 행렬들을 저장하는데, 왜냐하면 우리가 그것들을 world에서 pixel들을 재 사영시키는데 쉐이더에게 보낼필요가 있기 떄문이다. 그리고 light clip/projection space로도 보내야 한다. 우리가 또한 그 renderer를 face를 CCW로 렌더링하도록 되돌려야 하는 것에 주목해라.

-------------------------------------------------------------------------------------------
뭐 이정도면 내가 생각해놓았던 파이프라인이 맞다고 생각한다.
나는 내 쉐이더에 DirLight가 여러개가 될 수 있도록 해놓았는데,
그에 따라서 해당 DirLight의 LightViewProjMatrix를 또 보관해야할 것이다.

한 벡터 방향에서 다른 벡터를 향하도록하는 쿼터니언 만들기 (번역)

http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors

이 글에서, 나는 널리 사용되는 공식의 유도 뒤에 있는 사고 프로세스를 설명하려고 한다: 그것은 두 개의 3D 벡터 사이의 회전을 나타내는 쿼터니언을 찾는 것이다. 정말 새로운 것은 없지만, 희망스럽게도, 몇 가지 아이디어들이 다른 때에 재사용될 수 있다.

Note : 여기에서 보여지는 과정은 목적에서 불완전하다. production code에서 사용될 수 있는 버전에 대해, 대신에 다음의 자료를 보아라, Quaternion from two vectors: the final version.

Naive method
회전은 회전축과 한 각을 사용하여 가장 잘 시각화된다. degenerate cases를 제외하고서, 그 회전축은 두 개의 원래 벡터들의 외적을 연산하여 얻어질 수 있다:

그러고나서, 그 각도는 외적 그리고/또는 내적의 성질들을 사용하여 얻어질 수 있다:

외적과 내적공식.. 외적은 cosTheta, 내적은 sinTheta

θ는 항상 0과 π사이이기 떄문에(저 기호가 뭔지모르겠다, 여튼 각도 제한이 있다), 우리는 오직 내적만을 신경쓴다. 이것은 우리에게 쿼터니언을 만드는 어떤 명백한 코드를 준다 (명확성을 위해 θ = 0인 corner cases를 생략하고):


quat quat::fromtwovectors(vec3 u, vec3 v)
{
    float cos_theta = dot(normalize(u), normalize(v));
    float angle = acos(cos_theta);
    vec3 w = normalize(cross(u,v));
    return quat::fromaxisangle(angle, w);
}

이것은 단순하지만, 작동한다. "quaternion from two vectors"에 대해 구글링 하는 것은 이 포럼 포스트이 질문을 보여주는데, 여기에서 변형 이론이 제안된다.

Looking under the hood
axis와 angle로부터 쿼터니언을 구성할 때 무슨 일이 발생하는지를 봐보자:



이것은 quat::fromaxisangle에 대한 코드가 어느정도 이렇게 보인다는 것을 의미한다:


quat quat::fromaxisangle(float angle, vec3 axis)
{
    float half_sin = sin(0.5f * angle);
    float half_cos = cos(0.5f * angle);
    return quat(half_cos, half_sin * axis.x,
                          half_sin * axis.y,
                          half_sin * axis.z);
}

Avoiding trigonometry
만약 너가 Inigo Quilze의 최근 자료 avoiding trigonometry를 읽었다면, 너는 아마도 우리가 cos(θ)로부터 θ를 연산하고, 그러고나서 sin(θ/2)와 cos(θ/2)를 연산했다는 사실에 눈살을 찌뿌렸을 것이다.

사실, 그것을 하는 좀 더 간단한 방법이 있다; precalculus의 반각공식이 우리에게 다음을 말해준다:




이것은 우리가 우리의 쿼터니언을 만드는 코드를 간단하게 만든다:


quat quat::fromtwovectors(vec3 u, vec3 v)
{
    float cos_theta = dot(normalize(u), normalize(v));
    float half_cos = sqrt(0.5f * (1.f + cos_theta));
    float half_sin = sqrt(0.5f * (1.f - cos_theta));
    vec3 w = normalize(cross(u,v));
    return quat(half_cos, half_sin * w.x,
                          half_sin * w.y,
                          half_sin * w.z);
}

이것은 꽤 좋다. 잘 알려진 삼각법 공식을 사용하여, 우리는모든 삼각기하 함수 호출을 제거했다!

Avoiding square roots
우리가 조금 더 잘할 수 있다는 것이 발생한다. 우리가 세 개의 벡터들을 표준화시킨다는 것에 주목해라: u, v 그리고 cross(u,v). 그것은 세 개의 square roots이다. 요지는, 우리가 이미 이 공식을 통해서 w의 norm을 안다는 것이다:



글고 우리는 또 다시 precalculus로부터 sin(θ)를 안다:



또한, sqrt(a) * sqrt(b) = sqrt(ab)라는 사실을 이용하여, 우리는 더 적은 square root를 수행한다.

그러므로 우리는 다음의 성능향상 개선을 만들어낼 수 있다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
     float norm_u_norm_v = glm::sqrt(glm::dot(u,u) * glm::dot(v, v));
     float cos_theta = glm::dot(u, v) / norm_u_norm_v;
     float half_cos = sqrt(0.5f * (1.f + cos_theta));
     float half_sin = sqrt(0.5f * (1.f - cos_theta));
     glm::vec3 w = cross(u, v) * (1.f / (norm_u_norm_v * 2.f * half_sin * half_cos);
    return quat(half_cos, half_sin * w.x,
                          half_sin * w.y,
                          half_sin * w.z);
}

오 잠깐! 우리는 w를 연산하기 위해 sin(θ/2)로 나누고{vec3 w를 계산할 때 }, 그러고나서 또 다시 sin(θ/2)를 곱한다{return quat할 때}. 이것은 우리가 그 변수가 필요없다는 것을 의미하고, 우리는 더 간단하게 할 수 있다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
     float norm_u_norm_v = glm::sqrt(glm::dot(u,u) * glm::dot(v, v));
     float cos_theta = glm::dot(u, v) / norm_u_norm_v;
     float half_cos = sqrt(0.5f * (1.f + cos_theta));
     glm::vec3 w = cross(u, v) * (1.f / (norm_u_norm_v * 2.f * half_cos);
    return quat(half_cos, w.x,
                          w.y,
                          w.z);
}

이것은 OgreVector3.h에서 Ogre3D engine에 의해서 사용되는 코드이다. 그것들이 최종 결과에 대해 부가적인 표준화 단계를 수행하는 것을 제외하고. 이것은 수학적으로 쓸모없지만, numerical 안정성 문제 때문에, 그럼에도 불구하고 그렇게 하는 것은 아마도 안전하다.

이 마지막 표준화 단계는 실제로 그 코드를 간결하게 하는 한 기회이다.

Improving on Ogre3D
우리는 두 개의 square roots와 4개의 나눗셈으로 내려왔고, 더해서 꽤 몇 개의 곱셈과 더하기가 있다. 우리가 작동시키는 플랫폼에 따라서, 더 간단하게 하고 성능을 개선시키는 것이 가능하다. 예를들어, 많은 SIMD 아키텍쳐에서, 쿼터니언을 표준화 시키는 것은 매우 빠를 수 있다.

이것은 만약 우리가 그 쿼터니언에 2.f * half_cos로 모든 컴포넌트에 곱한다면 얻는 코드이고, normalize()가 그 나머지 일을 하게 한다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
     float norm_u_norm_v = glm::sqrt(glm::dot(u,u) * glm::dot(v, v));
     float cos_theta = glm::dot(u, v) / norm_u_norm_v;
     float half_cos = sqrt(0.5f * (1.f + cos_theta));
     glm::vec3 w = cross(u, v) * (1.f / norm_u_norm_v);
    return normalize(quat(2 * half_cos * half_cos, w.x,
                          w.y,
                          w.z));
}

이제 half_cos는 오직그것의 제곱 형태로만 나타나고, 그것은 square root로부터 나오기 때문에, 우리는 간단히 그 square root를 제거할 수 있다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
     float norm_u_norm_v = glm::sqrt(glm::dot(u,u) * glm::dot(v, v));
     float cos_theta = glm::dot(u, v) / norm_u_norm_v;
     glm::vec3 w = cross(u, v) * (1.f / (norm_u_norm_v);
    return normalize(quat(1.f + cos_theta, 
                          w.x,
                          w.y,
                          w.z));
}
{
1.f + cos_theta는 아까 반각공식에서 제곱하고 * 2하면 저렇게 나와서 코드가 저렇게 된다.
}

그리고 같은 이유로, 우리는 모든 쿼터니언 컴포넌트를 norm_u_norm_v로 곱할 수 있다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
     float norm_u_norm_v = glm::sqrt(glm::dot(u,u) * glm::dot(v, v));
     glm::vec3 w = cross(u, v);
    return normalize(quat(norm_u_norm_v + dot(u, v), 
                          w.x,
                          w.y,
                          w.z));
}

우리는 여전히 두 개의 square roots와 4개의 나누기 연산을 하는데, 거기에서 어떤 것들은 normalize()에 숨겨져 있지만, 그 코드는 이제 상당히 더 짧다.

Final form
만약 u와 v가 단위 벡터라고 강요될 수 있다면, norm_u_norm_v는 생략되어질 수 있고, 간단히 1.0f로 대체될 수 있다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
    glm::vec3 w = cross(u, v);
    return normalize(quat(1.f + dot(u, v), 
                          w.x,
                          w.y,
                          w.z));
}

아름답지 않냐? 우리가 시작한 sin(), cos() 그리고 acos() 어지러운 것들을 고려하면?

이 알고리즘은 인터넷 전반에서 발견될 수 있지만, 나는 누가 처음에 이것을 만들어냈는지를 모른다. 또한, 많은 3D 엔진들 (publicly하게 이용가능하고 다소 private하기도 한) 은 그것으로부터 이득을 얻는다.

Update(06/01/2014)
아래의 코멘트에서, Michael Norel은 non-unit 버전에 대해 다음의 개선을 제안한다.
d = dot(u, v)와 w = cross(u, v)의 값들이 어쨋든 계산되기 때문에, sqlength(u) * sqlength(v)의 값은 다른 방식으로 연산될 수 있다. 즉, d * d + sqlength(w). 다음의 코드는 적어도 세 번의 곱을 덜 한다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
    glm::vec3 w = cross(u, v);
    glm::quat q = glm::quat(glm::dot(u,v), w.x, w.y, w.z);
    q.w += length(q);
    return normalize(q);    
}

또한, Marc B. Reynolds는 unit version에서, 최종 표준화 factor는 sqrt((1 + dot(u, v))^2 + sqlength(cross(u,v)))이고, 이것은 sqrt(2 +2dot(u,v))로 줄어든다, sin^2+ cos^2 항등식 떄문에. 그것은 다음의 가급적 개선된 버전으로 이끈다:


glm::quat fromtwovectors(const glm::vec3 &u, const glm::vec3& v)
{
    float m = sqrt(2.f + 2.f * dot(u,v));
    glm::vec3 w = (1.f / m) * glm::cross(u,v);
    return glm::quat(0.5f * m, w.x, w.y, w.z);
}

Final Version
나는 방향들 중의 하나에서 다른 것으로 transform시킬 두 개의 임의의 방향 벡터에서 쿼터니언을 구성하는 첫 번째 글을 보였다. 그 자료는 신중히 두 벡터가 서로를 마주보는 특별 케이스를 빠뜨렸다. 그래서 나는 어떻게 orthogonal vector를 빠르게 고르는지를 설명하는 두 번째 article을 썼었다.

두 글로부터의 결과를 합치는 것은 간단할 것이다. 여기에 그 결과인 최종 함수가 있다:


/* Build a unit quaternion representing the rotation
 * from u to v. The input vectors need not be normalised. */
quat quat::fromtwovectors(vec3 u, vec3 v)
{
    float norm_u_norm_v = sqrt(dot(u, u) * dot(v, v));
    float real_part = norm_u_norm_v + dot(u, v);
    vec3 w;

    if (real_part < 1.e-6f * norm_u_norm_v)
    {
        /* If u and v are exactly opposite, rotate 180 degrees
         * around an arbitrary orthogonal axis. Axis normalisation
         * can happen later, when we normalise the quaternion. */
        real_part = 0.0f;
        w = abs(u.x) > abs(u.z) ? vec3(-u.y, u.x, 0.f)
                                : vec3(0.f, -u.z, u.y);
    }
    else
    {
        /* Otherwise, build quaternion the standard way. */
        w = cross(u, v);
    }

    return normalize(quat(real_part, w.x, w.y, w.z));
}

----------------------------------------------------------------------------------------------
두 번째 Article 바로 번역

On picking an orthogonal vector (and combing coconuts)
다음의 문제를 고려해라: 3D 공간에서 non-zero vector v가 주어지고, v에 orthogonal한 벡터 w를 찾아라 (즉, dot(v, w) = 0인).

처음 보기에, 이것은 쉬운 것처럼 보인다. 모든 가능한 v에 대해, 한 평면에 놓여있는 무한한 orthogonal vectors들이 있다:

우리는 하나를 고를 필요가 있다. 그러나, 그것은 보기와는 다르게 사소하지 않다.

First attempts






2018년 12월 3일 월요일

glTexImage2D Deferred Rendering에서 사용시 주의할 점.

현재 Deferred Rendering을 만드는 중인데,

쉐이더에서 뭔가 요구하는 대로 안되어서 여러가지 명세서를 읽다가 이제 알아챘다.

First Pass에서 G-buffer를 입력하고, Second Pass에서
Lighting Calculation을 계산하는 쉐이더에서

다음과 같은 코드가 있었다.


if(MyBool.a == 1.0)
{
    // Light Map Material Calculation
}
else
{
    // Color Material Calculation
}

이렇게 했을 때, 오브젝트가 그려지지 않은 background pixel이 light map material calculation할 때, directional light의 색깔을 따라 갔다.

위의 코드에는 두 개의 문제점이 있는데 일단

가장 첫 번째는 floating point value의 comparison을 수행할 때 단순히 저렇게 하는 것은 precision으로 인해 올바른 비교가 되지 않을 가능성이 높다.

만약 다음의 세 가지 코드를 각각 사용했을 때


glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);

세 번째 파라미터는 internal format인데, shader에서 쓰이는 값의 format을 말한다.
첫 번째 것은 값들을 0 ~ 1로 clamp 시켜버린다.
두 번째 것은 값들을 어떤 범위 제한 없이 사용할 수 있다. 다만 floating point value가 16 bit precisino에서 표현된다.
세 번째 것은 값들을 어떤 범위 제한 없이 사용할 수 있다. 다만 floating point value가 32bit precision에서 표현된다.

첫 번째 파라미터의 RGBA는 unsigned normalized 형식으로 바꾼다고 한다.
두 번째 파라미터는 그대로 쓰는데 16bit precision으로 표현된다, 그리고 명세서에 의하면 8번째 파라미터가 gl_float인데, 값들이 gl_half_float로 내부적으로 바뀐다고 한다.
세 번째 파라미터는 그대로 쓴느데 32bit precision으로 표현된다.

그래서, 어쨋든 이 bit precision에 주의해서 비교를해야 한다.
만약 내가 MyBool.a 값을 0.5, 0.25, 0.125, 0.0625 이런식으로 2진수로 표현하기에 딱 떨어지는 수로 한다면, == 을 썼을 때 내가 원하는대로 작동할 확률이 높다.

GL_RGBA32F는 거의 대부분 == 에 대해서 성공하고
GL_RGBA16F는 저기 위에서 언급한 2진수로 딱 떨어지는 수가 좋다.
GL_RGBA도 0 ~ 1 사이의 2진수로 딱 떨어지는 수로 하여야 한다.

두 번째
두 번째는 내가 명세서를 찾아도 거의 나오지 않는 것들인데, first pass에서 g-buffer에 오브젝트를 그리지 않은 곳에서 MyBool을 texture 함수를 통해 값을 얻으려할 때,

(0, 0, 0, 1)

의 값이 들어가게 된다. 그래서 if (MyBool.a == 1) 일 때 background pixel value가 directional lights에 따라서 바뀌게 된 것이다.

그래서, 이걸 주의해서 MyBool 값을 설정해주면 문제가 없어진다.

왜 저 값이 있는지에 대한 것은

https://stackoverflow.com/questions/34497195/difference-between-format-and-internalformat#comment56736641_34497470

이 스택오버플로우 대화에서 볼 수 있는데,

https://gamedev.stackexchange.com/questions/66613/blank-texture-in-frame-buffer-object-in-deferred-rendering
https://www.opengl.org/discussion_boards/showthread.php/185819-How-to-render-few-objects-with-texture-and-other-without-%28Simple-deferred-shading%29
이 내용도 더 봐보자.

=================================================
알아버렸다. glClearColor(r, g, b, a) 시에
해당 framebuffer에 연결된 color buffer들은 해당 rgba 값으로 변경된다.

그래서 위에서 오브젝트가 그려지지 않은 곳의 텍스쳐 값은 모두 다 0, 0, 0, 1인 이유가
glClearColor(0, 0, 0, 1)로 했기 때문이다. 이로서 고민 해결 끝.

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glClearColor.xhtml
명세에서도, color buffer를 해당 값으로 clear하므로, deferred rendering에서
오브젝트가 그려지지 않는 background pixel을 참조할 시에 glClearColor 값들이
들어오게 될 것이다.






glTexImage2D 설명

https://stackoverflow.com/questions/34497195/difference-between-format-and-internalformat#comment56736641_34497470

여기에서 두 번째 답변이 좀 더 이해가 잘 된다. 그걸 번역하도록 하겠다.

internal format은 텍스쳐가 GPU에서 어떻게 저장 될지를 설명한다.
format은 client memory에서 너의 pixel data가 어떻게 저장될지를 설명한다 (type parameter와 함께)

internal format이 data type뿐만 아니라 채널의 개수 (1에서 4) 둘 다 명시한다는 것에 주목해라, 반면에 클라이언트 메모리에서 pixel data에 대해, 둘 다 두 개의 별개의 파라미터로 명시된다.

GL은 너의 pixel data를 internal format으로 변경할 것이다. 만약 너가 효율적인 texture uploads를 원한다면, 너는 어떠한 변환이 필요하지 않은 matching formats을 사용해야만 한다. 그러나 대부분의 GPU들이 텍스쳐 데이터를 BGRA 순서로 저장한다는 것을 인지해라. 이것은 여전히 internal format GL_RGBA에 의해 표현된다 - 그 internal format은 채널이ㅡ 개수와 data type을 설명하기만 하고, 그 internal layout은 전적으로 GPU-specific이다. 그러나, 그것은 클라이언트 메모리에서 너의 픽셀 데이터 포맷으로 GL_BGRA를 사용하는 것이 최대 성능을 위해 추천된다.

첫 번째 internalFormat은 GL에게 그것이 텍스쳐를 선호되는 precision에서 (채널 당 8비트) 표준화되는 정수를 저장해야 한다고 말한다. 두 번째 format은 GL에게 너가 RGBA 순서로 픽셀 당 4개의 채널을 제공한다는 것을 말한다.

예를들어 너는 그 데이터를 3개 채널의 RGB로 공급할 수 있고, GL은 자동적으로 이것을 RGBA로 확장할 수 있다 (A를 1로 설정하여), 만약 그 internal format이 RGBA라면. 너는 또한 오직 Red 채널만을 지원할 수 있다.

반대로, 만약 너가 GL_RED를 internalFormat으로 사용한다면, 그 GL은 GBA채널을 너의 입력 데이터에서 무시할 것읻자.

또한 data types가 변경될 것이라는 것에 주목해라. 만약 너가 채널당 32 bit float를 가진 pixel RGB를 제공한다면, 너는 GL_FLOAT를 사용할 수 있다. 그러나, 너가 여전히 GL_RGBA internal format을 사용할 때, 그 GL은 이러한 것을 채널 당 8비트를 가진 표준화된 정수로 변환할 것이다. 그래서 그 추가 precision은 잃어진다. 만약 너가 GL이 부동 소수점 정확도를 사용하길 원한다면, 너는 또한 GL_RGBA32F 같은 floating point texture format을 사용해야만 한다.

_INTEGER formats은 표준화 되지 않은 integer textures이다. GL에서 정수 텍스쳐에 대한 어떠한 자동 변환은 없다. 너는 integer internal format을 사용해야만 하고, 그리고 너는 너의 pixel data를 어떤 _INTEGER format으로 명시해야만 한다. 그렇지 않으면 그것은 에러를 발생시킬 것이다.

2018년 11월 29일 목요일

Chapter 5 Visual Appearance (RTR)

Chapter 5 Visual Appearance
"좋은 사진은 선한 행위(? deed)와 같다 - Vincent Van Gogh

너가 3차원 모델의 이미지를 렌더링할 때, 그 모델들은 적절한 기하 도형을 가져야할 뿐만 아니라, 그들은 또한 바람직한 시각적 외관을 가져야 한다. 많은 경우에 (그러나 항상은 아니다 - Chapter 11을 보아라) 그 목적은 photorealism 이다 - 이것은 실제 오브젝트들의 사진과 매우 가까운 외관이다. 이 목적에 도달하기 위해, 우리의 guide로서 현실을 사용하는 것은 가치가 있다. 이 챕터는 처음에 light와 materials가 실 세계에서 행동하는 방식을 이야기 한다. 간단한 lighting과 surface model은 그러한 모델들이 프로그램 가능한 쉐이더들을 사용하여 어떻게 구현될 수 있는지에 대한 예제로서 사용된다.

챕터의 나머지는 렌더링된 모델들에 현실적인 외관을 주기위해 사용될 수 있는 부가적인 기법들을 소개한다. 이러한 것은 transparency(투명도), antialiasing, 그리고 compositing을 포함한다.

5.1 Visual Phenomena
그림 5.1에서와 같은 scene의 현실적인 렌더링을 수행할 때, 관련된 물리 현상을 이해하는 것이 도움이 된다. 이러한 것들은:

  • 빛은 태양 또는 다른 자원들 (자연적이거나 인공적인 것)들에 의해 방출된다.
  • 빛은 scene에서 오브젝트들과 상호작용한다; 일부는 흡수되고, 일부는 흩뿌려지고, 새로운 방향으로 전파된다.
  • 마지막으로, 빛은 sensor (인간의 눈, 전자 센서, 또는 필름)에 의해 흡수된다.
그림 5.1에서, 우리는 모든 세계의 현상들의 증거를 볼 수 있다. Light는 lamp로부터 발현되고, 방에 있는 오브젝트들에게 전파된다. 그 오브젝트 surfaces들은 어떤 것을 흡수하고, 어떤 것을 새로운 방향으로 흩뿌린다. 흡수되지 않은 그 빛은 계속해서 환경을 통해 이동하고, 다른 오브젝트들을 만나게 된다. scene을 통해 이동하는 빛의 아주 작은 부분은 이미지를 캡쳐하기위해 사용되는 sensor에 들어간다. 이 경우에 디지털 카메라의 전자 센서이다.

다음 섹션들에서, 우리는 이러한 현상들을 이야기할 것이고, 그것들이 렌더링 파이프라인과 이전 챕터의 GPU 쉐이더들을 사용하여 어떻게 그려질 수 있는지를 이야기할 것이다.

5.2 Light Sources
빛은 다양하게 기하학적 rays, electromagnetic waves, or photons (몇 가지 wave properties가 있는 quantum particles)로 모델링된다. 그것이 어떻게 다뤄지는 것과 상관없이, light는 electromagnetic radiant energy이다 - 공간을 통해 이동하는 electromagnetic energy 이다. Light sources는 빛을 흩뿌리거나 흡수하기보다는 light를 방출(emit)한다.

광원들은 렌더링 목적을 위해 많은 다른 방식들로 표현될 수 있다. 여기에서 우리는 간단한 light model을 이야기할 것이다 - 좀더 복잡하고 표현력있는  모델들은 Section 7.4와 챕터 8에서 이야기 된다. 태양처럼 극히 먼 거리의 광원들은 재현하기에 가장 간단하다; 그것들의 빛은 scene 전체에 같은 단일 방향으로 이동한다. 이 이유 때문에, 그것들은 directional lights라고 불려진다. 렌더링 목적을 위해, 그러한 광원의 방향성은 light vector l로 묘사되고, world space에서 명시된다. 그 벡터 l은 항상 길이가 1이라고 가정되어야 한다, 이 책에서 만나질 때 마다. directional lights에 대해, l은 일반적으로 shading 동안 다시 표준화할 필요가 있는 것을 피하기 위해 프로그램에 의해 표준화되어진다 (1로 스케일되어진다). 그 light vector l은 보통 빛이 이동하는 방향과 반대 방향을 가리키도록 정의된다 (see Figure 5.2) 이것 뒤에 있는 이유는 곧 명확해 질 것이다.

그 빛의 방향 외에도, 그것이 방출하는 illumination의 양은 명시될 필요가 있다. light를 측정하는 과학, 즉 radiometry(복사량, 방사측정) 은 Section 7.1에서 이야기 될 것이다; 이 챕터는 오직 관련된 개념들만을 보여줄 것이다. directional light source의 emission은 l에 수직인 unit area surface를 통과하는 power를 측정하여 수량화될 수 있다 (see Figure 5.3). irradiance(방사조도, 복사조도)라고 불려지는 이 양은  1초에 표면을 통과하는 광자(photons)의 에너지들의 합과 같다. (만약 빛을 파장으로 다룬다면, irradiance는 (측정 평면에서) 제곱된 wave amplitude (파장 진폭)과 비례한다. ) Light는 색칠해질 수 있고, 그래서 우리는 irradiance를 세 개의 숫자를 포함하는 RGB 벡터로 나타낼 것이다: 빨간색, 초록색, 그리고 파란색의 각각에 대해 하나씩 (Section 7.3에서, 색에 대한 주제가 좀 더 엄격히 다뤄질 것이다). painting 프로그램에 있는 색과 다르게, RGB irradiance 값들은 1을 초과할 수 있고, 이론적으로 임의로 크게될 수 있다는 것에 주목해라.

비록 하나의 주요한 광원이 있을지라도, 그 오브젝트들과 방의 벽에 튕기는 light는 그 오브젝트를 또한 비출 것이다. surrounding or environmental light의 이러한 유형은 ambient light라고 불려진다.  Chapters 8과 9에서, 우리는 ambient light를 모델링하는 다양한 방법들을 볼 것이다. 그러나 간단하게 해서, 우리는 이 챕터에서 그것들 중 어떤 것도 이야기 하지 않을 것이다. 우리의 간단한 lighting model에서, 직접적으로 비춰지지 않는 표면들은 검정색이다.

비록 l과 수직인 한 평면에서 irradiance를 측정하는 것이 우리에게 그 빛이 일반적으로 얼마나 밝을지를 말해줄지라도, 한 표면에서 그것의 illumination을 연산하기 위해, 우리는 그 표면에 평행한 평면 (즉, surface normal n에 수직)에서 irradiance를 측정할 필요가 있다. 그 surface irradiance는 l과 수직으로 측정되는 irradiance와 동일하고, ln 사이의 θ_i 각의 코사인과 곱해진다. (그림 5.4의 왼쪽을 보아라).

그림 5.4에서 중앙과 오른쪽 그림은 이 코사인 factor의 기하학적 해석을 보여준다. 중앙에서, 그 광선등른 그 표면에 수직으로 부딪히는 것으로 보여진다. 그 irradiance는 광선의 밀도와 비례하고, 그것들 사이의 거리 d에 반비례한다. 이 경우에, 표면에서 irradiance와 l에 수직인 irradiance는 같다.  오른쪽에서, 광선은 다른 방향에서 들어오는 것으로 보여진다. 그것들은 평면의 normal과 각 θ_i를 만든다. 그것들이 표면에 부딪히는 곳에서 광선 사이의 거리는 d / cosθ_i 이다. 그 irradiance가 이 거리에 반비례하기 때문에, 그것이 cosθ_i에 비례해야만 하는 것을 의미한다. 이 코사인은 두 벡터의 내적을 취해서 쉐이더에서 쉽게 연산될 수 있다 (두 ln이 항상 길이가 1이라는 것이 가정되어야 한다). 여기에서 우리는 light vector l이 빛이 이동하는 방향과 반대로 정의해야 하는 것이 왜 편리한지를 알게 된다; 그렇지 않다면, 우리는 내적을 취하기전에 우리는 그것을 negate해야만 한다.

E는 irradiance를 위한 방정식에서 사용된다, 대개 n에 수직인 irradiance에 대해. 우리는 l에 수직인 irradiance에 대해 E_L을 사용할 것이다. 음의 코사인 값은 빛이 그 표면 아래에서 들어오는 상황과 일치한다는 것에 주목해라. 이 경우에 그 빛은 그 표면을 전혀 비추지 않는다. 그래서 우리는 음이 아닌 값으로 clamped된 코사인을 사용할 것이다 (bar(cos)로 표현된):


그림 5.3과 5.4