개발/OpenGL / / 2022. 3. 29. 18:27

OpenGL 공부일지 - OpenGL Super Bible 버텍스 프로세싱 및 드로잉 커맨드 - 4

반응형

Storing Transformed Vertices

opengl에서는 버텍스, 테셀레이션 이벨류에이션, 지오메트리 쉐이더 등 결과를 하나 이상의 버퍼 객체에 저장하는 것이 가능하다. 이는 변환 피드백이라고 하고 프로그래밍이 가능하지는 않지만, 설정이 가능한 opengl 파이프라인의 고정 함수 스테이지이다. 

변환 피드백이 적용되면 현 쉐이더 파이프라인의 마지막 스테이지에서 나온 일련의 속성 출력이 하나의 버퍼 집합으로 저장된다.

 지오메트리 쉐이더가 없으면, 버텍스 쉐이더와 테셀레이션 이벨류에이션 쉐이더에 의해 처리된 버텍스들이 기록된다. 지오메트리 쉐이더가 존재하면, 생성된 버텍스가 저장되고, 가변크기 데이터도 기록될 수 있다. 

Using Transform Feedback

변환 피드백 설정을 위해 opengl에 프론트엔드의 출력 기록을 알려주어야하는데, 마지막 스테이지에서의 출력을 가끔 베어링(varying)이라고도 부른다. 

void glTransformFeedbackVaryings(GLuint program,
    GLsizei count,
    const GLchar * const * varying,
    GLenum bufferMode);

위 함수를 통해 opengl에 어떤것을 기록할 지 알려준다.

bufferMode를 통해 varying을 기록시 모드를 지정하여 단일 버퍼에 연속적 또는 각 베어링을 고유 버퍼에 기록하는 등 다양한 모드로 기록 가능하다.

out vec4 vs_position_out;
out vec4 vs_color_out;
out vec3 vs_normal_out;
out vec3 vs_binormal_out;
out vec3 vs_tangent_out;

위 버텍스 쉐이더 코드는 출력 베어링을 선언한다.

static const char * varying_names[] =
{
    "vs_position_out",
    "vs_color_out",
    "vs_normal_out",
    "vs_binormal_out",
    "vs_tangent_out"
};

const int num_varyings = sizeof(varying_names) /
    sizeof(varying_names[0]);

glTransformFeedbackVaryings(program,
    num_varyings,
    varying_names,
    GL_INTERLEAVED_ATTRIBS);

베어링 지정을 위해 위처럼 vs_position_out, vs_color_out 등 단일 인터리브 변환 피드백 버퍼에 저장한다.

변환 피드백 버퍼에는 버텍스 쉐이더 출력을 모두 저장하지 않아도 되고, 일부만 저장하고, 프래그먼트쉐이더에 전송할 수도 있다. 

glLinkProgram(program);

glTransformFeedbackVaryings()를 사용해서 새로운 변환 피드백 베어링을 지정하면 위 코드와 같이 프로그램 객체를 링크해야한다.

GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_TARNSFORM_FEEDBACK_BUFFER, buffer);
glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, size, NULL, GL_DYNAMIC_COPY);

 

 

기록 수행 전에 버퍼를 생성해서 인덱스된 변환 피드백 버퍼 바인딩 포인트에 바인딩 해야한다. 위와 같은 방식으로 데이터 지정 없이 버퍼 공간을 할당한다.

 

glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer);

변환 피드백 모드에 따라 저장된 버텍스 속성이 단일 버퍼에 저장될 수 있다. 이때 변환 피드백 데이터가 저장될 버퍼를 지정하기 위해 변환 피드백 바인딩 포인트 중 하나에 바인딩 한다. 

위 함수를 통해 버퍼를 인덱스된 바인딩 포인트에 바인딩할 수 있다.

 

위 그림은 변환 피드백 바인딩 포인트들의 관계이다. 변환 피드백 저장을 버퍼 지정을 위해 위와 같이 GL_TRANSFORM_FEEDBACK_BUFFER포인드가 여러개 있다.

 

void glBindBufferRange(GLenum target,
    GLuint index,
    GLuint buffer,
    GLintptr offset,
    GLsizeiptr size);

위 함수는 glBindBufferBase()의 발전된 버전으로, 버퍼의 일부만 골라 인덱스된 바인딩 포인트에 바인딩 가능하다. glBindBuffer(), glBindBufferBase()와 같은 함수는 그렇지 못하다.

특정 모드의 변환 피드백을 사용해서 출력 버텍스 속성을 단일 버터의 별도 부분에 쓸 수 있다. 애플리케이션이 모든 속성을 하나의 버텍스 버퍼로 합치고 0이 아닌 오프셋을 버퍼에 지정하면, 변환 피드백의 출력을 버텍스 쉐이더의 입력에 매핑시킬 수 있다.

 

 glTransformFeedbackVaryings()함수의 인자에 따라 데이터가 빈틈없이 패킹시키거나, 혹은 버텍스 쉐이더출력을 개별 버퍼에 기록할 수 있다.

 interleaved attribs 모드는 변환 피드백 버퍼에 저장가능한 베어링 최대 개수에는 제한이 없지만, 쓸 수 있는 최대 요소 개수에는 제한이 있다.

필요시 변환 피드백 버퍼에 저장된 출력 구조체 사이에 빈 공간을 추가할 수 있다. 

 

static const char * varying_names[] =
{
    "carrots",
    "peas",
    "gl_NextBuffer",
    "beans",
    "potatoes"
};

const int num_varyings = sizeof(varying_names) / sizeof(varying_names[0]);

glTransformFeedbackVaryings(program,
    num_varyings,
    varying_names,
    GL_INTERLEAVED_ATTRIBS);

일련의 출력 베어링을 하나의 버퍼에 인터리브 형태로 쓰며 동시에 일부 속성을 다른 버퍼에 쓰는 것도 가능하다. 이럴 때는 gl_NextBuffer라는 가상 베어링 이름을 사용한다.

위 코드를 수행하고 glLinkProgram()을 호출하면 변환 피드백 스테이지는 gl_NextBuffer 앞 값들은 첫 번째 변환 피드백 버퍼에, 뒤 값들은 두 번째 변환 피드백 버퍼에 쓴다.

 

Starting, Pausing, and Stopping Transform Feedback

void glBeginTransformFeedback(GLenum primitiveMode);

변환 피드백을 담을 버퍼가 바인딩되면, 위 함수를 통해 변환 피드백 모드를 활성화 시킬 수 있다.

 

인자에 따라서 primitive의 타입을 구분해서 출력할 수 있는 것 같다. 지오메트리 쉐이더의 유무나 테셀레이션 이벨류에이션 쉐이더 등이 있고 없고에 따라서도 모드를 다르게 하여 호출해야하는 모양이다.

 

void glPauseTransformFeedback();

변환 피드백 모드 활성화 후 출력을 변환 피드백 버퍼에 기록하는데 위 함수는 기록을 임시로 멈추는 역할을 한다.

void glResumeTransformFeedback();

반대로 위 함수는 멈췄던 기록을 다시 시작하는 역할을 한다.

glEndTransformFeedback();

위 함수는 변환 피드백 모드를 종료한다.

Ending the Pipeline with Transform Feedback

변환 피드백을 사용하는 애플리케이션에서, 버텍스를 저장하지만 그리지 않는 경우도 있다.

glEnable(GL_RASTERIZER_DISCARD);

변환 피드백은 opengl 파이프라인에서 래스터라이제이션 앞에 위치하므로 위 함수를 통해 래스터라이제이션을 끌 수 있다.

그러면 변환 피드백 후 프리미티브 처리를 하지 않아 렌더링되지 않는다.

glDisable(GL_RASTERIZER_DISCARD);

반대로 위 함수는 래스터라이저를 활성화 시킨다.

Transform Feedback Example: Physical Simulation

springmass라는 super bible의 예제를 보자. 스프링과 질량을 갖는 mesh의 물리 시뮬레이션이다.

각 버텍스는 무게를 갖고, 네 인접 버텍스와 고무줄로 연결된다. 

Texture Buffer Object를 사용해서 일반 속성 배열 외 버텍스 위치 데이터를 저장함으로써, 같은 버퍼가 TBO에도 바인딩되고 버텍스 쉐이더의 위치 입력에 해당하는 버텍스 속성에도 바인딩 된다. 그러면 다른 버텍스의 위치도 무작위로 접근 가능하다.

 

위치 배열의 요소는 vec4로 x,y,z로 좌표, w로 버텍스 가중치를 담는다. 속도 배열은 vec3로 표현한다. 각각을 연결하는 스프링에 대한 정보를 ivec4배열에 담는다.

예를들어 그림에서 버텍스 12는 <7, 13, 17, 11>로 연결 벡터가 이루어진다. 버텍스 14의 경우 <9, -1, 19, 13>으로 -1로 연결되지 않는 부분을 표현한다.

 

vmath::vec4 * initial_positions = new vmath::vec4 [POINTS_TOTAL];
vmath::vec3 * initial_velocities = new vmath::vec3 [POINTS_TOTAL];
vmath::ivec4 * connection_vectors = new vmath::ivec4 [POINTS_TOTAL];

int n = 0;

for (j = 0; j < POINTS_Y; j++)
{
    float fj = (float)j / (float)POINTS_Y;
    for (i = 0; i < POINTS_X; i++)
    {
        float fi = (float)i / (float)POINTS_X;
        
        initial_positions[n] = vmath::vec4((fi - 0.5f) * (float)POINTS_X,
            (fj - 0.5f) * (float)POINTS_Y,
            0.6f * sinf(fi) * cosf(fj),
            1.0f);
        initial_velocities[n] = vmath::vec3(0.0f);
        
        connection_vectors[n] = vmath::ivec4(-1);
        
        if (j != (POINTS_Y - 1))
        {
            if (i != 0)
                connection_vectors[n][0] = n - 1;
            if (j != 0)
                connection_vectors[n][1] = n - POINTS_X;
            if (i != (POINTS_X - 1))
                connection_vectors[n][2] = n + 1;
            if (j != (POINTS_Y - 1))
                connection_vectors[n][3] = n + POINTS_X;
    }
    n++;
    }
}

glGenVertexArrays(2, m_vao);
glGenBuffers(5, m_vbo);

for (i = 0; i < 2; i++)
{
    glBindVertexArray(m_vao[i]);
    
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo[POSITION_A + i]);
    glBufferData(GL_ARRAY_BUFFER,
        POINTS_TOTAL * sizeof(vmath::vec4),
        initial_positions, GL_DYNAMIC_COPY);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(0);
    
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo[VELOCITY_A + i]);
    glBufferData(GL_ARRAY_BUFFER,
        POINTS_TOTAL * sizeof(vmath::vec3),
        initial_velocities, GL_DYNAMIC_COPY);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo[CONNECTION]);
    glBufferData(GL_ARRAY_BUFFER,
        POINTS_TOTAL * sizeof(vmath::ivec4),
        connection_vectors, GL_STATIC_DRAW);
    glVertexAttribIPointer(2, 4, GL_INT, 0, NULL);
    glEnableVertexAttribArray(2);
}

delete [] connection_vectors;
delete [] initial_velocities;
delete [] initial_positions;

// Attach the buffers to a pair of TBOs
glGenTextures(2, m_pos_tbo);
glBindTexture(GL_TEXTURE_BUFFER, m_pos_tbo[0]);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_vbo[POSITION_A]);
glBindTexture(GL_TEXTURE_BUFFER, m_pos_tbo[1]);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_vbo[POSITION_B]);

위 코드에서 스프링-질량 시스템의 각 노드 초기 위치, 속도, 연결 벡터를 설정한다.

버텍스 속성을 사용해서 자신의 위치와 연결 벡터를 얻는 버텍스 쉐이더를 실행하고, 그 다음 연결 벡터의 요소로 TBO의 인덱스를 구하고, 인덱스를 통해 연결된 버텍스의 현재 위치를 얻어온다. 

 

쉐이더는 가상 스프링의 늘어난 길이는 계산한다. 적용되는 힘을 계산하고, 질량 가속도를 계산하고, 속도 벡터 등을 계산하여 적용한다.

 

탄성과 관련된 후크의 법칙을 사용하나, 물리 공식에 대해서는 자세히 다루지 않겠다.

 

#version 450 core

// This input vector contains the vertex position in xyz, and the
// mass of the vertex in w
layout (location = 0) in vec4 position_mass;
// This is the current velocity of the vertex
layout (location = 1) in vec3 velocity;
// This is our connection vector
layout (location = 2) in ivec4 connection;

// This is a TBO that will be bound to the same buffer as the
// position_mass input attribute
layout (binding = 0) uniform samplerBuffer tex_position;

// The outputs of the vertex shader are the same as the inputs
out vec4 tf_position_mass;
out vec3 tf_velocity;

// A uniform to hold the time-step. The application can update this.
uniform float t = 0.07;

// The global spring constant
uniform float k = 7.1;

// Gravity
const vec3 gravity = vec3(0.0, -0.08, 0.0);

// Global damping constant
uniform float c = 2.8;

// Spring resting length
uniform float rest_length = 0.88;

void main(void)
{
    vec3 p = position_mass.xyz; // p can be our position
    float m = position_mass.w; // m is the mass of our vertex
    vec3 u = velocity; // u is the initial velocity
    
    vec3 F = gravity * m - c * u; // F is the force on the mass
    bool fixed_node = true; // Becomes false when force is applied
    
    for (int i = 0; i < 4; i++)
    {
        if (connection[i] != -1)
        {
            // q is the position of the other vertex
            vec3 q = texelFetch(tex_position, connection[i]).xyz;
            vec3 d = q - p;
            float x = length(d);
            F += -k * (rest_length - x) * normalize(d);
            fixed_node = false;
        }
    }
    
    // If this is a fixed node, reset force to zero
    if (fixed_node)
    {
        F = vec3(0.0);
    }
    
    // Acceleration due to force
    vec3 a = F / m;
    
    // Displacement
    vec3 s = u * t + 0.5 * a * t * t;
    
    // Final velocity
    vec3 v = u + a * t;
    
    // Constrain the absolute value of the displacement per step
    s = clamp(s, vec3(-25.0), vec3(25.0));
    
    // Write the outputs
    tf_position_mass = vec4(p + s, m);
    tf_velocity = v;
}

위 코드는 프로젝트의 버텍스 쉐이더 내용이다.

쉐이더 실행을 위해, 버퍼로 들어갔던 각 버텍스마다 반복한다. 상세한 코드의 흐름을 모두 이해하기에는 벅차 다음에 다시 알아보도록 한다. 

 

https://download.hanbit.co.kr/exam/2204/

 

Index of /exam/2204

 

download.hanbit.co.kr

위 링크로 예제코드를 가져올 수 있다.

필자의 경우 sharder.load로 쉐이더파일을 불러오는 것이 안되는건지 아무것도 렌더링이 되지 않았다.

 

int i;
glUseProgram(m_update_program);

glEnable(GL_RASTERIZER_DISCARD);

for (i = iterations_per_frame; i != 0; --i)
{
    glBindVertexArray(m_vao[m_iteration_index & 1]);
    glBindTexture(GL_TEXTURE_BUFFER, m_pos_tbo[m_iteration_index & 1]);
        m_iteration_index++;
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
        m_vbo[POSITION_A + (m_iteration_index & 1)]);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1,
        m_vbo[VELOCITY_A + (m_iteration_index & 1)]);
    glBeginTransformFeedback(GL_POINTS);
    glDrawArrays(GL_POINTS, 0, POINTS_TOTAL);
    glEndTransformFeedback();
}

glDisable(GL_RASTERIZER_DISCARD);

위 코드는 물리 시뮬레이션을 반복하며 VAO와 TBO를 교환한다(이 교환한다는 개념에 대해서는 잘 알지 못한다).

루프의 반복으로 모든 노드의 위치와 속도를 갱신한다.

static const GLfloat black[] = { 0.0f, 0.0f, 0.0f, 0.0f };

glViewport(0, 0, info.windowWidth, info.windowHeight);
glClearBufferfv(GL_COLOR, 0, black);

glUseProgram(m_render_program);

if (draw_points)
{
    glPointSize(4.0f);
    glDrawArrays(GL_POINTS, 0, POINTS_TOTAL);
}

if (draw_lines)
{
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer);
    glDrawElements(GL_LINES, CONNECTIONS_TOTAL * 2,
        GL_UNSIGNED_INT, NULL);
}

위 코드는 이제 시스템의 점들을 렌더링하는 코드이다.

 

이러한 식으로 가시화하며 표현된다. 

opengl을 더 잘 공부해서 위 코드를 

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유