[GPU프로그래밍] 7. Using Textures
◾ 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 value
h
로 나타난 gray scale image
- 모델이 얼마나 울퉁불퉁한지에 대한
h
값을 저장한 이미지
- viewer 입장에서 바라봤을 때, 원래라면 P의 컬러가 보이겠지만, Height map을 사용한다면 Q의 컬러가 보여야한다
- Q의 컬러를 알기 위해서는 bump mapping한 뒤 Q점의 texture coordinate를 알아야하므로 ∆x(위 그림의 빨간선)를 추정해야함
◾ Approximation ∆x
- 닮음으로 구하기-> 하지만.. view dir와 bump surface가 만나는 지점 Q를 계산할 수 없어서 불가능
- Q에서의 d를 구할 수 없으니까, P에서의 d로 대신하자!
- 가정: smooth surface (no high frequency variation)
-> 1−hp와 1−hq의 크기가 큰 차이가 없을 것이다
- 점 P(1−hq)대신 점 P(1−hp)을 사용하여 height
d
를 결정
- texture coordinate 점 P에 (∆x,∆y)를 더해 새로운 점 P'를 얻는다
- S: scale factor (크기를 [0, 0.05] 범위로 작게 맞춰줌 -> 효과가 드라마틱하진 않음)
- (∆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
(내려간 지점의 깊이보다1−hq < 내려간 거리 k/n가 크면 collsion)
- Collsion detection을 하지만 아주 정확한 값은 아니니까.. scale factor
S
존재
◾ Self shadowing
- 위와 같이 충돌을 감지할 수 있으면, 같은 방법으로 self shadowing도 가능
- collsion 위치
Q
에서 광원을 향해 가다가 collsion이 감지 -> Q
가 가려져서 그림자가 진다
- 가려진 부분은 ambient light만 주어서 렌더링
findOffset()
- 몇개의 간격으로 나눌지
nSteps
는 view dir의 각도(기울기)에 따라 결정 (기울기가 작을수록 큰 n)
- 뷰 방향으로의 ∆x을 계산하고, Collsion detect가 될 때까지 기존 texcoord에서 ∆x를 빼며 offsetting
- collsion 위치의 texcoord를 반환 -> offsetting된 위치의 컬러Tex와 노멀Tex의 값을 가져온다
- isOccluded
- 광원 방향으로의 ∆x을 계산하고, collision detect가 되거나 h범위를 벗어날 때까지
findOffset()
에서 구한 collsion 위치의 texcoord에 ∆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)
- 각도 ai는 알고있으므로, 위의 식을 사용해 굴절된 각도 at를 구해야함
- vertex shader에서 굴절 index의 비율 n2/n1를 구하고, 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)
- FBO bind
- 텍스처 렌더링
- FBO unbind (defaulf framebuffer로 돌아가기)
- 텍스처를 사용해서 씬 렌더링
- 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, ...)를 사용해서 복잡한 조명을 표현
- 이미지를 reflection을 위한 광원으로 사용 (여기선 diffuse만 고려)
◾ Diffuse convolution map
- environment map으로부터 계산하여 만든 맵으로, diffuse reflection 계산시 텍스처 정보로 사용
- 소에 적용된 은은한 갈색 톤...
◾ Reflectance equation
- Diffuse convolution map를 계산하기..~
- Reflectance equation의 BRDF에서 diffuse만 고려
- f(l,v)=fd+fs인데, fd만 고려
- fd=cdiff/π이고 상수값이므로 아래와 같이 식 정리(diffuse는 카메라 방향에 상관 없기 때문에 위 식의 모든 term은 미리 계산될 수 있다. 빠른 렌더링!)
- 광원 Li을 environment map으로 사용
- normal 기준 반구로 들어오는 모든 빛을 고려하기 위해 반구 범위로 적분하는데, 이는 Monte Carlo estimator로 근사 가능
- Monte Carlo estimator
- random sampling으로 적분
- 적은 sample들로 비슷하게 만들 수 있도록 하는 효율적인 Sampling 방법
- Integral 대신 sigma
-> random sample들의 합으로 적분값을 근사
- cdiff를 제외하고 나머지 식을 미리 계산하여 diffuse convolution map으로 저장
(cdiff는 물질마다 다른 값이니까)
◾ 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에 따라 값을 읽어오고, Cdiff와 곱하여 컬러를 계산