[GPU프로그래밍] 10. Using Geometry Shaders

jungizz_·2024년 6월 3일
0

GPU Programming

목록 보기
10/15
post-thumbnail

Geometry

  • primitive
  • line, dot, polygon 등 그려지는 작은 단위
    -> 이것을 추가하거나, 수정하거나, 제거할 수 있는 쉐이더가 geometry shader

◾ Geometry shader

  • 각 primitive마다 한번 씩 실행
    • ex) 삼각형 2개로 그리는 쿼드 -> geometry shader 2번, vertex shader 6번, fragmetn shader는 해상도에 따라 실행됨)

Input/Output

  • Primitive마다 실행되기 때문에, primitive의 정보(vertex의 위치, 컬러, 텍스처좌표 등)을 사용할 수 있다
  • Input
    • 위 정보가 배열형태로 들어옴
  • Output
    • zero: output이 없으면 원래 있던 primitive를 삭제하는 것
    • one: 유지
    • more: 추가
  • input과 output의 primitive type은 다를 수 있다
    • ex) input은 점인데 output은 polygon
    • 하지만 output은 한가지 primitive type만 가능

Functionality

  • 그려지지 않는 부분을 지워주는 culling
  • geometry를 추가해서 모양 늘리기
  • geometry를 바꾸진 않지만, 그 정보로 무언가 계산해내기 (ex-primitive에 속한 모든 vertex를 사용해 삼각형의 중심을 계산)
  • input과 다른 형태의 geometry로 primitive 만들기
  • 등등..

How to use

  • Primitive를 생성하고 vertex를 보내기 위해 built-in function EmitVertexEndPrimitive 사용 (-> primitive 생성)
    • EmitVertex: gs를 사용해서 생성하고자하는 primitive의 vertex 지정
    • EndPrimitive: EmitVertex된 vertex들을 연결하여 primitive 생성
  • EndPrimitive를 작성하지 않아도 gs가 끝날 때 자동으로 마무리되긴 한다 (그래도 명시적으로 사용하는게 좋음)
  • EmitVertex를 아예 호출하지 않으면, input primitive는 삭제됨 (렌더링되지 않음)
👀 아래부터는 Geometry shader를 사용하는 예제 3가지

1. Point Sprites

  • 쿼드를 사용해서 sprite 만들기
  • 카메라를 바라보는 sprite -> texture가 항상 카메라 방향에 수직이 되도록 해야함
  • 점을 그리고, gs에서 그 점을 기준으로 쿼드를 그린다
    • 쿼드 하나 당 한 vertex(point)만 있으므로 GPU로 보내는 데이터 양이 훨씬 줄어듦
  1. OpenGL Application에서 점 만들기
    • GL_POINTS를 사용해 원하는 vertex 위치에 점(single-point primitives)를 만듦
    • 보통 픽셀 크기로 그려짐
    • 회전이나 모양 변환이 어려워서 이것만드로 sprite를 그리는데 한계가 있음
  1. gs에서 점을 기준으로 쿼드 그리기
    • input으로 point P를 받음
    • camera coordinate 기준 -> 카메라 방향에 수직이 되는 쿼드를 계산하기 편함 (점 P와 A, B, C, D가 같은 평면)
    • gs가 EmitVertex를 할 때 필요한 texture coordinate를 자동으로 계산해줌
      -> 여러 스프라이트에 동일하게 적용해줄 수 있음
    • 계산에 따라 쿼드가 아닌 다른 모양도 그릴 수 있고, 복잡한 모양인 경우 텍스쳐의 alpha값을 사용하여 배경을 투명하게 해주는 방법도 있다

📃 Code

OpenGL Application

  • point들의 정보가 담긴 vertex buffer 필요
    • normal, texture coord 정보는 필요 없음 (gs에서 계산함)
    • vertex의 연결관계도 필요 없어서 index buffer같은 것도 필요X

Vertex shader

  • 쿼드의 중심이 될 point들의 좌표를 input으로 받고, CC까지만 변환

Geometry shader

  • CC기준 point를 받아 vertex 4개를 생성한다
  • 각 vertex의 위치와 texture coordinate를 계산하고 EmitVertex (총 4번 진행)
  • 이때 vertex의 위치는 projection까지 진행한 후 fragment로 넘겨준다

Fragment shader

  • gs에서 계산된 texture coordinate에 맞춰 texture mapping

2. Drawing a Wireframe on Top of Shaded Mesh

  • mesh를 쉐이딩하고, mesh마다 edge를 그려줌
    • gs를 사용하지 않으면 모델을 두 번 렌더링하여 그려줘야하는데(depth test때문에 한 모델은 좀 더 크게 렌더링), gs를 사용하면 1pass로 그릴 수 있음
  • primitive의 정보를 계산하기 위해 gs를 사용 (형태 변화x)
  • 각 fragment가 edge랑 가까우면 edge color, 멀면 shading color를 적용하는 방식으로 적용
    • 각 fragment의 edge까지의 거리를 알아야하고 이는 gs에서 계산할 수 있음

✔️ fragment에서 edge까지의 거리 구하기

  1. 삼각함수 식을 사용해 한 vertex에서 반대 edge까지의 거리를 구함
    • 이 과정은 한 삼각형 안에서 얻는 값으로, 3개의 vertex가 필요하므로 gs에서 계산해야함
  2. EmitVertex를 할 때, edge-distance vector를 보냄
  3. fs에서 삼각형에 속하는 특정 fragment가 삼각형의 세 버텍스를 iterpolation한 값을 가지고, 특정 fragment에서의 각 edge까지의 거리를 알 수 있음
    • 이때, 가장 가까운 edge와의 거리로 해당 fragment가 edge에 속하는지를 판별하여 컬러를 결정한다
  • Edge 굵기가 같아보이게 하기 위해 screen space에서 계산
    -> vertex 좌표를 screen space로 변환한 뒤 계산해야함
  • fragment에서의 edge까지 가장 짧은 거리가 line width보다 작으면 line color와 섞음
    • edge부분에선 자연스럽기 위해 smoothstep 기능으로 fade (antialiasing)

📃 Code

Vertex Shader

  • gs에서 geometry가 바뀌는게 아니므로, 그냥 평소처럼 계산하여 fs로 넘겨준다 생각하면 됨

Geometry Shader

  • screen space로 변환한 버텍스 위치값으로 edge까지의 거리를 구하고, edge-distance vector를 만들어 Emit

Fragment Shader

  • gs에서 받은 interpolation된 edge-distance vector의 x, y, z 성분 중 가장 짧은 값을 찾음
  • 그 값이 edge와 가까우면 line color와 mix해서 자연스럽게 그려줌 (smoothstep)

❗issues

  • vertex shader에서 projection된 좌표를 geometry에서 사용할 때, w로 나누어서 3차원 좌표로 나타내는 과정에서 생길 수 있는 문제
  • w가 아주 작으면(버텍스가 카메라에 아주 가깝다면), w로 나눈 값이 매우 커지거나 무한대로 변할 수 있다
    • 3D상에서는 크지 않았는데 projection하며 무한히 커질 수 있음
    • (절두체 view volume에 projection matrix를 곱하면 정사각형 형태의 view volume)
  • 삼각형의 버텍스 중 하나라도 이러한 문제를 겪으면(삼각형이 커서), 삼각형 전체가 왜곡될 수 있음

3. Drawing Silhouette Lines

  • 실루엣을 작고 얇은 쿼드로 그리기

✔️ silhouette edge 찾고 그리기

  • 서로 바라보는 방향이 다른 두 삼각형이 만나는 line이 실루엣 엣지
  • 카메라 방향이 z축의 방향이므로, cc기준 normal이 z가 양수면 front-facing, 음수면 not front-facing
    • normal의 z좌표만 계산하면 된다 (ABC는 버텍스, xy는 좌표)
  • 실루엣 엣지에 카메라와 수직인 쿼드를 추가
  • 삼각형의 바라보는 방향을 gs에서 계산하고 실루엣 엣지 판별을 하는데, 이 때 다른 primitive 정보도 알아야함 (주변 점)

    Adjacency Modes

    • 주변 primitive에 접근하여 추가적인 버텍스 정보 얻기
      • 메시에 포함되지 않는 주변 버텍스도 받아올 수 있음
      • GLLINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY, GL_TRIANGLES_ADJACENCY, GL_TRIANGLE_STRIP_ADJACENCY
  • GL_TRIANGLES_ADJACENCY mode를 사용해 한 삼각형 주변의 3개 버텍스 추가 정보를 알아온다 (한 Primitive에 총 6개의 버텍스 정보를 가져옴)
    • 0, 2, 4는 실제 삼각형을 구성하는 버텍스
    • 1, 3, 5는 주변 삼각형을 구성하는 버텍스
  • 한 삼각형을 기준으로, 주변 삼각형의 normal등을 계산할 수 있다!
  • 넘어온 6개의 버텍스에 자동으로 index가 붙는 것은 아니여서, opengl application에서 index array에 adjacency 정보도 추가해야함

📃 Code

OpenGL Application

  • adjacency 정보가 추가되도록 mesh data(index array) 세팅
  • 실루엣 엣지의 크기 및 컬러 설정

vertex shader

  • 쉐이딩 및 노말 계산을 위한 cc기준 Vnormal과 Vposition
  • 모델 렌더링을 위한 projection까지 마친 gl-Position

geometry shader

  • cc기준 normal의 z좌표를 계산하여 facing 판별 isFrontFacing()
  • 원래 삼각형이 front-facing일 때를 기준으로(back일 땐 엣지 그릴 필요도 없으니까), 주변 삼각형이 not front-facing인 경우 쿼드 생성!
    • 쿼드를 생성할 때, uniform 변수로 넘어온 쿼드 길이 및 두께 정보를 사용해 4개의 버텍스를 emit해준다 emitEdgeQuad()
    • 또한, counter clock wise 방향을 잘 지켜서 쿼드가 모델 바깥쪽으로 생길 수 있도록 한다
  • 실루엣 쿼드인지 기존 삼각형인지 구분을 해줘야지 fs에서 color 결정을 할 수 있기 때문에 primitive 구별 정보 GisEdge를 내보내준다
    • edge그리는 함수에서 true가 되고, edge 다 내보낸 뒤 기존 삼각형을 내보내기 전에 false로 만들어준다

fragment shader

  • GisEdge의 값에 따라 컬러 결정

❗issues

  • edge위에 쿼드를 올릴 때, edge의 곡률이 클수록 많이 벌어지고 틈이 생긴다
    • 곡률 정보를 반영하기는 복잡해서 보통 edge 길이를 늘려서PctExtend 틈을 채우는데, 너무 늘리면 쿼드가 삐져나온다
  • Depth testing으로 인해 실루엣 쿼드가 가려질 수 있다 -> 끊어지는 실루엣
profile
( •̀ .̫ •́ )✧

0개의 댓글