[GPU프로그래밍] 7. Using Textures

jungizz_·2024년 4월 17일
0

GPU Programming

목록 보기
7/15
post-thumbnail

◾ Textures in OpenGL

  • 텍스처에 컬러 정보 말고도 depth, shading parameters, displacement map, normal vectors, ... 등등 다양한 버텍스 데이터를 저장할 수 있다
    -> 텍스처를 데이터 저장소(데이터 버퍼)로 활용
  • Immutable(불변) storage textures
    • OpenGL 4.2버전부터
    • 메모리 할당을 고정 (메모리를 잡고 크기, 형식, 이미지 등 콘텐츠 자체는 변경 가능)
    • 메모리를 일관성있게 사용해서 체크?를 피할 수 있다
    • 속도, 편리함 등의 이점
    • 아래 코드는 과거 방식

◾ Applying texture

◾ single 2D texture

  • 텍스처 메모리에 접근하여 컬러로 사용할건데, 앞에서 계산한 컬러랑 텍스처 컬러를 어떻게 합칠것인가

◾ OpenGL Application에서 텍스처 로딩 및 선언

  • 이미지 파일을 불러오기
  • Texture ID를 생성하고, ID로 바인딩
  • 데이터를 버퍼에 저장하여 CPU의 텍스처 데이터를 GPU로 보냄
  • 텍스처를 텍스처좌표 [0, 1]에 매핑할 때, texture filtering 필요
    • GL-NEAREST: 가운데 텍스처 좌표에 가장 자까운 픽셀 선택
    • GL-LINEAR: 텍스처 좌표의 이웃한 텍셀에서 보간된 값
  • CPU의 texture data는 더이상 필요 없어서 메모리 할당 해제

◾ OpenGL Application에서 렌더링 과정 중 텍스처를 쉐이더로 넘겨주기

  • 오브젝트를 그릴 때, 여러 종류의 텍스처를 사용하는 경우 텍스처를 저장하는 공간도 여러개
    • 텍스처마다 texture unit/channel 설정을 설정해야함
  • Texture의 n번 unit 활성화한 뒤, 위의 텍스처 로드 함수를 통해 받은 Texture ID로 사용할 texture를 바인딩
  • Texture의 n번 unit을 Uniform변수로 쉐이더에 넘겨준다
👀 texture의 unit 하나가 shader code의 쉐이더 유니폼 변수 하나에 연결

◾ Shader code에서 텍스처 사용

  • vertex shader에서는 값만 넘겨주고
  • fragment shader에서 blinn-phong계산 및 텍스처 컬러 사용
    • GLSL에서 텍스처를 사용하기 위해 Texture sampler Object 필요
      -> shader 코드 안에 Uniform sampler 변수 선언하여 OpenGL application으로부터 해당 Unit의 텍스처를 받아온다
    • texture함수로 텍스처 오브젝트에서 원하는 texCoord의 텍스처 값을 읽어오기
  • 텍스처 넘겨줄 때, Uniform변수 대신 shader code에서 아래와 같이 layout qualifier를 작성하여 텍스처를 받을 수도 있음

◾ multiple textures

  • 여러개의 텍스처 유닛 사용
  • 알파값을 사용해 두개의 텍스처를 섞기

◾ Alpha map to discard pixels

  • 텍스처의 알파가 0인 부분만 fragments discard
  • 뚫리면 뒤가 보이니까.. 앞면 뒷면 판별해서 노멀 뒤집기 해줘야됨

◾ Normal mapping

  • Geometry 표면의 실제 normal이 아닌, 다른 normal로 faking하여 계산하는 기술
    • normal map texture에 저장된 값으로 normal vector를 수정
  • 버텍스 개수가 적어도 모델의 디테일 표현 가능 (간단한 모델링 후 normal mapping으로 디테일 추가)
  • 하지만 실루엣은 매끈한 형태 그대로 나타난다는 한계 존재

◾ Normal map

  • color대신 normal vector로 해석될 수 있는 데이터가 저장된 텍스처
  • Normal map을 저장하는 인코딩 단계에서 normal map의 XYZ coord [-1, 1] 정보를 RGB [0, 1] 범위로 변환
    • 저장된 Normal map을 읽어올 때는 반대로 [0, 1] -> [-1, 1]

◾ Tangent space

  • Tangent space: 한 표면 위에 한 점을 원점을 가지고, 그때의 normal을 z축(0, 0, 1)으로 가지는 좌표계
    • x축은 tangent(접선), y축은 bitangent(normal과 tangent의 외적)
      -> 한 점에는 여러개의 tangent coord를 가짐
    • 각 점마다 좌표계가 다른 것! (각자의 tangent space를 가짐)
  • Object coord 안에서 local하게 있는 coord로 object local coordinate system이라고도 함
  • normal이 object space가 아닌, tangent space에 있다고 가정할 때, normal map은 tangent space의 vector들로 해석할 수 있다
  • normal map의 normal vector를 tangent space를 기준으로 저장

◾ Tangent space의 장점

  • normal map의 normal vector는 기존 노멀을 약간 변화한 정도이므로, tangent coord에서 실제 노멀(z축)을 기준으로 어느 방향으로 변화하면 되는지를 떠올릴 수 있음
    • 노멀을 변화하고 싶을 때, object coord라면 복잡할 수 있음!
  • object coord와 독립적
    • object가 transform됐을 때, normal에도 같은 transform해줄 필요 없이, normal map을 보고 기존 normal의 약간 변화만 다시 해주면 됨 -> normal map에는 상대적인 정도가 저장되므로 재사용 측면에서 굳!
    • WC나 CC에서 모델에 non-uniform scaling이 발생했을 때, 노멀map의 값도 변환시키기 위해 무거운 행렬을 계산해야한지만, tangent space에서는 노멀map의 값을 변환시키기 위해 모델의 실제 표면의 노멀을 기준으로 가벼운 transform만 적용시키면 된다. 즉, non-uniform scaling에 independent해진다.
    • 수정 없이 reflection model에 normal map값을 사용할 수 있음 (아래)

◾ Reflection model

  • 기존에는 refelction model(ex-blinnphong) 계산할 때, CC기준 normal을 사용함 -> tangent space에서 해보자~
  • vertex shader에서 CC의 vector들을 tangent space로 변환

◾ Tangent Vectors

  • 오브젝트 데이터에 포함되어있는 경우도 있음
  • 없으면 직접 계산
    • 다양한 방법이 있지만 공통적으로 고려하는 것은:
    • 각 점마다 여러개의 tangent를 가지지만, 주변에 있는 점과의 tangent가 너무 많이 다르면 문제가 생길 수 있어서, 최대한 부드럽게 변화되도록 해야한다
      (문제점: 이를 바탕으로 데이터가 normal map에 저장되는데, 텍스처 filtering 과정에서 이상하게 계산될 수 있음 -> 근처에 있는게 비슷할텐데.. 다른 값이 보이면.. filtering입장에서 이상하게 느낄수도)
  • vertex shader에서는 binormal계산과 cc에서 tangetn space로의 좌표 변환
  • fragment shader에서는 [0,1]범위로 바꾼 normal texture의 값을 사용해 blinn-phong 계산

◾ Parallax mapping

  • Normal map은 viewer의 위치 변화에 따른 parallax effect와 self-occlusion을 제공하지 않음
    • Parallax effect: 가까이 있으면 많이 움직이고, 멀리 있으면 적게 움직이는 현상
    • self-occlusion: 튀어나오는 부분으로 인해 가려지는 부분까지 표현
  • height map에 기반한 texture coordinates의 변화를 활용하여 Parallax effect와 self-occlusion를 구현

◾ Height map (= Bump map)

  • 각 texel의 표면 height를 나타내는 이미지로, texel마다 single scalar valueh로 나타난 gray scale image
  • 모델이 얼마나 울퉁불퉁한지에 대한 h값을 저장한 이미지
  • viewer 입장에서 바라봤을 때, 원래라면 P의 컬러가 보이겠지만, Height map을 사용한다면 Q의 컬러가 보여야한다
    • Q의 컬러를 알기 위해서는 bump mapping한 뒤 Q점의 texture coordinate를 알아야하므로 x∆x(위 그림의 빨간선)를 추정해야함

◾ Approximation x∆x

  • 닮음으로 구하기-> 하지만.. view dir와 bump surface가 만나는 지점 Q를 계산할 수 없어서 불가능
  • Q에서의 d를 구할 수 없으니까, P에서의 d로 대신하자!
    • 가정: smooth surface (no high frequency variation)
      -> 1hp1-h_p1hq1-h_q의 크기가 큰 차이가 없을 것이다
    • P(1hq)P(1-h_q)대신 점 P(1hp)P(1-h_p)을 사용하여 height d를 결정
  • texture coordinate 점 P에 (x,y)(∆x, ∆y)를 더해 새로운 점 P'를 얻는다
    • S: scale factor (크기를 [0, 0.05] 범위로 작게 맞춰줌 -> 효과가 드라마틱하진 않음)
    • (x,y)(∆x, ∆y)는 위에서 닮음으로 계산한 것
  • 새로운 점 P' 위치의 texture coordinate에 해당하는 텍스처 노멀/컬러값 사용!
👀 normal map은 RGB채널만 쓰기 때문에, A채널에 height map도 넣어주면 메모리 절약 가능

◾ Steep Parallax mapping

  • Q를 직접 계산하여 확실히 하기
    • h 0~1범위를 n개의 간격으로 나누어서, view direction을 따라 1/n씩 내려간다
    • 1/n씩 내려갔으므로 내려간 거리를 알 수 있고, 내려간 지점의 height와 비교하여 collsion detection
      (내려간 지점의 깊이보다1hq1-h_q < 내려간 거리 k/nk/n가 크면 collsion)
    • Collsion detection을 하지만 아주 정확한 값은 아니니까.. scale factorS 존재

◾ Self shadowing

  • 위와 같이 충돌을 감지할 수 있으면, 같은 방법으로 self shadowing도 가능
  • collsion 위치Q에서 광원을 향해 가다가 collsion이 감지 -> Q가 가려져서 그림자가 진다
  • 가려진 부분은 ambient light만 주어서 렌더링
  • findOffset()
    • 몇개의 간격으로 나눌지nSteps는 view dir의 각도(기울기)에 따라 결정 (기울기가 작을수록 큰 n)
    • 뷰 방향으로의 x∆x을 계산하고, Collsion detect가 될 때까지 기존 texcoord에서 x∆x를 빼며 offsetting
    • collsion 위치의 texcoord를 반환 -> offsetting된 위치의 컬러Tex와 노멀Tex의 값을 가져온다
  • isOccluded
    • 광원 방향으로의 x∆x을 계산하고, collision detect가 되거나 h범위를 벗어날 때까지 findOffset()에서 구한 collsion 위치의 texcoord에 x∆x를 더한다
      (기존 collsion 위치의 height가 정확하진 않으므로, 약간 올린 뒤 계산함)
    • collsion이 없어 h범위를 벗어나면 false, collsion이 있어 h범위를 벗어나지 않으면 true -> true일 때 그림자!
    • 그림자가 지는 구역에는 diff와 spec을 계산하지 않고 ambient만 사용해서 색을 결정한다

◾ Simulating Reflection with cube maps

  • Environment map: 주변 환경을 나타내는 텍스쳐로, 오브젝트 표면으로 매핑됨

◾ Cube map

  • environment가 큐브면으로 projection된 6개의 이미지들
    • 큐브의 한 가운데 viewer가 있다고 생각할 때의 6 view
    • 만들기 쉽고 렌더링이 간편하다!!
  • 3차원 이므로 texture coordinate는 (s, t, r)이고, 큐브맵 한 면의 위치를 나타낸다
  • cubemap이 오브젝트로 매핑될 때의 texture coord는 반사벡터가 닿는 부분
  • OpenGL application에서 Cubemap texture 로드
    • OpenGL에서 cubemap texture를 자체 지원
    • 1개의 cubemap texture만 먼저 로드하여 알아낸 width/height로 storage를 잡고, 나머지 5개의 texture도 로드
    • cubemap의 각 texture가 연결될 때 경계가 자연스럽도록 filtering
  • vertex shader에서 reflect함수로 반사벡터 계산
    • WC에서 계산
      • WC에서 sample된 값이 cubemap에 있기 때문에, WC에서 normal과 reflection을 계산해야 의미있는 cubmap 컬러가 물체에 매핑된다
      • CC에서 계산하면 처음 cam의 위치에서 계산된 값으로 고정된다 (그 후에 물체가 움직이거나 카메라가 움직여도 계속 같은 상이 매핑된다)
    • 원래는 position과 normal에 곱해지는 변환 행렬이 다른데, rotation만 있다고 가정하는 경우는 같아도 괜찮다.. ?
  • fragment shader에서는 반사된 방향에 있는 texel값을 사용해 cubemap컬러를 가져오고, material컬러와 mix하여 최종 컬러를 계산
    • reflectFactor가 작으면 material컬러가 많이 섞이고, 크면 반사가 잘 되니까 cubemap컬러(environment)가 많이 섞임

◾ Issues

  • 오브젝트는 한 환경맵만 반사할 수 있어서(텍스처값만 갖고오는거라), 다른 물체의 반사가 고려되지 않는다 (반사되지 않아 상이 맺히지 않는다)
  • 물체를 이동해도 같은게 반사되어보인다 (같은데 매핑)
    • 배경이 엄청 멀리 있다고 가정해야한다

◾ Simulating Refraction

  • cube map에서의 굴절
  • 투명한 물체는 입사되는 빛의 방향을 굴절시킴
    • n은 물체에 따라 정해지는 값 (refraction index)
    • 각도 aia_i는 알고있으므로, 위의 식을 사용해 굴절된 각도 ata_t를 구해야함
  • vertex shader에서 굴절 index의 비율 n2/n1n_2/n_1를 구하고, built-in function refract를 사용하여 굴절된 빛의 방향을 계산
  • fragment shader에서 반사 컬러와 굴절 컬러를 반사도를 기준으로 mix해서 계산

Issuse

  • 물체를 보는 각도에 따라 반사/굴절률을 고려해야함
    • 근데 이게 Linear하지 않고, 물질에 따라 다른 성질도 아니여서 반사/굴절을 섞을 때 고려해야함
    • ex) 멀리서 호수 표면을 바라보면 빛 반짝임 (n과 v가 90도에 가까워 nDotv가 1에 가까우면 하얀색으로 반사), 가까이서 호수 표면을 수직으로 바라보면 물 속 보임 (굴절 많이)
  • 각 색상마다 굴절률이 다르지만 고려X
    • R, G, B별로 다른 Eta값을 준다면 고려 가능
  • 실제로는 빛이 투명한 오브젝트를 들어갈 때, 나올 때 총 2번 굴절되지만, 1번 굴절만 구현됨

◾ Projected texture

  • 물체의 색상이 projection 텍스처에 의해 결정
    -> texture coord 계산 필요
  • projector를 카메라 위치로 생각
    • WC의 projector에 view matrix V 를 곱해준다
    • View frustum을 [-1, 1]범위를 가지는 cubuc volume으로 변환하는 perspective projection matrix P도 곱해줌
    • [-1, 1] 큐브를 [0, 1]의 tex coord범위로 바꿔주는 추가 mat까지 곱함 (view frustum의 범위가 [0, 1]이 되도록 re-scaling and translating)
  • projector에서 ray를 쏠 때, 대응하는 texel값을 fragment의 컬러 값으로 사용(초록색)
    • view frustum의 범위가 [0, 1]이므로, fragment에 대응되는 tex coord를 찾을 수 있는 것!
  • OpenGL Application에서 texture filtering 및 matrix 생성
  • vertex shader에서는 위의 mat를 사용하여 projection을 위한 texCoord를 계산
  • fragment shader에서는 위의 texCoord를 사용하여 texture mapping

◾ Frmaebuffer objects (FBOs)

  • Rendering to a texture
  • framebuffer가 아닌 텍스처에 바로 렌더링하고, 그 텍스처를 다음 렌더링에 적용 (multipass rendering)
    • 1pass: pipeline 한번 통과
  1. FBO bind
  2. 텍스처 렌더링
  3. FBO unbind (defaulf framebuffer로 돌아가기)
  4. 텍스처를 사용해서 씬 렌더링
  • OpenGL Application에서 FBO 세팅
    • FBO 생성 및 바인딩
    • Texture Object와 depth butter 생성 및 Storage할당 (데이터는 없는 상태)
    • FBO에 texture object 및 depth buffer 연결
      • tex object는 glFramebufferTexture2D, depth buf는 glFramebufferRenderbuffer
    • fragment shader의 output을 세팅
    • FBO의 세팅 끝나면 unbind (defaulf framebuffer로 돌아가기.. 그냥 기본으로 해두는게 안꼬임)
  • 이어 렌더링 과정에서, 두번의 Pass 진행
    • 1st Pass: 위에서 세팅한 FBO를 바인딩해서 그림이 그려질 버퍼를 세팅하고, 소의 texture를 사용해 소 렌더링 -> 그 결과가 FBO의 texture storage(unit0)에 저장
    • 2nd Pass: 기본 프레임버퍼를 바인딩해서 그림이 그려질 버퍼(화면)를 세팅하고, FBO에 저장된 텍스처(unit0)를 사용해 큐브 렌더링

◾ Sampler Objects

  • Texture Object 세팅 과정 중 parameter 설정 (filtering)
  • 한 texture data를 가지고 다른 filtering을 적용하고 싶은 경우
    -> 따로따로 texture object를 만들 필요 없이, Sampler Object 사용
  • sampling parameter와 texture object를 분리할 수 있는 오브젝트
  • sampler object에 한번만 parameter 세팅을 하면, 다양한 texture obejct에 적용할 수 있음
    • texture/sampler object를 따로 세팅한 다음, 필요한 애들끼리 binding
    • ex) texture object 1개, sampler object 2개
    • 또는, 여러개의 텍스처에 동일한 parameter 세팅을 적용하고 싶은 경우에도 굳~
  • OpenGL application에서 세팅

◾ Diffuse image-based lighting

◾ Image-based lighting

  • real world에서는 다양한 빛이 존재한다 (only point light가 아님)
  • image(cube map, equirectangular panoramic map, ...)를 사용해서 복잡한 조명을 표현
    • high dynamic range image
  • 이미지를 reflection을 위한 광원으로 사용 (여기선 diffuse만 고려)

◾ Diffuse convolution map

  • environment map으로부터 계산하여 만든 맵으로, diffuse reflection 계산시 텍스처 정보로 사용
  • 소에 적용된 은은한 갈색 톤...

◾ Reflectance equation

  • Diffuse convolution map를 계산하기..~
  • Reflectance equation의 BRDF에서 diffuse만 고려
    • f(l,v)=fd+fsf(l, v)=f_d+f_s인데, fdf_d만 고려
    • fd=cdiff/πf_d=c_{diff}/π이고 상수값이므로 아래와 같이 식 정리(diffuse는 카메라 방향에 상관 없기 때문에 위 식의 모든 term은 미리 계산될 수 있다. 빠른 렌더링!)
    • 광원 LiL_i을 environment map으로 사용
    • normal 기준 반구로 들어오는 모든 빛을 고려하기 위해 반구 범위로 적분하는데, 이는 Monte Carlo estimator로 근사 가능
  • Monte Carlo estimator
    • random sampling으로 적분
    • 적은 sample들로 비슷하게 만들 수 있도록 하는 효율적인 Sampling 방법
    • Integral 대신 sigma
      -> random sample들의 합으로 적분값을 근사
    • cdiffc_{diff}를 제외하고 나머지 식을 미리 계산하여 diffuse convolution map으로 저장
      (cdiffc_{diff}는 물질마다 다른 값이니까)

◾ pseudocode (렌더링 전 offline에서의 random sampling과정)

❗diffuse convolution map의 모든 texel값을 정해주는 과정
  • 각 texel에 대해서 n이 정해질 때,
    • (WC에서) n을 중심으로한 반구의 random sample로 random direction li를 구하고,
    • li 위치의 environment map 값을 읽와서 texel값L을 얻는다
  • 이걸 nSamples번 반복하여 여러개의 L의 합
  • Reflectance equation을 계산하여 n방향에 대한 값을 texel에 저장
👀 environment map의 한 texel은 특정한 n방향에 대한 값이 저장된 것

◾ rendering

  • vertex shader에서는 필요한 값들을 WC로 변환
  • fragment shader에서는 Diffuse convolution map을 texture로 받아와서 normal에 따라 값을 읽어오고, CdiffC_{diff}와 곱하여 컬러를 계산
profile
( •̀ .̫ •́ )✧

0개의 댓글