Programmable shader Hanyang University Jungsik Park
Objective • Introduction • GLSL language definition • Data types • Qualifiers • Operators • Constructor and component selector • Flow control and function • Vertex shader • Fragment shader • Example • Simple shader • Phong shading shader
INTRODUCTION
Conventional OpenGL rendering pipeline • 파이프라인의 각 박스는 고정된 기능만을 수행
Conventional OpenGL rendering pipeline • OpenGL에서 지원하는 모든 옵션과 상태 변수를 검사해서 적용 여부를 판단해야 하므로 저사양 하드웨어에서는 비효율적(ex 모바일 플랫폼) • Modified Phong illumination model만을 지원하는 고정된 조명 계산 • 물리적인 현실성은 떨어지지만, 계산이 빠르고 대체로 적절한 이미지 생성 • Gouraud shading만을 지원하는 고정된 음영 처리 • 버텍스 색상을 계산한 후, 버텍스 색상을 보간하여 픽셀 색상을 결정 • 마하 밴드가 나타나거나 픽셀값이 잘못 계산될 수 있다.
Extending OpenGL • 그래픽 하드웨어의 발전에 따라 복잡한 그래픽 기법을 적용하기 위한 기능의 지원 필요 • OpenGL은 새로운 버전에 추가된 기능을 확장 기능으로 지원 • 이전 버전의 API를 수정하지 않음으로써 이전 버전과의 호환성 유지 • 함수나 매크로 상수 이름에 확장 기능을 식별할 수 있도록 접미어를 붙여 명명 • _ARB, _EXT, _NV, _ATI 등등 • 프로그래머블 하드웨어를 지원하기 위한 API를 확장 기능으로 제공 • 고정 파이프라인을 이용하는 대신 사용자가 작성한 코드대로 음영 처리를 할 수 있는 프로그래머블 파이프라인의 이용이 가능
Programmable pipeline • 사용자는 vertex shader, fragment shader를 작성하여 다양한 음영처리 기법을 적용할 수 있다.
Shading languages • 저수준 음영처리 언어 • 초기의 음영처리 언어로 어셈블리 언어와 같은 문법을 가진다. • 고수준 음영처리 언어 • C 언어 기반의 언어로 C 언어의 구문을 대부분 포함하면서, 음영처리 프로그램이 쉽도록 언어의 특징과 자료형 추가(ex. 벡터 자료형, 벡터 연산 등) • GLSL(OpenGL Shading Language) • OpenGL용 음영 처리 언어로 OpenGL 2.0의 한 부분 • Cg • OpenGL 및 DirectX를 모두 지원 • OpenGL과 Cg간의 인터페이스는 GLSL과의 인터페이스에 비해 다소 복잡함 • HLSL(High Level Shading Language) • DirectX용 음영 처리 언어
GLSL(OPENGL SHADING LANGUAGE)
Data types • 스칼라형 • 벡터형 및 행렬형 • 행렬은 열 우선으로 구성된다. • 배열 • 1차원 배열만 가능하고, 상수 크기로 지정되어야 한다(동적 할당 불가). • 배열 크기를 얻는 방법 • 포인터는 지원하지 않는다. • 구조체 • C 언어 형식의 구조체를 사용할 수 있다.
Qualifiers • 타입 한정자 • 사용 예 • attribute • 버텍스 속성에는 built-in vertex attribute와 user-defined vertex attribute가 있다. • user-defined vertex attribute의 경우 ‘attribute’ 한정자를 사용하여 선언하여야 한다. • uniform • 모든 버텍스 처리에서 동일하게 사용되는 전역 상수 • varying • 버텍스 쉐이더에서 프래그먼트 쉐이더로 전달되는 변수 • user-defined varying variable의 경우 버텍스 쉐이더 및 프래그먼트 쉐이더 두 곳 모두에서 선언되어야 한다.
Operators • 연산자 • C 언어 연산자와 유사하나, 포인터 관련 연산자 및 비트 연산자, 형변환 연산자는 지원하지 않는다.
Constructor • 생성자 • 변수의 초기화는 C++ 형식의 생성자를 통해 이루어진다. • 또한 생성자는 초기화 외에도 식에도 사용될 수 있다. • 벡터에 하나의 스칼라값을 지정하면 벡터의 모든 요소에 할당된다. • 전체 데이터 타입을 초기화할 충분한 요소를 지정한다면 스칼라와 벡터, 행렬을 생성자 내에서 혼합해 사용할 수 있고, 여분의 요소가 있는 경우 버려진다. • 행렬은 열우선으로 구성되고, 단일 스칼라값을 지정하는 경우 대각 행렬이 된다(대각 이외의 요소는 0으로 채워진다.). • 형변환은 생성자를 통해서만 가능하다.
Component selector(swizzling) • 벡터 및 행렬의 요소에 접근하는 방법 • 벡터형의 요소에 접근하기 위한 요소 선택자 • 세 종류의 요소 선택자들 중 어느 것을 사용해도 상관없으나 혼합해서 사용할 수는 없다. • 요소 선택자를 이용하여 재배치 및 복제가 가능하다. • 요소 선택자를 사용하여 벡터의 일부 요소만 수정하는 것이 가능하다. • 벡터형의 요소는 배열 첨자 혹은 요소 선택자를 사용해서 접근 가능 • vec v4 = (1.0, 2.0, 3.0, 4.0); • v4[2], v4.b, v4.z, v4.p는 모두 v4의 세 번째 요소에 접근하는 방법이다. • 행렬 형의 경우 배열 첨자를 이용하여 대응 하는 열의 벡터에 접근하고, 이중 배열 첨자를 사용하면 대응하는 열벡터의 요소에 접근할 수 있다.
Flow control • 흐름 제어 구문 • 반복문 • C/C++과 동일한 방식의 for, while, do/while 반복문이 지원되고, 중첩도 가능하다. • if/else • C/C++과 동일한 방식의 if/else 조건문이 지원되고, 중첩도 가능하다.
Function • C 언어와 마찬가지로 쉐이더도 main()로 시작한다. • void main() { … } • 사용자 정의 함수 • 함수의 리턴 타입으로는 배열을 제외한 모든 타입의 사용이 가능하다. • 함수의 인자로는 배열 및 구조체를 포함한 모든 타입의 사용이 가능하다. • Call by value로만 호출되므로 다음의 한정자를 사용하여 함수 내에서 인자의 값이 변경될 수 있는지 여부를 지정할 수 있다. • 재귀함수(recursive function)은 허용되지 않는다.
VERTEX SHADER
Vertex shader • 각 버텍스 단위로 처리되어야 하는 기본적인 사항은 다음과 같다. • 입력 • 버텍스 좌표, 법선 벡터, 버텍스 색상, 텍스처 좌표, 재질속성등의 버텍스 속성과 변환행렬, 그리고 기타 사용자 정의 변수 • 처리 • 버텍스 변환 • 모델 좌표계 상의 버텍스 위치와 법선 벡터를 모델뷰 변환행렬과 투영 변환행렬에 의해 클립 좌표계상의 위치로 변환 • 버텍스 색상 계산 • 법선 벡터, 재질속성, 조명등을 이용하여 조명 효과에 의한 버텍스 색상을 계산한다. • 텍스처 좌표 생성 및 변환 • 출력 • 클립 좌표계 상의 버텍스 위치, 전 ·후면에 대한 버텍스 색상, 텍스처 좌표 등 • 계산된 버텍스 위치로 클리핑을 수행 • 계산된 버텍스 색상 및 텍스처 좌표를 보간하여 폴리곤 내부의 각 픽셀의 색상 및 텍스처 좌표 생성 • 그 외에 프래그먼트 쉐이더에 전달할 값은 varying 변수를 통해 전달
Application • Moving vertices • Morphing • Wave motion • Fractals • Particle systems • Lighting • More realistic lighting models • Cartoon shaders
Built-in vertex shader variables • Built-in attribute input variables • glVertex*(), glNormal*(), glColor*(), glTexCoord*() 등의 함수에 의해 전달되는 버텍스 속성 • 그 외의 다른 속성을 전달하는 경우, user-defined attribute variables로 전달
Built-in vertex shader variables • Special output variables • 버텍스에 대한 출력값 • Varying output variables • 프래그먼트 쉐이더에서 사용되기 위한 변수로 같은 폴리곤을 구성하는 버텍스의 출력들간의 보간된 값이 프래그먼트 쉐이더에 전달된다.
Simple vertex shader • 다음은 버텍스 좌표를 클립 좌표계로 변환하고, glColor*() 함수로 지정된 버텍스 색상을 그대로 버텍스에 할당하는 간단한 버텍스 쉐이더의 예이다. • 여기서 gl_Vertex는 glVertex*() 함수에 의해 지정된 버텍스 좌표이고, gl_ModelViewProjectionMatrix는 모델뷰 행렬과 투영 행렬이 곱해진 행렬로 두 변수를 곱함으로써 버텍스 좌표가 클립 좌표계로 변환된다. • gl_Color는 glColor*()에 의해 지정된 버텍스 색상이고, gl_FrontColor는 버텍스의 전면 색상 출력 변수이다. • “gl_” 접두어가 붙은 변수는 built-in 변수로 따로 선언할 필요가 없다. void main() { gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; gl_FrontColor = gl_Color; }
FRAGMENT SHADER
Fragment shader • 각 프래그먼트 단위로 처리되어야 하는 기본적인 사항은 다음과 같다. • 입력 • 버텍스 색상을 보간하여 생성된 색상, 보간된 텍스처 좌표, 기타 사용자 정의 변수 및 버텍스 쉐이더에서 전달된 varying 변수 • 처리 • 입력 색상으로부터텍스처 매핑, 블렌딩 등의 과정을 거쳐 픽셀 색상을 출력한다. • 출력 • 픽셀 색상 및깊이값
Application • Per fragment lighting calculations • Texture mapping • Image processing environment mapping bump mapping
Built-in fragment shader variables • Input variables • 버텍스 출력 변수들이 보간되어 전달되는 변수 • Output Variables
Simple fragment shader • 다음은 보간된 색상을 그대로 픽셀 색상으로 적용하는 간단한 프래그먼트 쉐이더의 예이다. • 여기서 gl_FrontColor는 버텍스의 전면 색상을 보간하여 생성된 전면 색상이고, gl_FragColor는 최종적으로 출력되는 픽셀의 색상으로 변수이다. • 버텍스 쉐이더와 마찬가지로 “gl_” 접두어가 붙은 변수는 built-in 변수이다. void main() { gl_FragColor = gl_Color; }
SETUP FOR USING GLSL SHADER
glew library • GLSLshader를 사용하기 위해서는 OpenGL 확장 기능을 이용해야한다. • glew(OpenGL Extension Wrangler) library는 OpenGL 확장 기능을 이용할 수 있도록 해 주는 라이브러리로 • http://glew.sourceforge.net • 또는, 그래픽 하드웨어 제조사에서 제공하는 SDK(ex. nVidia OpenGL SDK)를 통해 얻을 수 있다. • glew.h, glew32.lib, glew.dll를 glut library 설치와 동일한 방법으로 설치한다. • 코드에서 glew.h를 인클루드, 프로젝트 속성에서 glew32.lib을 링크한다. • 쉐이더를 사용하기 전에 glewInit()을 호출하여 확장함수들의 주소를 얻어온다.
Setup Steps • Step 1: Create Shaders • Create handles to shaders • Step 2: Specify Shaders • load strings that contain shader source • Step 3: Compiling Shaders • Actually compile source (check for errors) • Step 4: Creating Program Objects • Program object controls the shaders • Step 5: Attach Shaders to Programs • Attach shaders to program obj via handle • Step 6: Link Shaders to Programs • Another step similar to attach • Step 7: Enable Program • Finally, let GPU know shaders are ready
App Setup GLhandleARB vShader, fShader, prog; // handles to objects // Step 1: Create a vertex & fragment shader object vShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); fShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); // Step 2: Load source code strings into shaders glShaderSourceARB(vShader, 1, &VS_String, NULL); glShaderSourceARB(fShader, 1, &FS_String, NULL); // Step 3: Compile the vertex, fragment shaders. glCompileShaderARB(vShader); glCompileShaderARB(fShader); // Step 4: Create a program object prog = glCreateProgramObjectARB(); // Step 5: Attach the two compiled shaders glAttachObjectARB(prog, vShader); glAttachObjectARB(prog, fShader); // Step 6: Link the program object glLinkProgramARB(prog); // Step 7: Finally, install program object as part of current state glUseProgramObjectARB(prog); 여기서 VS_String은 버텍스 쉐이더 코드가 저장된 문자형 배열, FS_String은 프래그먼트 쉐이더 코드가 저장된 문자형 배열 버텍스 쉐이더나 프래그먼트 쉐이더 중 어느 하나를 attach하지 않는 경우, 해당 쉐이더 대신 고정 기능 쉐이더가 작동하게 된다. 고정 기능 쉐이더를 사용하고자 하는 경우 인자에 0을 지정
Set attribute/uniform values • 쉐이더가 링크될 때, attribute/uniform 변수 테이블이 생성된다. • 쉐이더 내에 선언된 attribute/uniform 이름으로 변수의 테이블 인덱스를 얻어 온다. • GLint i =glGetAttribLocation(P,”myAttrib”)GLint j =glGetUniformLocation(P,”myUniform”) • Attribute/uniform 변수 인덱스를 통해 해당 변수에 값을 할당 • glVertexAttrib1f(i,value)glUniform1f(j,value) • 포인터를 이용하여 전달하는 경우 • glVertexAttribPointer(i,…) // passing attributes using vertex array
EXAMPLE – SIMPLE SHADER
Simple shader • Simple.vert • Simple.frag void main() { gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; gl_FrontColor = gl_Color; } void main() { gl_FragColor = gl_Color; }
PrepareShader function • 버텍스 쉐이더 혹은 프래그먼트 쉐이더를 로드 및 컴파일 한 후, 프로그램 객체에 부착하여 링크시키는 함수 작성 • 파일 입출력 필요 • 컴파일 및 링크 에러 발생시 에러 내용을 쿼리하여 출력해 주는 부분 필요 // 쉐이더를로드, 컴파일, 프로그램객체에부착후링크 int PrepareShader(GLhandleARB *pProgram, char *filename, GLuint flag) { int ret; GLhandleARB *pShader = (GLhandleARB*)malloc(sizeof(GLhandleARB)); FILE *fShader; if (flag != GL_VERTEX_SHADER_ARB && flag != GL_FRAGMENT_SHADER_ARB) return FALSE; fShader = fopen(filename, "rb"); if (fShader == NULL) return FALSE;
PrepareShader function(cont’d) int leng, logLength, success; char **src, *log = NULL; fseek(fShader, 0, SEEK_END); leng = ftell(fShader); src = (char **)malloc(sizeof(char*)); src[0] = (char *)malloc(sizeof(char) *(leng+1)); fseek(fShader, 0, SEEK_SET); fread(src[0], 1, leng, fShader); src[0][leng] = 0; *pShader = glCreateShaderObjectARB(flag); glShaderSourceARB(*pShader, 1, (const GLcharARB **)src, NULL); free(src[0]); free(src); fclose(fShader); glCompileShaderARB(*pShader); glGetObjectParameterivARB(*pShader, GL_OBJECT_COMPILE_STATUS_ARB, &success); if (!success) { glGetObjectParameterivARB(*pShader, GL_OBJECT_INFO_LOG_LENGTH_ARB, &logLength); log = (char *)malloc(sizeof(char)*logLength); glGetInfoLogARB(*pShader, logLength, NULL, log); fprintf(stderr, "%s : shader compile error.\n", filename); fprintf(stderr, "%s\n", log); free(log); ret = FALSE; } 컴파일 오류 체크 및 오류 내용 출력
PrepareShader function(cont’d) else{ glAttachObjectARB(*pProgram, *pShader); glLinkProgramARB(*pProgram); glGetObjectParameterivARB(*pProgram, GL_OBJECT_LINK_STATUS_ARB, &success); if (!success) { glGetObjectParameterivARB(*pProgram, GL_OBJECT_INFO_LOG_LENGTH_ARB, &logLength); log = (char *)malloc(sizeof(char)*logLength); glGetInfoLogARB(*pProgram, logLength, NULL, log); fprintf(stderr, "program link error.\n"); fprintf(stderr, "%s\n", log); free(log); ret = FALSE; } else{ glValidateProgramARB(*pProgram); glGetObjectParameterivARB(*pProgram, GL_OBJECT_VALIDATE_STATUS_ARB, &success); if (!success){ glGetObjectParameterivARB(*pProgram, GL_OBJECT_INFO_LOG_LENGTH_ARB, &logLength); log = (char*)malloc(sizeof(char)*logLength); glGetInfoLogARB(*pProgram, logLength, NULL, log); fprintf(stderr, "program validate error.\n"); fprintf(stderr, "%s\n", log); free(log); ret = FALSE; } else ret = TRUE; } } free(pShader); return ret; } 링크 오류 체크 및 오류 내용 출력
SetupRC()에서 • glewInit() 실행 • 프로그램 객체 생성 및 PrepareShader() 실행 • 쉐이더가 올바르게 준비되었으면 glUseProgramARB()로 해당 프로그램 객체 사용 // OpenGL 확장기능을사용할수있도록초기화 glewInit(); // 프로그램객체(vert+frag) 생성 simpleProgram = glCreateProgramObjectARB(); // 쉐이더를로드하여컴파일및링크후프로그램객체에부착 int ret = PrepareShader(&simpleProgram, "simple.vert", GL_VERTEX_SHADER_ARB); ret |= PrepareShader(&simpleProgram, "simple.frag", GL_FRAGMENT_SHADER_ARB); // 에러가없는경우프로그램객체사용 if (ret) glUseProgramObjectARB(simpleProgram);
프로그램 객체를 전역 변수로 선언 • 프로그램 객체 및 쉐이더 객체는 GLhandleARB 형을 갖는다. • Main() 함수에서 • glutMainLoop()가 끝난 후 • 프로그램 객체 제거 GLhandleARB simpleProgram; // 프로그램객체제거 if (simpleProgram) glDeleteProgramsARB(1, &simpleProgram);
Result • SimpleShader 프로젝트를 빌드 후 실행
EXAMPLE – PHONG SHADING
Shader code • Phong_shading.vert varying vec3 N; varying vec3 L; varying vec3 E; void main() { gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; // 시점좌표계상에서조명효과를계산하기위해 // 버텍스좌표를시점좌표계로변환 vec4 eyePosition = gl_ModelViewMatrix*gl_Vertex; // 광원좌표는자동으로시점좌표계로변환되어 // built-in 구조체에저장되어있다. vec4 eyeLightPos = gl_LightSource[0].position; // homogeneous 좌표계형태의법선벡터, 광원벡터, 시점벡터를 // 3차원유클리드 좌표 형태로 varying 변수에저장 N = normalize(gl_NormalMatrix*gl_Normal); L = eyeLightPos.xyz - eyePosition.xyz; E = -eyePosition.xyz; }
Shader code • Phong_shading.frag varying vec3 N; varying vec3 L; varying vec3 E; void main() { vec3 Normal = normalize(N); vec3 Light = normalize(L); vec3 Eye = normalize(E); vec3 Half = normalize(Eye+Light); float f = 1.0; // diffuse 및specular 반사에서vector 내적에의한요소계산 // max() 함수는내적결과가음수인경우0.0으로만들기위함. float Kd = max(dot(Normal,Light), 0.0); float Ks = pow(max(dot(Half,Normal), 0.0), gl_FrontMaterial.shininess); // diffuse, specular, ambient 반사성분계산 vec4 diffuse = Kd*gl_FrontLightProduct[0].diffuse; if (dot(Normal, Light)<0.0) f = 0.0; vec4 specular = f*Ks*gl_FrontLightProduct[0].specular; vec4 ambient = gl_FrontLightProduct[0].ambient; gl_FragColor = ambient + diffuse + specular; }
Result • Gouraud shading • Phong shading