OpenGL을 사용한 두 텍스처 혼합

이 튜토리얼에서는 OpenGL을 사용하여 두 개의 텍스처를 혼합하는 방법을 알아봅니다. 두 텍스처의 크기가 같아야 하며, 동일한 색상 깊이를 가져야 합니다. 예를 들어, 하나는 24비트 RGB이고 다른 하나는 32비트 RGBA인 경우, 텍스처 데이터를 로드할 때 동일한 레이어 수를 사용해야 합니다.

이 과정은 두 개의 샘플러를 사용하여 각 텍스처에서 샘플링한 후, 이 샘플링된 색상을 혼합하는 방식으로 진행됩니다.


#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

// 화면 설정
const unsigned int SCREEN_WIDTH = 800;
const unsigned int SCREEN_HEIGHT = 600;

unsigned int VAO, VBO, EBO;
unsigned int bottomTextureID, topTextureID;
int shaderProgram;

// 셰이더 로드 및 컴파일 함수
void initShader(const char* vertexPath, const char* fragmentPath) {
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;

    vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);

    try {
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    } catch (const std::exception& e) {
        std::cerr << "SHADER READ FILE FAILED: " << e.what() << std::endl;
    }

    const char* vShaderCode = vertexCode.c_str();
    const char* fShaderCode = fragmentCode.c_str();

    // 버텍스 셰이더 컴파일
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vShaderCode, NULL);
    glCompileShader(vertexShader);
    // 오류 확인
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cerr << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // 프래그먼트 셰이더 컴파일
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fShaderCode, NULL);
    glCompileShader(fragmentShader);
    // 오류 확인
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cerr << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // 셰이더 프로그램 링크
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // 오류 확인
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cerr << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

// 텍스처 로드 함수
void loadTexture(unsigned int& texture, const char* imagePath, GLenum textureUnit) {
    glGenTextures(1, &texture);
    glActiveTexture(textureUnit);
    glBindTexture(GL_TEXTURE_2D, texture);

    // 텍스처 매핑 설정
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 이미지 로드
    stbi_set_flip_vertically_on_load(true);
    int width, height, nrChannels;
    unsigned char* data = stbi_load(imagePath, &width, &height, &nrChannels, 0);
    if (data) {
        GLenum format = GL_RGB;
        if (nrChannels == 4)
            format = GL_RGBA;
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        std::cerr << "Failed to load texture: " << imagePath << std::endl;
    }
    stbi_image_free(data);
}

// 렌더링 함수
void render() {
    glBindVertexArray(VAO);
    glUseProgram(shaderProgram);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glUseProgram(0);
    glBindVertexArray(0);
}

// 초기화 함수
void initialize(const char* bottomImagePath, const char* topImagePath) {
    // 버텍스 데이터 (위치, 텍스처 좌표)
    float vertices[] = {
        // 위치          // 텍스처 좌표
        1.0f,  1.0f, 0.0f, 1.0f, 1.0f, // 상단 우측
        1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // 하단 우측
       -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // 하단 좌측
       -1.0f,  1.0f, 0.0f, 0.0f, 1.0f  // 상단 좌측
    };
    // 인덱스 데이터
    unsigned int indices[] = {
        0, 1, 3, // 첫 번째 삼각형
        1, 2, 3  // 두 번째 삼각형
    };

    // VAO, VBO, EBO 생성
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 텍스처 로드
    loadTexture(bottomTextureID, bottomImagePath, GL_TEXTURE0); // 기본 텍스처
    loadTexture(topTextureID, topImagePath, GL_TEXTURE1);    // 혼합될 텍스처

    // 셰이더 설정
    glUseProgram(shaderProgram);
    glUniform1i(glGetUniformLocation(shaderProgram, "textureBottom"), 0); // GL_TEXTURE0
    glUniform1i(glGetUniformLocation(shaderProgram, "textureTop"), 1);    // GL_TEXTURE1

    // 버텍스 속성 설정
    // 위치 속성
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 텍스처 좌표 속성
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

// 입력 처리 함수
void processInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// 윈도우 크기 변경 콜백
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

int main() {
    // GLFW 초기화
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // GLFW 윈도우 생성
    GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Texture Mixing", NULL, NULL);
    if (window == NULL) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // GLAD 초기화
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cerr << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // 셰이더 및 초기화
    initShader("vertexShader.glsl", "fragmentShader.glsl");
    initialize("./bottom_texture.jpg", "./top_texture.bmp"); // 실제 이미지 경로로 변경

    // 렌더링 루프
    while (!glfwWindowShouldClose(window)) {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        render();

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 종료
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glfwTerminate();
    return 0;
}

#version 330 core
out vec4 FragColor;
in vec2 TexCoord;

uniform sampler2D textureBottom;
uniform sampler2D textureTop;

void main() {
    vec4 colorBottom = texture(textureBottom, TexCoord);
    vec4 colorTop = texture(textureTop, TexCoord);

    // 두 텍스처를 혼합합니다.
    // mix(a, b, factor) = a * (1 - factor) + b * factor
    // 여기서는 top 텍스처를 더 많이 보이도록 factor를 조정했습니다.
    FragColor = mix(colorTop, colorBottom, 0.7);
    // 또는 알파 블렌딩을 사용하여 투명도를 적용할 수도 있습니다.
    // FragColor = colorTop * 0.6 + colorBottom * 1.0;
}

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 texCoord;

out vec2 TexCoord;

void main() {
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    TexCoord = texCoord;
}

예시로 강아지 이미지와 무지개 이미지를 사용할 수 있습니다. 무지개 이미지는 투명도(알파 채널)를 가지고 있다면, 다음과 같이 간단한 픽셀 덧셈으로도 혼합 효과를 낼 수 있습니다:

FragColor = texture(textureTop, TexCoord) * 0.6 + texture(textureBottom, TexCoord) * 1.0;

mix 함수는 두 색상을 보간하는 데 사용되며, blend 함수도 텍스처 혼합에 활용될 수 있습니다.

태그: OpenGL 텍스처 GLSL 셰이더 그래픽스

8월 2일 00:09에 게시됨