vec4 normalColorInProjection = texture2D(normalTexture, uv);
vec4 normalDirectionInWorldZeroPos = (normalColorInProjection*2.0)-1.0;
/*normalDirectionWorldZeroPos 는 해당 노말의 포지션은 전혀 제외한
월드 공간에서의 법선 방향임
이는 오직 방향만 가지고 있으며 원점 정보를 가지고 있지 않음.
*/
normalDirectionInWorldZeroPos = vec4(normalize(vec3(normalDirectionInWorldZeroPos)),0.0);
vec4 worldNormal = normalDirectionInWorldZeroPos;
vec4 normalDirectionInView = viewMatrix * worldNormal;
normalDirectionInView = vec4(normalize(vec3(normalDirectionInView)),0.0);
먼저 노말 텍스처를 가져와서 현재 픽셀의 법선 노말을 가져오고 해당 노말을 뷰 공간으로 이동한다. 방향 벡터이기 때문에 w는 0.0으로 계산하였고, 결과적으로 뷰 공간에서 방향만을 나타내는 법선 벡터이다.
표면의 오리지널 정점 구하기
vec4 originPixelDepthColorInProjection = texture2D(depthTexture, uv);
float originPixelDepthInProjection = originPixelDepthColorInProjection.r;
vec4 originPixelInProjection = vec4(uv.x*2.0-1.0, uv.y*2.0-1.0, originPixelDepthInProjection, 1.0);
vec4 originPositionInView = reverseProjection * originPixelInProjection;
originPositionInView = originPositionInView / originPositionInView.w;
현재 픽셀의 깊이 정보를 읽어와서 2D로 투영된 정점 정보를 3D 정점 정보로 역변환 하는 과정을 거친다.
이 때 월드 3D 좌표계가아니라, 카메라의 뷰 좌표계 기준으로 옮긴다. 카메라의 좌표가 0,0,0이 되는 것이다.
카메라 - 표면까지의 방향 벡터 구하기
vec4 cameraToOriginPositionInView = originPositionInView;
cameraToOriginPositionInView = vec4(normalize(vec3(cameraToOriginPositionInView)),0.0
카메라 좌표계로 옮긴 뒤에 카메라에서 해당 정점을 향하는 방향+크기 벡터를 구하고, 노말라이즈하여 단위 벡터로 변환한다.
즉 여기까지 카메라 - 표면까지의 방향 벡터, 표면의 법선 벡터를 픽셀 쉐이더에서 구하게 된 것이다.
반사 벡터 구하기
vec4 reflectedVectorInView = reflect(cameraToOriginPositionInView, normalDirectionInView);
reflectedVectorInView = vec4(normalize(vec3(reflectedVectorInView)),0.0);
카메라 - 표면까지의 방향 벡터와 표면 법선 벡터를 통해 반사 벡터를 구하고, 해당 반사 벡터를 단위화 한다.
Ray Marching 과정을 통해 충돌 확인 후 샘플링 시작
int sampleCount = 100;
vec4 addedColor = vec4(0.0,0.0,0.0,0.0);
for(int i=1;i<sampleCount;i++){
vec4 originPositionAddVectorInView = originPositionInView + reflectedVectorInView * float(i) * 0.07;
originPositionAddVectorInView.w = 1.0;
먼저 샘플 개수만큼 반복을 수행하면서 표면의 정점 위치에서부터 구한 반사 벡터로 일정 길이 (0.07)씩 나아간다. 즉 한번의 반복에서 0.07 길이만큼 반사 벡터로 표면에서 나아간다. 이렇게 나아간 것은 정점이기 때문에 w는 1.0으로 해 주었다.
계산된 반사 스텝 지점에 대한 2D 투영
vec4 rayDestinationPixelPosition = projectionMatrix * originPositionAddVectorInView;
rayDestinationPixelPosition = rayDestinationPixelPosition/rayDestinationPixelPosition.w;
vec2 rayUVPosition = vec2((rayDestinationPixelPosition+1.0)/2.0);
그리고 위 스텝으로 나아간 지점에 대해서 2D 좌표로 투영하여 UV 텍스처 좌표로 가져온다.
2D 투영된 좌표에 대해서 UV 가 초과 감지
if(rayUVPosition.x<=0.0 || rayUVPosition.y<=0.0)
break;
if(rayUVPosition.x>=1.0 || rayUVPosition.y>=1.0)
break;
.... 생략
반사 벡터의 경우 오직 스크린 좌표계 안에서 처리되기 때문에 반사된 벡터를 투영했을 때 UV 좌표를 벗어나는 (-0.1~, 또는 1.1~) 범위가 나올 수 있다. 이런 벡터는 자칫 잘못 계산했다가 화면의 아래나 상단이 렌더링되는 경우가 있기 때문에 (기본 설정에서 UV 좌표는 벗어나면 다시 리피트되기 때문이다.) 이를 제외해주는 것이다.
깊이 버퍼 가져오기
float rayProjectedObjectDepthValue = texture(withoutMirrorTexture,rayUVPosition).z;
float rayDepthValue = rayDestinationPixelPosition.z;
반사된 지점에서 오브젝트의 깊이와 반사 벡터의 Z 깊이를 가져온다. 오브젝트 깊이는 이미 그려진 오브젝트의 깊이이고, 반사 벡터의 Z 깊이는 스텝으로 진행된 지점의 Z 버퍼이다. 두 버퍼를 가져온 이유는 정점이 표면을 충돌한지 확인하기 위해서 이다. 안타깝게도 SSR 는 화면 영역에서 반사를 하는 것이기 때문에 반사 벡터가 오브젝트를 실제로 충돌했는지 확인하는 것이 아니라, 깊이가 겹쳐지거나 일정 오차 범위 이하로 줄어들면 충돌했다고 감지하는 것이다. z buffer가 이동한 라인이 z buffer 경계를 충돌하거나, z 가 일정 오차 이하에 있거나, 버퍼 뒤에 있는 경우 충돌로 감지할 수 있다. 하지만 각각의 장단점이 존재하는 것 같고, 오차가 심하기도 해서 나름 다른 방법을 고민 중에 있다. 여기서는 abs 값으로 하여 오차 미만인 경우에 반사되도록 하였다.
충돌 지점 파악
if(rayProjectedObjectDepthValue!=0.0)
if(abs(rayProjectedObjectDepthValue-rayDepthValue)<=0.002)
{
addedColor+= pickedColor;
calculatedCount+=1;
break;
}
}
abs(rayProjectedObjectDepthValue-rayDepthValue)는 현재 반사 벡터의 스텝 만큼 이동한 정점 z 깊이와 해당 지점에 그려진 오브젝트의 z 깊이 차가 0.002 이하인 거의 겹치는 경우에 해당 지점의 색상을 가져와 반사 시작 지점의 색상으로 사용하는 것을 의미한다. 충돌하지 않은 경우 sampleCount 만큼 반복하여 스텝을 이동하는 것이다. 끝 지점에 도달하면 break로 나오고 아닌 경우 계속 반복하고 sampleCount 만큼 이동하여도 표면을 발견하지 못한 경우 반사할 사항이 없는 것으로 판단한다.
색상 융합
vec4 renderColor = texture2D(uTexture, uv);
vec4 stencilColor = texture2D(stencilTexture, uv);
gl_FragColor = renderColor;
if(stencilColor.r>=1.0){
if(calculatedCount!=0){
addedColor = addedColor / float(calculatedCount);
gl_FragColor = (gl_FragColor*0.1 + addedColor*0.9);
}else{
gl_FragColor = gl_FragColor;
}
}
마지막으로 원본 렌더 컬러를 가져오고 스텐실 버퍼는 거울 표면에만 1.0으로 했기 때문에, 거울 표면에 해당하는 경우 해당 지점에 추가적으로 계산된 반사 벡터가 존재하는 경우 gl_FragColor 에 추가적으로 섞어준다.