본문 바로가기
Opengles 3.0 with Android

Chapter 9.2 Gawsian Blur ( 2 step )

by SimonLee 2024. 12. 1.

 

가우시안 커널은 가우시안 분포(정규 분포)를 기반으로 한 값들의 집합으로, 이미지를 부드럽게(블러링) 만들거나 노이즈를 제거하는 데 사용됩니다. 커널의 크기와 형태는 가우시안 함수의 매개변수에 따라 달라집니다.

 

 

 

Blur를 2 pass로 구현한 내용

objLoader + phong shader로 원기둥을 fbo1->  textue1 렌더링 하고

horizontal blur 4 샘플링으로 fbo2 -> texture 2 렌더링을 할때, 바인딩은 texture1을 해주어 input texture로 사용한다.

그리고 texture2를 바인딩 하고 vertical blur 4 샘플링으로 블러를 적용하는 방식이다.

 

삽질 했던 부분은 texture2를 바인딩 하고 샘플러로 들어가는 부분을 제대로 설정하지 않아서

horizontal blur 적용 텍스처가 아닌, 원기둥으로 설정되어서 vertical blur만 적용되었던 문제였다.

 

1패스 가우시안 블러의 특징

  1. 작동 방식:
    • 2D 가우시안 커널을 직접 생성하여, 한 번의 렌더링 패스에서 이미지를 블러 처리합니다.
    • 픽셀의 모든 이웃 픽셀 값을 곱하고 합산하여 블러를 계산.
  2. 장점:
    • 한 번의 패스로 모든 작업이 완료되므로 렌더링 코드가 간결해질 수 있습니다.
    • 프레임 버퍼를 전환할 필요가 없습니다.
  3. 단점:
    • 계산 복잡도: 커널 크기2\text{커널 크기}^2 만큼의 계산량이 필요합니다. 예를 들어, 커널 크기가 7이면 49개의 샘플링이 필요.
    • 효율성 저하: GPU에서 큰 커널 크기를 처리하는 데 성능이 크게 떨어질 수 있음.
    • 비용이 큰 텍스처 접근: 2D 텍스처 샘플링이 많아질수록 메모리 접근 비용이 증가.

2패스 가우시안 블러의 특징

  1. 작동 방식:
    • 1D 가우시안 커널을 사용해 수평 방향 블러를 먼저 적용.
    • 그 결과를 입력으로 사용하여 수직 방향 블러를 적용.
    • 커널 크기가 nn이라면 각각 nn번의 샘플링을 수행하므로 총 2n2n번의 샘플링으로 완료.
  2. 장점:
    • 효율성: 2D 블러를 직접 계산하는 것보다 계산량이 크게 줄어듦. 예: 커널 크기가 7일 때 49개의 샘플링 대신 14개만 필요.
    • GPU 친화적: 작은 커널 크기를 반복 사용하므로 텍스처 접근 비용 감소.
    • 유연성: 큰 커널을 처리할 때도 효율적.
  3. 단점:
    • 프레임 버퍼를 두 번 사용해야 하므로 코드가 약간 복잡.
    • 두 번의 렌더링이 필요.

 

Initialize

// GaussianBlur 클래스: 가우시안 블러 초기화 함수 정의
void GaussianBlur::InitModel() {
    // 점 모델 초기화
    objModel->InitModel();

    // 텍스처 초기화 (수평 및 수직 모델 포함)
    textureQuad->InitModel();
    textureQuad->InitHorizontalModel();
    textureQuad->InitVerticalModel();

    // 프레임 버퍼 객체 생성
    GenerateBlurFBO1();
    GenerateBlurFBO2();

    // 투영 행렬 설정
    SetUpPerspectiveProjection();
}

// SimpleTexture 클래스: 수평 블러 모델 초기화
void SimpleTexture::InitHorizontalModel() {
    // 수평 블러 셰이더 초기화
    if (!(program = ProgramManagerObj->Program((char *)"HorizontalBlur"))) {
        LOGI("Init HorizontalBlur");
        program = ProgramManagerObj->ProgramInit((char *)"HorizontalBlur");
        ProgramManagerObj->AddProgram(program);
    }

    // 셰이더 로드 및 컴파일
    program->VertexShader = ShaderManager::ShaderInit(VERTEX_SHADER_PRG, GL_VERTEX_SHADER);
    program->FragmentShader = ShaderManager::ShaderInit(HORIZONTAL_BLUR_SHADER_PRG, GL_FRAGMENT_SHADER);

    // 버텍스 셰이더 로드
    CACHE* m = reserveCache(VERTEX_SHADER_PRG, true);
    if (m) {
        if (!ShaderManager::ShaderCompile(program->VertexShader, (char *)m->buffer, 1)) exit(1);
        freeCache(m);
    }

    // 프래그먼트 셰이더 로드
    m = reserveCache(HORIZONTAL_BLUR_SHADER_PRG, true);
    if (m) {
        if (!ShaderManager::ShaderCompile(program->FragmentShader, (char *)m->buffer, 1)) exit(2);
        freeCache(m);
    }

    // 셰이더 프로그램 링크
    if (!ProgramManagerObj->ProgramLink(program, 1)) exit(3);

    glUseProgram(program->ProgramID);

    // 셰이더 유니폼 변수 가져오기
    MVP = GetUniform(program, (char*)"ModelViewProjectionMatrix");
    TEX_HOR = GetUniform(program, (char*)"Tex1");
    GAUSSIAN_WEIGHT_HOR = GetUniform(program, (char*)"Weight[0]");
    PIXEL_OFFSET_HOR = GetUniform(program, (char*)"PixOffset[0]");
    SCREEN_COORD_X_HOR = GetUniform(program, (char*)"ScreenCoordX");
    PIXELSIZE = GetUniform(program, (char*)"pixelSize");

    // 화면 좌표 및 픽셀 크기 설정
    glUniform1f(SCREEN_COORD_X_HOR, TEXTURE_WIDTH / 2.0f);
    glUniform2f(PIXELSIZE, pixelScale, pixelScale);

    // 가우시안 가중치 계산
    float gWeight[FILTER_SIZE];
    float sum = 0;
    gWeight[0] = GaussianBlur::GaussianEquation(0, sigma) * scaleFactor;
    sum = gWeight[0];

    for (int i = 1; i < FILTER_SIZE; i++) {
        gWeight[i] = GaussianBlur::GaussianEquation(i, sigma) * scaleFactor;
        sum += 2.0f * gWeight[i];
    }

    for (int i = 0; i < FILTER_SIZE; i++) {
        gWeight[i] /= sum;
    }

    // 유니폼 변수에 가우시안 가중치 전달
    if (GAUSSIAN_WEIGHT_HOR >= 0) {
        glUniform1fv(GAUSSIAN_WEIGHT_HOR, FILTER_SIZE, gWeight);
    }

    // 픽셀 오프셋 설정
    float pixOffset[FILTER_SIZE];
    for (int i = 0; i < FILTER_SIZE; i++) {
        pixOffset[i] = static_cast<float>(i);
    }

    if (PIXEL_OFFSET_HOR >= 0) {
        glUniform1fv(PIXEL_OFFSET_HOR, FILTER_SIZE, pixOffset);
    }

    // 텍스처 유니폼 바인딩
    glUniform1i(TEX_HOR, 0);
}

// SimpleTexture 클래스: 수직 블러 모델 초기화
void SimpleTexture::InitVerticalModel() {
    LOGI("Init VerticalBlur");

    // 수직 블러 셰이더 초기화
    if (!(program = ProgramManagerObj->Program((char *)"VerticalBlur"))) {
        program = ProgramManagerObj->ProgramInit((char *)"VerticalBlur");
        ProgramManagerObj->AddProgram(program);
    }

    // 셰이더 로드 및 컴파일
    program->VertexShader = ShaderManager::ShaderInit(VERTEX_SHADER_PRG, GL_VERTEX_SHADER);
    program->FragmentShader = ShaderManager::ShaderInit(VERTICAL_BLUR_SHADER_PRG, GL_FRAGMENT_SHADER);

    CACHE* m = reserveCache(VERTEX_SHADER_PRG, true);
    if (m) {
        if (!ShaderManager::ShaderCompile(program->VertexShader, (char *)m->buffer, 1)) exit(1);
        freeCache(m);
    }

    m = reserveCache(VERTICAL_BLUR_SHADER_PRG, true);
    if (m) {
        if (!ShaderManager::ShaderCompile(program->FragmentShader, (char *)m->buffer, 1)) exit(2);
        freeCache(m);
    }

    // 셰이더 프로그램 링크
    if (!ProgramManagerObj->ProgramLink(program, 1)) exit(3);

    glUseProgram(program->ProgramID);

    // 셰이더 유니폼 변수 가져오기
    MVP = GetUniform(program, (char*)"ModelViewProjectionMatrix");
    TEX_VERT = GetUniform(program, (char*)"Tex1");
    GAUSSIAN_WEIGHT_VERT = GetUniform(program, (char*)"Weight[0]");
    PIXEL_OFFSET_VERT = GetUniform(program, (char*)"PixOffset[0]");
    SCREEN_COORD_X_VERT = GetUniform(program, (char*)"ScreenCoordX");
    PIXELSIZE = GetUniform(program, (char*)"pixelSize");

    // 화면 좌표 및 픽셀 크기 설정
    glUniform1f(SCREEN_COORD_X_VERT, TEXTURE_WIDTH / 2.0f);
    glUniform2f(PIXELSIZE, pixelScale, pixelScale);

    // 가우시안 가중치 계산
    float gWeight[FILTER_SIZE];
    float sum = 0;
    gWeight[0] = GaussianBlur::GaussianEquation(0, sigma) * scaleFactor;
    sum = gWeight[0];

    for (int i = 1; i < FILTER_SIZE; i++) {
        gWeight[i] = GaussianBlur::GaussianEquation(i, sigma) * scaleFactor;
        sum += 2.0f * gWeight[i];
    }

    for (int i = 0; i < FILTER_SIZE; i++) {
        gWeight[i] /= sum;
    }

    // 유니폼 변수에 가우시안 가중치 전달
    if (GAUSSIAN_WEIGHT_VERT >= 0) {
        glUniform1fv(GAUSSIAN_WEIGHT_VERT, FILTER_SIZE, gWeight);
    }

    // 픽셀 오프셋 설정
    float pixOffset[FILTER_SIZE];
    for (int i = 0; i < FILTER_SIZE; i++) {
        pixOffset[i] = static_cast<float>(i);
    }

    if (PIXEL_OFFSET_VERT >= 0) {
        glUniform1fv(PIXEL_OFFSET_VERT, FILTER_SIZE, pixOffset);
    }

    // 텍스처 유니폼 바인딩
    glUniform1i(TEX_VERT, 3);
}

 

 

Renderer

// GaussianBlur 클래스: 가우시안 블러 렌더링 함수 정의
void GaussianBlur::Render() {
    // 원본 객체 렌더링
    RenderObj();

    // 뷰포트 설정 및 텍스처 활성화
    glViewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureId);

    // 직교 투영 설정
    SetUpOrthoProjection();

    // 수평 블러 렌더링
    RenderHorizontalBlur();

    // 수평 블러 완료 대기
    glFinish();

    // 수직 블러 렌더링
    RenderVerticalBlur();
}

// GaussianBlur 클래스: 가우시안 분포 함수
float GaussianBlur::GaussianEquation(float val, float sigma) {
    // 가우시안 방정식: e^(-(x^2)/(2*sigma^2)) / (sqrt(2π) * sigma)
    double coefficient = 1.0 / (2.0 * PI * sigma);  // 계수 부분
    double exponent = -(val * val) / (2.0 * sigma); // 지수 부분
    return static_cast<float>(coefficient * exp(exponent));
}

// GaussianBlur 클래스: 원본 객체 렌더링
void GaussianBlur::RenderObj() {
    // 현재 바인딩된 프레임버퍼 ID 저장
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &CurrentFbo);

    // 프레임버퍼 1 바인딩
    glBindFramebuffer(GL_FRAMEBUFFER, blurFboId1);
    glViewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);

    // 컬러 및 깊이 버퍼 초기화
    glClearColor(1.0, 1.0, 1.0, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 텍스처 연결
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureId, 0);

    // 객체 렌더링
    objModel->Render();

    // 이전 프레임버퍼 복원
    glBindFramebuffer(GL_FRAMEBUFFER, CurrentFbo);
}

// GaussianBlur 클래스: 수평 블러 렌더링
void GaussianBlur::RenderHorizontalBlur() {
    // 깊이 테스트 비활성화
    glDisable(GL_DEPTH_TEST);

    // 프레임버퍼 2 바인딩
    glBindFramebuffer(GL_FRAMEBUFFER, blurFboId2);
    glViewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);

    // 텍스처 연결
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId2, 0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureId);

    // 수평 블러 셰이더 적용
    program = textureQuad->ApplyShader(HorizontalBlurShader);

    // 쿼드 렌더링
    textureQuad->Render();
}

// GaussianBlur 클래스: 수직 블러 렌더링
void GaussianBlur::RenderVerticalBlur() {
    // 기본 프레임버퍼로 전환
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);

    // 텍스처 연결
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, textureId2);

    // 수직 블러 셰이더 적용
    program = textureQuad->ApplyShader(VerticalBlurShader);

    // 쿼드 렌더링
    textureQuad->Render();
}

 

Phong Shader

#version 300 es
precision mediump float;

// Material property
uniform vec3    MaterialAmbient;
uniform vec3    MaterialSpecular;
uniform vec3    MaterialDiffuse;

// Light property
uniform vec3    LightAmbient;
uniform vec3    LightSpecular;
uniform vec3    LightDiffuse;
uniform float   ShininessFactor;


uniform vec3    LightPosition;
in vec3 normalCoord;
in vec3 eyeCoord;
in vec3 ObjectCoord;
layout(location = 0) out vec4 FinalColor;

vec3 normalizeNormal, normalizeEyeCoord, normalizeLightVec, V, R, ambient, diffuse, specular;
float sIntensity, cosAngle;

vec3 PhongShading()
{
    normalizeNormal   = normalize( normalCoord );
    normalizeEyeCoord = normalize( eyeCoord );
    normalizeLightVec = normalize( LightPosition - eyeCoord );
    
    // Diffuse Intensity
    cosAngle = max( 0.0, dot( normalizeNormal, normalizeLightVec ));

    // Specular Intensity
    V = -normalizeEyeCoord; // Viewer's vector
    R = reflect( -normalizeLightVec, normalizeNormal ); // Reflectivity
    sIntensity 	= pow( max( 0.0, dot( R, V ) ), ShininessFactor );

    // ADS color as result of Material & Light interaction
    ambient    = MaterialAmbient  * LightAmbient;
    diffuse    = MaterialDiffuse  * LightDiffuse;
    specular   = MaterialSpecular * LightSpecular;

    return ambient + ( cosAngle * diffuse ) + ( sIntensity * specular );
}


void main() {
    float Side      = 0.30;
    float DotSize   = 0.13;
    vec3 Cube       = vec3(Side, Side, Side);
    vec3 RenderColor= vec3(0.0, 0.0, 0.0);

    // Front face Model(mesh) color
    vec3 ModelColor = vec3(1.0, 1.0, 1.0);
    // Front face Model(mesh) color
    vec3 DotColor   = vec3(0.4, 0.5, 1.0);

    // Back face Model(mesh) color
    vec3 BackSideModelColor = vec3(0.0, 1.0, 0.0);
    // Back face polka dot color
    vec3 BackSideDotColor   = vec3(1.0, 1.0, 1.0);

    float insideCircle, length;
    vec3 position = mod(ObjectCoord, Cube) - Cube/2.0;
    length        = sqrt( position.x * position.x + position.y * position.y +position.z * position.z );
    //length        = length( position );
    insideCircle  = step(length,DotSize);
    
    // Determine final render output color based on front and back shading
    if (gl_FrontFacing){
        RenderColor  = vec3(mix(ModelColor, DotColor, insideCircle));
        FinalColor = vec4(RenderColor * PhongShading() , 1.0);
        //FinalColor = vec4(RenderColor, 1.0);
        
    }
    else{
        RenderColor  = vec3(mix(BackSideModelColor, BackSideDotColor, insideCircle));
        FinalColor = vec4(RenderColor * PhongShading(), 1.0);
        //FinalColor = vec4(RenderColor, 1.0);
    }
}

 

Phong Vertex Shader

#version 300 es
// Vertex information
layout(location = 0) in vec4  VertexPosition;
layout(location = 1) in vec3  Normal;

// Model View Project matrix
uniform mat4    ModelViewProjectionMatrix;
uniform mat4    ModelViewMatrix;
uniform mat3    NormalMatrix;

out vec3    normalCoord;
out vec3    eyeCoord;
out vec3    ObjectCoord;

void main()
{
    normalCoord = NormalMatrix * Normal;
    eyeCoord    = vec3 ( ModelViewMatrix * VertexPosition );
    ObjectCoord = VertexPosition.xyz;
    gl_Position = ModelViewProjectionMatrix * VertexPosition;
}

 

Blur Horizontal

#version 300 es
precision mediump float;

// 텍스처 좌표 및 유니폼 변수 선언
in vec2 TexCoord;
uniform vec2 pixelSize;            // 픽셀 크기
uniform sampler2D Tex1;            // 입력 텍스처
uniform float ScreenCoordX;        // 화면 중간 좌표
uniform float PixOffset[5];        // 픽셀 오프셋
uniform float Weight[5];           // 가우시안 가중치

// 출력 색상
layout(location = 0) out vec4 outColor;

void main() {
    // 화면 중심 근처에 빨간 선 그리기
    if (gl_FragCoord.x < ScreenCoordX + 10.0 && gl_FragCoord.x > ScreenCoordX - 10.0) {
        outColor = vec4(1.0, 0.0, 0.0, 1.0); // 빨간색
        return;
    }

    if (gl_FragCoord.x > ScreenCoordX) {
        // 텍스처 샘플링 및 가우시안 블러 계산
        float dx = pixelSize.x; // 텍스처 좌표에서 x 방향 픽셀 크기
        vec4 sum = vec4(0.0);   // 블러 결과 초기화

        // 중앙 픽셀에 가중치 적용
        sum += texture(Tex1, TexCoord) * Weight[0];

        // 주변 픽셀에 가우시안 가중치 적용 (대칭)
        for (int i = 1; i < 5; i++) {
            sum += texture(Tex1, TexCoord + vec2(PixOffset[i], 0.0) * dx) * Weight[i];
            sum += texture(Tex1, TexCoord - vec2(PixOffset[i], 0.0) * dx) * Weight[i];
        }

        // 블러 결과 출력
        outColor = sum;
    } else {
        // 화면 중심 이전에는 원본 텍스처 출력
        outColor = texture(Tex1, TexCoord);
    }
}

 

Blur Vertical

#version 300 es
precision mediump float;

// 텍스처 좌표 및 유니폼 변수 선언
in vec2 TexCoord;
uniform vec2 pixelSize;            // 픽셀 크기
uniform sampler2D Tex1;            // 입력 텍스처
uniform float ScreenCoordX;        // 화면 중간 좌표
uniform float PixOffset[5];        // 픽셀 오프셋
uniform float Weight[5];           // 가우시안 가중치

// 출력 색상
layout(location = 0) out vec4 outColor;

void main() {
    // 화면 중심 근처에 빨간 선 그리기
    if (gl_FragCoord.x < ScreenCoordX + 10.0 && gl_FragCoord.x > ScreenCoordX - 10.0) {
        outColor = vec4(1.0, 0.0, 0.0, 1.0); // 빨간색
        return;
    }

    if (gl_FragCoord.x > ScreenCoordX) {
        // 텍스처 샘플링 및 가우시안 블러 계산
        float dy = pixelSize.y; // 텍스처 좌표에서 y 방향 픽셀 크기
        vec4 sum = texture(Tex1, TexCoord) * Weight[0]; // 중심 픽셀에 가중치 적용

        // 주변 픽셀에 가우시안 가중치 적용 (대칭)
        for (int i = 1; i < 5; i++) {
            sum += texture(Tex1, TexCoord + vec2(0.0, PixOffset[i]) * dy) * Weight[i];
            sum += texture(Tex1, TexCoord - vec2(0.0, PixOffset[i]) * dy) * Weight[i];
        }

        // 블러 결과 출력
        outColor = sum;
    } else {
        // 화면 중심 이전에는 원본 텍스처 출력
        outColor = texture(Tex1, TexCoord);
    }
}

 

 

Blur Vertex Shader

#version 300 es

// 버텍스 셰이더: 정점 정보
layout(location = 0) in vec3 VertexPosition;    // 정점 위치
layout(location = 1) in vec2 VertexTexCoord;    // 텍스처 좌표

// 텍스처 좌표 출력
out vec2 TexCoord;

// 유니폼 변수
uniform mat4 ModelViewProjectionMatrix;        // 모델-뷰-투영 행렬

void main(void) {
    // 텍스처 좌표 전달
    TexCoord = VertexTexCoord;

    // 모델-뷰-투영 행렬을 이용한 정점 위치 변환
    vec4 glPos = ModelViewProjectionMatrix * vec4(VertexPosition, 1.0);

    // 정점 위치의 방향 계산 (X, Y의 부호를 구함)
    vec2 Pos = sign(glPos.xy);

    // 최종 정점 위치 설정
    gl_Position = ModelViewProjectionMatrix * vec4(VertexPosition, 1.0);
}

'Opengles 3.0 with Android' 카테고리의 다른 글

9_4 엠보싱 효과  (0) 2024.12.29
9.3 툰 셰이딩: 엣지 디텍팅과 카툰화  (0) 2024.12.25
Chapter 7.5. displace map  (0) 2024.11.18
Chapter 7.4 FBO  (0) 2024.11.17
Chapter 3.5 Primitive Restart  (2) 2024.10.31