Doyub Kim

Computer graphics, software development, web,
photography, and graphic design




Main     Research     Photography     Blog     About




Computer Graphics

Triple Shadows and Fake Reflections: Future Graphics by doyub 2009/11/14
SIGGRAPH Asia 2009 : Technical Papers Preview Trailer by doyub 2009/11/05
GLSL Ray Marching by doyub 2009/02/05



Pages

1  



Triple Shadows and Fake Reflections:
Future Graphics

16:18 06 November 2009

ns_logo





From NewScientist.com

Now into its second year, the ACM Siggraph Asia conference brings together computer graphics professionals and researchers to showcase the latest developments in graphics. Here are our picks of the research...

2009/11/14 13:01 2009/11/14 13:01


No Trackback |  No Comment

Comments

Have your say







SIGGRAPH Asia 2009 :
Technical Papers Preview Trailer




2009/11/05 11:29 2009/11/05 11:29


No Trackback |  No Comment

Comments

Have your say







GLSL Ray Marching


Ray casting example

볼륨 데이터를 GLSL로 렌더링 한 결과.


MRI나 CT같은 의학용 어플리케이션을 비롯하여 전산유체역학, 문화재 및 자원탐사 등, 3차원 볼륨 데이터의 가시화는 그 활용범위가 매우 높다고 할 수 있겠습니다. 제가 주로 연구하는 유체 시뮬레이션의 경우도 연기나 불과 같은 기체의 가시화를 위해서는 볼륨 렌더링은 필수적입니다. 수요가 많은 만큼 다양한 가시화 기법들이 존재하는데, 이번에 소개할 ray marching은 볼륨 렌더링을 위해 널리 쓰이는 방법들 중 하나입니다. 사실, ray marching은 상당한 연산량을 요구하게 되는데, 다행히 최근 급격히 발전한 GPU의 힘을 빌어 실시간에 구현하는 것이 가능해졌습니다.

이 글은 OpenGL과 Shading language인 GLSL을 사용하여 volume rendering 기법인 ray marching을 구헌하는 방법을 소개합니다. 사실 어려운 작업은 전혀 아니지만, 기본 코드가 있으면 좋겠다는 생각으로 Peter Trier의 블로그에 소개된 OpenGL + Cg 기반의 예제를 살짝 컨버팅해보았습니다. 굳이 GLSL을 사용한 이유는 일단 제 컴퓨터가 ATI 그래픽카드를 사용하고 있었고-.- (지금은 nVidia로 바꿨습니다만..) GLSL 관련 기사들이 그리 많지 않은 것 같아서 나름 도움이 되고자 선택하였습니다. 본 글은 OpenGL 및 GLSL에 대한 기초적인 지식을 알고 있다는 가정하에 쓰여졌습니다. 또한 본 글을 이해하기 앞서, Peter Prier의 글을 꼭 읽어보시길 권합니다. GLSL이 낯설다면 Lighthouse 3D의 튜토리얼을 참고하시기 바랍니다.

Peter Prier의 로그를 읽으셨다면 아시겠지만, ray marching을 구현하기 위해 필요한 정보는 크게 세 가지입니다.

  • Ray가 시작하는 좌표와 방향 계산을 위한 front-face 각 점마다의 local-coordinate.
  • Ray의 방향 계산을 위한 back-face의 local-coordinate.
  • 볼륨 데이터
첫 번째는 다양한 방법으로 구할 수 있는데, 저는 front-face의 3차원 텍스쳐 좌표를 사용했습니다. 두 번째는 본격적으로 ray marching을 시작하기 전에 미리 따로 렌더링 해 놓아야 하는 부분인데, 이는 OpenGL의 FBO를 활용하여 텍스쳐로 저장해놓고 있으면 됩니다. 그리고 쉐이더에서 ray marching을 시작할 때, 미리 저장해놓은 텍스쳐에서 렌더링 된 결과를 읽어오면 됩니다. 물론 back-face를 렌더링 할 때는 텍스쳐 좌표를 컬러값으로 그려줌으로써 local-coordinate을 저장할 수 있습니다. 세 번째의 볼륨 데이터는 매우 간단합니다. GL_TEXTURE_3D 형태로 저장해놓고 쉐이더에서 읽기만 하면 됩니다.

이제 Peter Trier의 코드와 다른 점을 설명하겠습니다. 위에서 설명한 대로라면, ray marching 쉐이더에서는 두 개의 텍스쳐를 입력으로 받아야 합니다. 하나는 back-face를 렌더링한 결과이며, 나머지 하나는 볼륨 데이터입니다. 쉐이더에서 이를 동시에 인식하기 위해서는 단순히 텍스쳐를 바인딩해서는 안됩니다. Cg에는 텍스쳐를 매우 간단히 쉐이더에 넘겨줄 수 있는 함수가 있지만, GLSL의 경우는 아래와 같이 멀티-텍스쳐를 활용하여 쉐이더에 전달해야 합니다.

glActiveTexture( GL_TEXTURE0 );
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, backFaceBuffer);

glActiveTexture( GL_TEXTURE1 );
glEnable(GL_TEXTURE_3D);
glBindTexture(GL_TEXTURE_3D, volumeTexture);
glUseProgram(program);

uniform1f( "stepSize", stepSize );
uniform1f( "viewWidth", WINDOW_WIDTH );
uniform1f( "viewHeight", WINDOW_HEIGHT );
uniform1i( "backFace", 0 );
uniform1i( "volumeData", 1 );

쉐이더에 uniform변수를 전달해주는 함수인 uniform1f와 uniform1i는 다음과 같이 구현했습니다.

void uniform1f( const char* name, float value ) {
GLint location;
location = glGetUniformLocation( program, name );
glUniform1f( location, value );
}

void uniform1i( const char* name, int value ) {
GLint location;
location = glGetUniformLocation( program, name );
glUniform1i( location, value );
}

물론, 쉐이더를 통한 ray marching을 마치면 위에서 바인딩한 텍스쳐를 각각의 텍스쳐 아이디에 맞게 해제시켜야 하겠죠.

glActiveTexture( GL_TEXTURE1 );
glDisable(GL_TEXTURE_3D);
glActiveTexture( GL_TEXTURE0 );
glDisable(GL_TEXTURE_2D);

이제는 쉐이더를 어떻게 구현해야 하는지 알아보겠습니다. Peter Trier의 Cg코드를 기반으로 GLSL로 컨버팅을 하는 것은 크게 어려운 일이 아니며, 사실 거의 고칠 부분은 없습니다. 차이가 있다면 앞서 보여준 멀티텍스쳐를 받아오는 부분일 것입니다. 우선 멀티텍스쳐를 사용하기 위해, 그리고 텍스쳐 좌표계를 읽어들이기 위해 vertex shader는 다음과 같이 간단히 구현 가능합니다.

void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_FrontColor = gl_Color;
gl_Position = ftransform();
}

Fragment shader는 좀 긴데, ray의 시작점을 알아내는 부분은 다음과 같습니다.

vec4 start = gl_TexCoord[1];

시작점을 알아내기 위해 gl_TexCoord[1]를 사용했다는 것을 알 수 있습니다. 볼륨 데이터를 감싸는 육면체의 local 좌표를 텍스쳐 좌표로 매핑했기 때문입니다. Ray의 방향을 계산하기 위해서 back-face의 렌더링 결과를 읽는 작업도 필요한데, 그 부분만 떼어서 보면 다음과 같습니다.

vec2 pixelCoord = gl_FragCoord.st;
pixelCoord.x /= viewWidth;
pixelCoord.y /= viewHeight;

vec4 backPosition = texture2D(backFace,pixelCoord);

그리 깔끔한 구현은 아닌 것이, FBO의 타겟으로 GL_TEXTURE_2D를 사용하였기 때문에 현재 ray marching을 하고 싶은 픽셀 좌표(gl_FragCoord.st)를 [0,1]x[0,1]사이의 텍스쳐 좌표로 변환하는 과정이 보입니다. Normalize되지 않은 좌표를 사용하는 GL_RECTANGLE을 사용한다면 위의 과정은 필요 없습니다. 시점과 종점, 그리고 방향을 알았다면 드디어 ray marching을 시작합니다.

for(int i = 0; i < 450; i++) {
colorSample = texture3D(volumeData,samplePos);
alphaSample = colorSample.a * stepSize;
colAcc += (1.0 - alphaAcc) * colorSample * alphaSample * 3.0;
alphaAcc += alphaSample;
samplePos += deltaDir;
lengthAcc += stepSize;

if ( lengthAcc >= len || alphaAcc > 1.0 )
break;
}

위의 코드에서 사용한 렌더링 공식은 물리 기반의 공식이 아니며, 이 마저도 구현에 따라 많이 차이가 납니다. 특히 알파 블렌딩을 하는 부분이나 색상을 누적시키는 부분은 널리 쓰이는 공식이랑 아주 조금 차이가 있습니다. 볼륨 렌더링 공식에 대한 좀 더 자세한 설명은 GPU Gems를 참고하시면 될 것 같습니다.

참 쉽죠?-_-/ 이렇게 작성된 코드는

glsl-ray-marching-src.zip

에서 다운 받으실 수 있습니다. 기본적으로 Peter Trier의 코드와 크게 다르진 않습니다. 대신, GLSL을 사용하기 위해 위에서 설명한 부분 외에도 몇 가지 추가된 코드가 있을 것입니다.

설명을 위한 매우 간단한 코드이지만 혹시 문제점이 발견되거나 궁금한 점이 있으면 답글 달아주세요. 제가 테스트해본 바로는 Mac OS X나 Windows에서 문제 없이 잘 돌아갔습니다. 그래픽카드는 nVidia GeForce 8800 및 9600에서 테스트해보았습니다.


References

2009/02/05 22:36 2009/02/05 22:36


No Trackback |  No Comment

Comments

Have your say




Pages

1  


Copyright ⓒ 2010 Doyub Kim. All rights reserved.
Powered by Textcube, Blueprint CSS Framework 0.8 and script.aculo.us 1.8.2

Entries RSS | Comments RSS