GRAPHICS / 기술 노트

GLSL 컴파일·링크 오류와 uniform 갱신

파일 읽기, 셰이더 단계 컴파일, 프로그램 링크, uniform 갱신을 나눠 실패를 찾는다. 생성자와 입출력 오류를 수정하는 실습과 풀이를 제공한다.

검은 화면을 하나의 오류로 취급하지 않기

Shaders 노트의 입출력과 uniform을 오류 확인 순서로 다시 구성했다. C++가 컴파일됐다는 사실은 문자열 안의 GLSL까지 컴파일됐다는 뜻이 아니다. GLSL은 현재 컨텍스트의 드라이버가 처리한다.

단계확인할 값실패했을 때
소스 읽기실제 읽은 파일과 문자열작업 디렉터리·빈 파일·인코딩 확인
셰이더 컴파일GL_COMPILE_STATUS해당 단계의 info log
프로그램 링크GL_LINK_STATUS단계 간 타입·이름·버전 확인
사용·갱신현재 program과 uniform location다른 프로그램·오타·최적화 제거 확인

OpenGL 3.3 core를 기준으로 한다. 컨텍스트와 함수 로더는 렌더 루프 글의 전제다.

위치와 색을 연결하는 두 셰이더

basic.vert는 다음과 같다. new vec4처럼 C# 생성 문법을 붙이지 않는다.

#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
out vec3 vertexColor;
uniform float offsetX;
void main() {
    vertexColor = color;
    gl_Position = vec4(position + vec3(offsetX, 0.0, 0.0), 1.0);
}

basic.frag에서 연결된 색에 tint를 곱한다.

#version 330 core
in vec3 vertexColor;
out vec4 fragColor;
uniform vec3 tint;
void main() {
    fragColor = vec4(vertexColor * tint, 1.0);
}

location 0·1의 CPU 입력 설정은 정점 배치 글과 맞춘다. 프로그램은 링크 성공 직후 uniform 값을 초기화한다. offsetX=0, tint=(1,1,1)이 기준이다.

로그를 읽고 실패한 객체를 정리하기

다음 함수는 GL 헤더를 포함한 C++ 파일에 넣는 컴파일 도우미다. 문자열 src의 길이가 GLint 범위를 넘지 않는 작은 예제를 전제로 한다.

#include <stdexcept>
#include <string>
#include <vector>

GLuint compileStage(GLenum type, const std::string& src) {
    GLuint shader = glCreateShader(type);
    if (!shader) throw std::runtime_error("glCreateShader failed");
    const char* text = src.c_str();
    glShaderSource(shader, 1, &text, nullptr);
    glCompileShader(shader);
    GLint ok = GL_FALSE;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
    if (!ok) {
        GLint size = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size);
        std::vector<char> log(size > 0 ? size : 1, '\0');
        glGetShaderInfoLog(shader, static_cast<GLsizei>(log.size()), nullptr, log.data());
        std::string message(log.data());
        glDeleteShader(shader);
        throw std::runtime_error(message);
    }
    return shader;
}

두 셰이더를 얻으면 glCreateProgram, glAttachShader 두 번, glLinkProgram 순서로 연결한다. 링크 확인은 glGetProgramiv(program, GL_LINK_STATUS, &ok)다. 실패 로그는 glGetProgramInfoLog로 읽는다. 성공 여부와 관계없이 만든 셰이더는 정리하고, 실패한 프로그램도 삭제한다. 컴파일 도중 두 번째 단계가 실패해도 첫 번째 셰이더를 누락하지 않도록 소유 객체나 try/catch로 감싼다.

uniform의 위치는 프로그램에 속한다

링크가 끝난 program에 다음을 실행한 뒤 draw한다.

glUseProgram(program);
GLint tintLocation = glGetUniformLocation(program, "tint");
GLint offsetLocation = glGetUniformLocation(program, "offsetX");
if (tintLocation < 0 || offsetLocation < 0)
    throw std::runtime_error("required uniform missing");
glUniform3f(tintLocation, 1.0f, 1.0f, 1.0f);
glUniform1f(offsetLocation, 0.2f);

location -1은 오타일 수도 있고 사용되지 않아 제거된 uniform일 수도 있다. -1에 대한 갱신은 무시되므로 필수 입력이라면 직접 오류로 취급한다. location은 다른 프로그램과 공유하지 않으며 재링크 후 다시 얻는다. glUniform은 현재 사용 중인 프로그램을 갱신한다. LearnOpenGL Shaders

실습과 풀이

  1. vec4 앞에 new를 붙인다. 풀이: 정점 셰이더 컴파일 단계의 실패를 기록하고 GLSL 생성자 문법으로 되돌린다. 드라이버별 로그 문구와 줄 번호는 같다고 가정하지 않는다.
  2. fragment 입력을 in vec2 vertexColor로 바꾼다. 풀이: 셰이더가 각각 컴파일되더라도 단계 간 인터페이스가 맞지 않아 링크가 실패할 수 있다. 두 단계의 타입을 vec3로 일치시킨다.
  3. tint를 tintColor라는 이름으로 조회한다. 풀이: -1을 필수 uniform 검사에서 잡아야 한다. 값을 갱신하지 않은 tint가 검은 화면의 원인이 될 수 있지만, 검은 화면의 모든 원인이 uniform인 것은 아니다.

확인한 범위

기존 노트의 단계와 공개 API 사용법을 대조해 코드를 새로 작성했다. 원문 번역이나 Shader 클래스 전체를 복제하지 않았다. C++·GLSL 컴파일, 드라이버 info log와 실제 색·이동 화면은 이번에 실행하지 않았다. 실습의 실패 지점은 검증할 예상이며 실측 로그가 아니다. AI를 작성 보조에 사용했다.

이어 읽기

전체 학습 순서 · 다음 글: 텍스처 객체·텍스처 유닛·sampler와 이미지 포맷

← 글 모아보기