Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

CH04 Shaders and Programs

Hyunwoo Jo edited this page Aug 16, 2023 · 6 revisions

CH 04. Shaders and Programs

Created: 2023년 8월 16일 오전 11:46

초록

ch02 에서는 하나의 삼각형을 그리는 간단한 프로그래밍 예제를 소개했다.

이번 장에서는 shader를 생성하고, 컴파일하고, program object로 링크하는 과정의 전부를 다룬다. 그중 vertex shader와 fragment shader의 작성은 추후에 알아보고, 아래의 주제에 집중한다.

  • Shader, program Object 란 무엇인지.
  • Shader의 생성과 컴파일
  • Program의 생성과 링킹
  • Uniform의 생성(setting)과 접근(getting)
  • Attributes의 생성(setting)과 접근(getting)
  • Shader 컴파일러와 program binaries

Shader and Programs

shader를 이용해 랜더링을 하기 위해서는 shader objectprogram object 라는 두 가지 기본 객체를 생성해야 한다. Shader object 와 program object를 생각하는 가장 좋은 방법은 C 컴파일러, 링커와 비교하는 것이다. C complier는 object code 를 생성하고 (ex .obj, .o) 그 뒤 C linker 는 obj file 들을 엮어 실행 가능한 프로그램을 만든다.

비슷한 일련의 과정이 OpenGL에서 또한 일어나는데, shader object는 하나의 shader를 담는 객체이고, 이는 object form으로 컴파일된다. 컴파일 된 이후 program object에 부착될 수 있다. program object는 여러 개의 shader object를 가질 수 있다. Desktop OpenGL 은 여러 개의 shader를 가질 수 있지만, ES에서는 ‘딱 하나의 vertex shader’ 와 ‘ 딱 하나의 fragment shader obj‘를 가질 수 있다(적어서도, 더 많아서도 안됨). program object는 랭킹 이후 최종 실행 가능한 결과물을 만들어내고, 이것이 렌더(render)에 사용된다.

일반적으로 다음과 같은 6개의 과정을 통해 linked shader object 가 만들어진다

  1. Vertex shader object와 fragment shader object를 생성한다.
  2. 각각의 shader object에 소스코드를 첨부(attach).
  3. Shader object를 컴파일
  4. Program object를 생성
  5. 컴파일된 shader object를 program object에 첨부
  6. Program object를 링크

모든 과정이 오류없이 종료되면, GL 에게 생성된 프로그램을 통해 화면을 그리도록 요청 할 수 있다.

Creating and Compiling a Shader

Shader object로 작업하기 위한 첫번째 단계: 셰이더 “생성”하기

Type을 가지고 새로운 vertex shader 또는 fragment shader를 생성할 수 있다.

GLuint glCreateShader(GLenum type)
-------------------------------------------------------------
type: GL_VERTEX_SHADER, GL_FRGMENT_SHADER 둘중에 하나.

참고로, gl3.h에 다음과 같이 정의되어있다.
#define GL_FRAGMENT_SHADER                0x8B30
#define GL_VERTEX_SHADER                  0x8B31

Shader를 다 쓰고 나면 아래 함수를 호출하여 지울 수 있다. 단, shader가 program object 에 연결되어 있다면, 즉각적으로 shader가 지워지지 않는다. Shader는 지워질것이라고 내부적으로 표시만 되고, 그리고 해당 shader가 더이상 어떠한 program object에 참조가 안되고 있을때 지워질 것이다.

void glDeleteShader(GLuint shader)
-------------------------------------------------------------
shader: 삭제할 쉐이더의 identifier

두번째 단계: 생성한 후, shader에 source code 제공하기

void glShaderSource(
	GLuint shader, 
	GLsizei count, 
	const GLchr* const *string, 
	const GLint *length,
)
-------------------------------------------------------------
shader: 소스를 추가할 쉐이더의 identifier 
count: 소스 문자열 배열의 length. 여러개의 string source를 제공할 수 있지만, 
				main entrypoint는 오직 하나여야 한다.
string: C에는 문자열이라는 타입이 없기 때문에, 이를 const char* 로 표현한다.
우리는 문자열의 배열을 전달해줄 꺼기 때문에, (const char* = String) const * 가 되는것.
length: 각 문자열의 길이를 담고있는 배열

세번째 단계: 쉐이더에 소스를 특정해 준 뒤, 컴파일 진행.

void glCompileShader(GLuint shader)
-------------------------------------------------------------
shader: 컴파일할 쉐이더의 identifier 

아마 여러분들은 컴파일을 하고 나면, 컴파일 에러가 나는지, 잘 동작 하는지 알고 싶을 것이다.

→ 그때 필요한 것은 glGetShaderiv() 함수이다.

void glGetShaderiv(GLint shader, GLenum pname, GLint *params)
---------------------------------------------------------------
shader: 상태를 알아볼 쉐이더의 identifier 
pname: 
	GL_COMPILE_STATUS : 컴파일 성공하면 params 가 true, 아니면 false 
	GL_DELETE_STATUS : 
	GL_INFO_LOG_LENGTH : 컴파일 glGetShaderInfoLog()를 사용하여 에러 로그를 확인가능
	GL_SHADER_SOURCE_LENGTH : 
	GL_SHADER_TYPE : vertex 인지 filament 인지
params: 

만약 위 과정에서, error 가 났다면 해당 로그를 확인하고 싶을 거다. 아래 내장 함수를 통해 확인 가능하다.

void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog)
---------------------------------------------------------------
shader: info log 를 가져올 수 있는 shader
maxLength: info log 를 저장할 수 있는 버퍼 사이즈
length: log 길이, NULL 가능
infoLog: 

Creating and linking a Program

Program object는 container object이다. 이 의미는 shader object들을 부착하고, 최종 실행 프로그램을 링크한다는 의미.

첫번째 단계: program object 생성.

Program object생성 함수:

GLuint glCreateProgram()
---------------------------------------------------------------
program object에 대한 handle을 반환한다. 
💡 여기서 handle이란, object를 지칭하는 일종의 id라고 생각하면 된다. openGL spec을 구현한 구현체는 C언어로 이루어져 있고 C언어는 객체지향 언어가 아니기에, 이러한 handle을 통해 object를 지칭한다.

Program object 삭제 함수:

void glDeleteProgram(GLuint program)
---------------------------------------------------------------
program: 삭제하기 위한 program obj handle

두번째 단계: program object에 shader 부착하기

부착 함수: 모든 program object에 vertex shader object, fragment shader object가 각각 1개만 연결되는 조건만 만족하면 shader는 compile여부, source code 존재 여부와 별개로 부착 가능하다.

void glAttach(GLuint program, GLuint shader)
---------------------------------------------------------------
program: program obj handle
shader: program obj에 부착하기 위한 shader obj handle

부착된 shader 분리 함수:

void glDetachShader(GLuint program, GLuint shader)
---------------------------------------------------------------
program : program obj handle
shader : program obj에 부착하기 위한 shader obj handle

세번째 단계: shader에 program 링크 및 오류 체크

링크 함수:

void glLinkProgram(GLuint program)
---------------------------------------------------------------
program : program obj handle

성공적 link 여부를 확인하는 함수:

void glGetProgramiv(GLuint program, GLenum pname, GLint *params)
-------------------------------------------------------------
program : 정보를 가져오기 위한 program obj handle
pname : 
	GL_ACTIVE_ATTRIBUTES : vertex shader의 활성된 속성 
	GL_ACTIVE_ATTRIBUTE_MAX_LENGTH : 가장  속성 이름의 최대 길이
	GL_ACTIVE_UNIFORM_BLOCK : 활성 uniform 포함하는 프로그램의 uniform block 
	GL_ACTIVE_UNIFORM_BLOCK_MAX_LENGTH : 활성 uniform 포함하는 프로그램의 uniform block 이름 최대 길이
	GL_ACTIVE_UNIFORMS : 활성된 uniform의 
	GL_ACTIVE_UNIFORM_MAX_LENGTH : 가장  uniform 이름의 최대 길이
	GL_ATTACHED_SHADERS : 부착된 shader 
	GL_DELETE_STATUS : program obj 삭제 표시 여부 확인
	GL_INFO_LOG_LENGTH : 쿼리할  있는 정보 로그 길이(program obj가 저장)
	GL_LINK_STATUS : 링크 성공 여부 확인
	GL_PROGRAM_BINARY_RETRIEVABLE_HINT : binary retrievable hint가 프로그램에 활성화되어있는지 여부 판단.
	GL_TRANSFORM_FEEDBACK_BUFFER_MODE : GL_SEPERATE_ATTRIBS / GL_INTERLEAVED_ATTRIBS 반환. (transform feedback이 활성화된 경우 버퍼 모드)
	GL_TRANSFORM_FEEDBACK_VARYINGS : 프로그램에 대한 쿼리는 각각 프로그램에 대한 transform feedback 모드에서 캡쳐할 출력 변수의 
	GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH : 프로그램에 대한 쿼리는 각각 프로그램에 대한 transform feedback 모드에서 캡쳐할 출력 변수의 최대 길이
	GL_VALIDATE_STATUS : 유효성 검사 작업의 상태
params : 해당 함수의 결과가 담긴 integer storage 위치를 가리키는 포인터
void glGetProgramiv(GLuint program, GLenum pname, GLint *params)
-------------------------------------------------------------
program : 정보를 가져오기 위한 program obj handle
pname : 
	GL_ACTIVE_ATTRIBUTES : vertex shader의 활성된 속성 
	GL_ACTIVE_ATTRIBUTE_MAX_LENGTH : 가장  속성 이름의 최대 길이
	GL_ACTIVE_UNIFORM_BLOCK : 활성 uniform 포함하는 프로그램의 uniform block 
	GL_ACTIVE_UNIFORM_BLOCK_MAX_LENGTH : 활성 uniform 포함하는 프로그램의 uniform block 이름 최대 길이
	GL_ACTIVE_UNIFORMS : 활성된 uniform의 
	GL_ACTIVE_UNIFORM_MAX_LENGTH : 가장  uniform 이름의 최대 길이
	GL_ATTACHED_SHADERS : 부착된 shader 
	GL_DELETE_STATUS : program obj 삭제 표시 여부 확인
	GL_INFO_LOG_LENGTH : 쿼리할  있는 정보 로그 길이(program obj가 저장)
	GL_LINK_STATUS : 링크 성공 여부 확인
	GL_PROGRAM_BINARY_RETRIEVABLE_HINT : binary retrievable hint가 프로그램에 활성화되어있는지 여부 판단.
	GL_TRANSFORM_FEEDBACK_BUFFER_MODE : GL_SEPERATE_ATTRIBS / GL_INTERLEAVED_ATTRIBS 반환. (transform feedback이 활성화된 경우 버퍼 모드)
	GL_TRANSFORM_FEEDBACK_VARYINGS : 프로그램에 대한 쿼리는 각각 프로그램에 대한 transform feedback 모드에서 캡쳐할 출력 변수의 
	GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH : 프로그램에 대한 쿼리는 각각 프로그램에 대한 transform feedback 모드에서 캡쳐할 출력 변수의 최대 길이
	GL_VALIDATE_STATUS : 유효성 검사 작업의 상태
params : 해당 함수의 결과가 담긴 integer storage 위치를 가리키는 포인터

GL_LINK_STATUSpname

  • fragment shader가 사용하는 모든 vertex shader 출력 변수가 vertex shader에 의해 작성되었는지, 동일한 유형으로 선언되었는지 확인한다.
  • vertex shader, fragment shader에서 모두 선언된 모든 uniform, uniform buffer들의 type 일치 여부를 확인한다.
  • 최종 프로그램이 구현 조건에 일치하는지 확인한다.(속성, uniform, 입력 및 출력 shader 변수)

(transform feedback : 8장에서 설명)

링킹 후, 프로그램 정보 로그에서 정보를 가져오는 함수(shader에서 정보 가져오는 것과 유사):

void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog)
---------------------------------------------------------------
program : 정보를 가져올 program obj의 handle
maxLength : 로그 저장을 위해 필요한 버퍼의 크기
length : 기록된 정보 로그의 길이(null문자 제외, null값일  있음.)
infoLog : 정보 로그를 저장할 문자 버퍼의 포인터

프로그램의 유효성 검사(성공적인 링크 여부 체크) 함수:

void glValidateProgram(GLuint program)
---------------------------------------------------------------
program : 유효성 검사를 진행할 program obj의 handle

해당 검사 결과는 glGetProgramiv()의 GL_VALIDATE_STATUS로 확인 가능하다.

(이 함수는 디버깅 목적으로만 사용할 수도 있으나, 느린 작업이어서 모든 렌더링 작업에 사용할 필요는 없다. 또한 해당 함수는 응용 프로그램이 성공적으로 렌더링되면 사용하지 않아도 된다.)

네번째 단계: Program object 활성화

활성화 함수:

void glUseProgram(GLuint program)
---------------------------------------------------------------
program : 활성화할 program obj의 handle

Uniforms and Attributes

링크된 프로그램 개체(linked program object)가 있으면 해당 개체에 대해 수행할 수 있는 여러 쿼리가 있다. 먼저 프로그램에서 활성상태의 유니폼(active uniforms)들에 대해 알아봐야 할 것이다. 유니폼(uniform)은 다음 장인 shading language에서 더 자세히 설명하겠지만, 응용 프로그램이 OpenGL ES 3.0 API를 통해 셰이더에 전달하는 읽기 전용 상수 값(read-only constant values)을 저장하는 변수이다.

  • What is the Uniform??

    https://www.khronos.org/opengl/wiki/Uniform_(GLSL)

    Untitled

    Uniform은 CPU에서 GPU로 데이터를 전달 할 수 있는 방법입니다. 또한 ‘전역(global)변수’ 로서 값을 업데이트하거나 바꾸지 않는이상 유지됩니다. 이를 통해 동일한 쉐이더 프로그램을 사용하면서 다양한 객체나 픽셀에 일관된 값을 적용할 수 있게 됩니다. 쉐이더 내에서 uniform 변수에 접근하고 값을 변경하기 위해서는 프로그램이 실행되기 전에 CPU에서 그 값을 설정해 주어야 합니다. 간단히 말해, uniform은 쉐이더 간에 공유되는 변하지 않는 값으로, 쉐이더의 렌더링 결과에 일관성을 제공하고 다양한 그래픽 요소를 조절하는 데 사용됩니다.

    예를 들어, 모델-뷰-프로젝션 행렬, 빛의 위치, 머터리얼 색상 등이 uniform으로 정의될 수 있습니다.

1개의 변수들을 지칭하는 uniform들을 모으면 uniform block이 된다. uniform block은 2가지 형태의 블록으로 나누어 진다. 첫 번째는 named uniform block으로, 셰이더 코드에서 다음과 같은 형태로 선언된다.

//  다음 예제에서는 세 개의 uniform(matViewProj, matNormal 및 matTexGen)을 포함하는 이름이 TransformBlock인 named uniform block을 선언합니다.
uniform TransformBlock
{
 mat4 matViewProj;
 mat3 matNormal;
 mat3 matTexGen;
};
  • 해당 block안에 있는 uniform 값들은 uniform buffer object에 매핑된다.
  • named uniform block에는 1개의 uniform block index가 할당됨.

두번째 카테고리는 default uniform block으로 셰이더 코드에서 다음과 같은 형태로 선언된다.

uniform mat4 matViewProj;
uniform mat3 matNormal;
uniform mat3 matTexGen;
  • named uniform block **외부에서 선언된 uniform들에 대한 기본적인 uniform block이다.
  • named uniform block과 달리 default uniform block에는 이름과 uniform block index가 없다.
  • 링크과정에서 uniform location 값을 부여받는다. 추후 이 uniform location이 특정 uniform에 대해 값을 초기화 할 때 필요하다.

우선 uniform block은 2가지 형태가 있다는 점만 알고 있는데 집중하자. 추후 named uniform block에 대해 설명할 것이다. 이번 장에서는 default uniform block의 uniform들을 어떻게 값을 초기화 하는지 알아보는데 집중할 것이다.

Getting and Setting Uniforms

default uniform block의 uniform들의 값을 초기화 하기 위해서 다음의 과정을 거친다.

  1. program object안의 uniform들의 갯수를 알아낸다.
  2. [optional] 이름*, type*, array유무* 를 알아야 한다.
  3. uniform location값을 알아낸다.
  4. 적절한 uniform setting 함수를 call 한다. 이때 초기화 하는 uniform의 uniform location 값을 넣는다.
💡 2번과정은 optional 인데, 그 이유는 shader 코드를 자기가 작성했다면 이름과 type, array 유무를 이미 알고 있을것이기 때문이다.

첫번째 단계: program obect안의 uniform 갯수 알아내기

먼저 program object안의 활성상태인 uniform의 총 갯수를 알아내려면 glGetProgramiv 함수를 GL_ACTIVE_UNIFORMS 파라미터를 넣어 호출하면 된다. 그 목록들은 program object의 셰이더 코드에 존재하는 named uniform blocks, default block uniforms안의 활성화된 uniform들의 갯수이다. 활성화된 uniform이라는 개념이 많이 언급되는데, 이것은 program에 의해 실제로 쓰이는가의 유무를 나타내는 개념이다. 즉, shader code에 선언만 해놓고 사용해지 않는다면 활성화된 uniform이 아닌것이다(링커가 optimize를 통해 변수를 날려버린다).

void glGetProgramiv(GLuint program, GLenum pname, GLint *params)
-------------------------------------------------------------
program : 정보를 가져오기 위한 program obj handle
pname : 
	GL_ACTIVE_UNIFORMS : 활성된 uniform의 수
params : 해당 함수의 결과가 담긴 integer storage 위치를 가리키는 포인터

또한, 나중에 uniform의 location을 알기 위해 uniform 변수들의 이름을 알아야 한다(optional). 이름을 알아내기 전 program object안의 uniform 변수들의 이름중 가장 큰 이름의 길이를 glGetProgramiv 함수에 GL_ACTIVE_UNIFORM_MAX_LENGTH 파라미터를 넣어 호출해 알아낼 수 있다.

두번째 단계(optional): 이름*, type*, array유무*를 알아내기.

일단 이렇게 활성화된 uniform 변수들의 갯수와 이름의 최대 길이값을 알아 내었으면 해당 uniform 각각의 보다 세부적인 속성값들에 대해 알아낼 수 있다. 이때 사용하는 함수가 glGetActiveUniform, glGetActiveUniformsiv 함수들이다.

/*
uniform 변수의 이름, type, size의 갯수를 알아내는 함수: 
계속 언급하지만, 이미 uniform의 이름 type, size를 알고 있다면 이 함수는 호출을 하지 않아도 된다.
*/
void glGetActiveUniform(GLuint program, GLuint index,
												GLsizei bufSize, GLsizei *length,
												GLint *size, GLenum *type,
												GLchar *name)
---------------------------------------------------------------
program : program obj handle
index : 쿼리할 uniform index
bufSize : name array의 문자 
length : null이 아닌 경우 name array에 기록된 문자 수로 기록
size : 쿼리되는 uniform 변수가 배열인 경우, 프로그램에서 사용되는 최대 배열요소(+1) 설정. 배열이 아닐 경우 1 설정.
type : uniform type
	GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3,
	GL_FLOAT_VEC4, GL_INT, GL_INT_VEC2, GL_INT_VEC3,
	GL_INT_VEC4, GL_UNSIGNED_INT,
	GL_UNSIGNED_INT_VEC2, GL_UNSIGNED_INT_VEC3,
	GL_UNSIGNED_INT_VEC4, GL_BOOL, GL_BOOL_VEC2,
	GL_BOOL_VEC3, GL_BOOL_VEC4, GL_FLOAT_MAT2,
	GL_FLOAT_MAT3, GL_FLOAT_MAT4, GL_FLOAT_MAT2x3,
	GL_FLOAT_MAT2x4, GL_FLOAT_MAT3x2, GL_FLOAT_MAT3x4,
	GL_FLOAT_MAT4x2, GL_FLOAT_MAT4x3, GL_SAMPLER_2D,
	GL_SAMPLER_3D, GL_SAMPLER_CUBE,
	GL_SAMPLER_2D_SHADOW, GL_SAMPLER_2D_ARRAY,
	GL_SAMPLER_2D_ARRAY_SHADOW,
	GL_SAMPLER_CUBE_SHADOW, GL_INT_SAMPLER_2D,
	GL_INT_SAMPLER_3D, GL_INT_SAMPLER_CUBE,
	GL_INT_SAMPLER_2D_ARRAY,
	GL_UNSIGNED_INT_SAMPLER_2D,
	GL_UNSIGNED_INT_SAMPLER_3D,
	GL_UNSIGNED_INT_SAMPLER_CUBE,
	GL_UNSIGNED_INT_SAMPLER_2D_ARRAY
*/
name : buffer size 문자 (null로 끝남)
// 보다 세부적인 property를 알아내고 싶을때 사용하는 함수
void glGetActiveUniformsiv(GLuint program, GLsizei count,
														const GLuint *indices,
														GLenum pname, GLint *params)
---------------------------------------------------------------
program : program obj handle
count : indices 배열의 요소 
indices : uniform indices의 목록
pname : params의 요소에 기록될 uniform indices 각각의 uniform 속성
	GL_UNIFORM_TYPE, GL_UNIFORM_SIZE,
	GL_UNIFORM_NAME_LENGTH, GL_UNIFORM_BLOCK_INDEX,
	GL_UNIFORM_OFFSET, GL_UNIFORM_ARRAY_STRIDE,
	GL_UNIFORM_MATRIX_STRIDE, GL_UNIFORM_IS_ROW_MAJOR
params: uniform indices의  uniform에 해당하는 pname으로 지정된 결과
*/

glGetActiveUniform 을 사용하면 uniform 변수의 거의 모든 속성들을 알아낼 수 있다. name, type, size를 알 수 있다. 이때 size가 1보다 크다면 array형 자료형이란 의미이다.

세번째 단계: uniform location값 알아내기

uniform name을 glGetUniformLocation 함수에 인자로 넣어 통해 uniform location값을 알아낼 수 있다. uniform location값은 program object안의 uniform이 위치하고 있는곳을 식별하기 위한 id라고 생각하면 된다(named uniform block안의 uniform들은 location 값을 부여받지 않는다는 것에 유의하자). uniform location은 uniform의 value를 loading하는 glUniform1f 함수에 인자로 쓰인다.

GLint glGetUniformLocation(GLuint program,
														const GLchar* name)
---------------------------------------------------------------
program : program obj handle
name : 위치를 가져올 uniform 이름

위 함수는 name인자에 매칭되는 uniform location을 반환한다. 만약 uniform이 active uniform이 아니거나 named uniform block uniform -1이 return된다. 일단 이렇게 uniform location, type, array size 세가지를 알아내었다면 uniform변수에 구체적인 값을 load할 준비가 끝난것이다. 결과적으로 uniform location은 함수의 인자로, type과 array size는 loading할때 적절한 함수를 선택하는데 필요한것이였다.

void glUniform1f(GLint location, GLfloat x)
void glUniform1fv(GLint location, GLsizei count, 
const GLfloat* value)
void glUniform1i(GLint location, GLint x)
void glUniform1iv(GLint location, GLsizei count,
const GLint* value)
void glUniform1ui(GLint location, GLuint x)
void glUniform1uiv(GLint location, GLsizei count, 
const GLuint* value)
void glUniform2f(GLint location, GLfloat x, GLfloat y)
void glUniform2fv(GLint location, GLsizei count,
const GLfloat* value)
void glUniform2i(GLint location, GLint x, GLint y)
void glUniform2iv(GLint location, GLsizei count,
const GLint* value)
void glUniform2ui(GLint location, GLuint x, GLuint y)
void glUniform2uiv(GLint location, GLsizei count,
const GLuint* value)
void glUniform3f(GLint location, GLfloat x, GLfloat y, 
GLfloat z)
void glUniform3fv(GLint location, GLsizei count, 
const GLfloat* value)
void glUniform3i(GLint location, GLint x, GLint y, 
GLint z)
void glUniform3iv(GLint location, GLsizei count, 
const GLint* value)
void glUniform3ui(GLint location, GLuint x, GLuint y, 
GLuint z)
void glUniform3uiv(GLint location, GLsizei count, 
const GLuint* value)
void glUniform4f(GLint location, GLfloat x, GLfloat y, 
GLfloat z, GLfloat w);
void glUniform4fv(GLint location, GLsizei count, 
const GLfloat* value)
void glUniform4i(GLint location, GLint x, GLint y, 
GLint z, GLint w)
void glUniform4iv(GLint location, GLsizei count, 
const GLint* value)
void glUniform4ui(GLint location, GLuint x, GLuint y, 
GLuint z, GLuint w)
void glUniform4uiv(GLint location, GLsizei count, 
const GLuint* value)
void glUniformMatrix2fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix3fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix4fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix2x3fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix3x2fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix2x4fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix4x2fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix3x4fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
void glUniformMatrix4x3fv(GLint location, GLsizei count, 
GLboolean transpose, 
const GLfloat* value)
---------------------------------------------------------------
location : 값을 로드할 uniform의 위치
count : 로드할 배열 요소의 (벡터 명령의 경우) 또는 수정할 행렬의 (행렬 명령의 경우)
transpose : 행렬 명령의 경우, 행렬이  주요 순서(GL_FALSE)인지  주요 순서(GL_TRUE)인지 지정한다.(전치행렬 여부)
x, y, z, w : 업데이트된 uniform 
value : count elements의 배열에 대한 포인터

Uniform에 값들을 로딩하는 function들은 대부분 자기 설명적이다. glGetActiveUnifom 함수가 return한 type 에 따라 호출되어야 하는 함수가 결정된다. 예컨대 type이 GL_FLOAT_VEC4 이라면 glUniform4fglUniform4fv 함수가 호출되어야 한다. 또는 셰이터 코드에서 선언한 uniform이 mat3 이라면 glUniformMatrix3fv을 사용하면 된다. size가 1보다 크다면 array형태를 초기화 할 수 있는glUniform4fv 함수를 호출하여 편리하게 array값들을 한번에 load할수 있다. 만약 uniform이 array가 아니라면 glUniform4f와 glUniform4fv 함수 중 아무것이나 사용할 수 있을것이다.

한가지 주목할 점은 glUniform* 함수들은 program object를 파라미터로 넣지 않는다는 것인데, 그 이유는 glUniform*함수는 glUseProgram함수 call을 통해 현재 bounding되어있는 program에 항시 적용되기 때문이다. uniform 변수 값들은 program object에 보관되어 있다. 한번 uniform을 program object에 할당한다면 그 값은 다른 program object가 active한 상태가 되더라도 그대로 program object에 남아있다. 한마디로 uniform 값들은 program object에 local 범위이다.

다음 코드는 이때까지 설명한 uniform들의 정보들을 알아내어 초기화(주석으로 표현)하는 코드이다.

GLint maxUniformLen;
GLint numUniforms;
char * uniformName;
GLint index;

glGetProgramiv(progObj, GL_ACTIVE_UNIFORMS, & numUniforms);
glGetProgramiv(progObj, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformLen);

uniformName = malloc(sizeof(char) * maxUniformLen);

for (index = 0; index < numUniforms; index++) {
    GLint size;
    GLenum type;
    GLint location;
    // Get the uniform info
    glGetActiveUniform(progObj, index, maxUniformLen, NULL,
							         &size, & type, uniformName);
    // Get the uniform location
    location = glGetUniformLocation(progObj, uniformName);
    switch (type) {
    case GL_FLOAT:
        // call glUniform1f(location, 0.1f)
        break;
    case GL_FLOAT_VEC2:
        // call glUniform2f(location, 0.1f, 0.2f)
        break;
    case GL_FLOAT_VEC3:
        // call glUniform3f(location, 0.1f, 0.2f, 0.3f)
        break;
    case GL_FLOAT_VEC4:
        // call glUniform4f(location, 0.1f, 0.2f, 0.3f, 0.4f)
        break;
    case GL_INT:
        // call glUniform1i(location, 1)
        break;
        // ... Check for all the types ...
    default:
        // Unknown type
        break;
    }
}

Getting and Setting Attributes

program object의 uniform 정보들을 질의하는것처럼 program object의 vertex 정보들 역시 조회 가능하다(조회해야 할 것이다!). 질의 쿼리들은 uniform 쿼리들과 유사하다. Active attributes를 GL_ACTIVE_ATTRIBUTES 쿼리를 활용해 알아낼 수 있다. 또한 attribute의 properties를 glGetActiveAttrib 명령을 통해 알아낼 수 있다. 그리고 나서 vertex array를 설정하여 vertex attributes를 value와 함께 로드하기 위한 일련의 루틴을 사용할 수 있다.

하지만, vertex attributes를 설정하는것은 primitives와 vertex shader에 대한 이해가 조금 더 필요하다. 대신 쳅터 6장에서(“Vertex Attributes, Vertex Arrays, and Buffer Objects”) vertex attribute와 vertex arrays에 대하여 1장을 할애하여 설명한다. 만약 어떻게 vertex attribute정보를 알아내고 싶다면 챕터 6장으로 건너뛰어 Vertex Attribute Variables 구간을 참고해라.

Shader Compiler

shader 코드 : 대부분의 컴파일된 언어(Abstract Syntax Tree)와 같이 중간 표현으로 구문 분석된다. 구문 분석 이후, 컴파일러는 추상 표현을 기계 명령어로 변환해야 한다. 하드웨어를 위해 컴파일러는 사용하지 않는 코드 제거, constant propagation과 같은 최적화의 모든 작업을 수행하기 위해 CPU 시간과 메모리를 사용해야 한다. 이러한 모든과정은 CPU time, memory의 많은 비용이 든다.

OpenGL ES 3.0의 모든 구현체(implementations)들은 반드시 online shader compilation을 지원해야 한다. 즉, glGetBoolean함수 GL_SHADER_COMPILER 인자를 넣어 호출하면 반드시 결과가 GL_TRUE 이어야 한다.

💡 online shader compilation이란, run-time에 코드를 compile 하는것을 의미한다. 셰이더 코드는 run-time에 컴파일 되므로 online compile되는 것이다.

어찌되었든, 이때까지 그래왔던것 처럼 glShaderSource를 사용하여 shader를 지정할 수 있다.

한편**, shader compilation의 리소스 영향을 완화**시킬 수 있다. 이 함수는 리소스를 해제할 수 있도록 shader compiler로 구현을 완료했다는 힌트를 제공하여 shader compilation과 연관된 리소스를 free시킬 수도 있다. 만약 free된 후 shader를 다시 컴파일할 경우가 생긴다면, 구현체는 컴파일러에 대한 리소스를 재할당 할 것이다.

void glReleaseShaderCompiler(void)
shader compiler에서 사용하는 리소스를 해제할  있다는 힌트를 제공한다.
힌트일 뿐이므로 일부 구현 과정에서는 해당 함수에 대한 호출을 무시할  있다.

Program Binaries

  • 완전한 컴파일 및 링크 프로그램의 binary 표현.
  • 이후 다시 사용할 수 있도록 파일 시스템에 저장할 수 있으므로 온라인 컴파일 비용을 피할 수 있어서 유용하다.
  • 구현 과정에서 shader 소스 코드를 배포할 필요가 없도록 하기 위해 사용할 수도 있다.

Load 함수:

void glGetProgramBinary(GLuint program, GLsizei bufSize, GLsizei *length, GLenum binaryFormat, GLvoid *binary)
---------------------------------------------------------------
program : program obj handle
bufSize : binary 형식으로 최대로   있는 바이트 
length : binary 데이터의 바이트 
binaryFormat : 벤더별 binary 형식 토큰
binary : shader 컴파일러에서 생성된 binary 데이터에 대한 포인터.

Save 함수:

void glProgramBinary(GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length)
---------------------------------------------------------------
program : program obj handle
binaryFormat : 벤더별 binary 형식 토큰
binary : shader 컴파일러에서 생성된 binary 데이터에 대한 포인터
length : binary 데이터의 바이트 

program binary 검색 후, 해당 결과를 파일 시스템에 저장하거나 glProgramBinary를 사용해 program binary를 구현으로 다시 로드 가능하다.

OpenGL ES 사양은 특정 binary 형식을 요구하지 않는다. 즉, binary의 구체적인 format은 vendor(GPU회사) 마다, 또는 GPU 모델마다 다를 수 있다. 따라서 저장된 program binary의 호환성을 확인하는 절차가 있으면 좋다. glProgramBinary()를 호출 후 glGetProgramiv를 통해 GL_LINK_STATUS를 쿼리한다. 호환되지 않는 경우, 호환되지 않는 로컬 시스템에서 shader 소스 코드를 다시 컴파일해야 한다.