블룸 효과란?
위 사진 처럼 멋있는 조명효과를 만들어낸다.
단계
위 그림과 같이
모든 픽셀에 대해서 밝은 픽셀은 그대로 두고, 어두운 픽셀을
아예 더 어둡게 만들어야 한다.
이제 가우시안 블러를 적용하는게 두 번째 단계이다.
가우시안 블러를 적용해서, 밝은색들이 주변에 퍼져나가는
느낌을 만들어야 한다.
이제, 원본이미지와 단계를 거쳐서 만든 이미지를 그저
더해주면 블룸 효과를 만들어낼 수 있다.
코드로 만들어보자
void Image::Bloom(const float& th, const int& numRepeat, const float& weight)
일단, Bloom이란 메소드를 만들 것이다.
이때 들어가는 파라메터를 각각 먼저 설명하겠다.
th . 밝은 픽셀인지 어두운 픽셀인지 구분 짓는 인자다.
numRepeat. 가우시안 블러를 몇 번 반복해서 사용할지에
대한 인자다.
weight - 효과에 대한 가중치 값이다. 약하게 주고 싶다면, 낮게 블룸효과를 더 주고 싶다면 1.0 과 가깝게 설정하는 인자다.
자, 우선 그럼 코드를 생각해보자.
가장 먼저, 어두운 픽셀은 어둡게 밝은 픽셀은 더 밝게 해줘야 한다.
우선 인자로 들어온 th보다 낮은 픽셀은 0.0 ( 검은색 )으로 바꿔준다.
그리고 픽셀의 밝기를 결정할 때는, 여러가지 방법들을 사용하지만, 가장 보편적인 방법은
Relative Luminance 을 사용하는 방법이다.
간단히 요약하자면, RGB색상 모델에서 각각의 색상 채널
빨강(R), 녹색 (G), 파랑 (B)을 나타내며, 각 채널은
0~255까지 값으로 표현한다. 이 때 상대적 밝기를
다음과 같이 계산한다.
- 각 채널 값을 선형공식에 따라 변환하여 각 채널의 값을 0~1의 사이 값으로 변환한다.
- 변환된 R,G,B값에 상대적 밝기에 영향을 미치는 가중치를 곱한다.
- 가중치에 적용된 값을 합산하여 상대적 밝기를 계산한다.
가중치는 위와 같은데. 위 공식은 과학자들이 실험을 통해
도출해낸, 공식이라고 한다. 녹색 성분이 강하면 더 강하다고 느끼고 반대로 파랑색 성분은 아주 작은 것을 알 수 있다.
자, 그럼 앞서 설명한 이론대로 적용할려면 먼저, 이미지의 RGB 값은 선형공식을 통해 0 ~ 1로 바꿔 줘야 한다.
이전 학습노트에선 따로 설명하진 않았지만, 이미 이미지를 불러올 때 해당 선형공식을 통해
불러오고 있었다.
채널 값을 선형공식에 따라 0~1로 변환
void Image::ReadFromFile(const char* filename) { /* vcpkg install stb:x64-windows 프로젝트 설정에서 _CRT_SECURE_NO_WARNINGS 추가 ('sprintf' in stb_image_write.h) #define STB_IMAGE_IMPLEMENTATION #include <stb_image.h> #define STB_IMAGE_WRITE_IMPLEMENTATION #include <stb_image_write.h> */ unsigned char* img = stbi_load(filename, &width, &height, &channels, 0); if (width) { std::cout << width << " " << height << " " << channels << std::endl; } else { std::cout << "Error: reading " << filename << " failed." << std::endl; } // channels가 3(RGB) 또는 4(RGBA)인 경우만 가정 // unsigned char(0에서 255)을 4채널 float(0.0f에서 1.0f)로 변환 pixels.resize(width * height); for (int i = 0; i < width * height; i ++) { pixels[i].v[0] = img[i * channels] / 255.0f; pixels[i].v[1] = img[i * channels +1] / 255.0f; pixels[i].v[2] = img[i * channels +2] / 255.0f; pixels[i].v[3] = 1.0f; } delete [] img; }
이미지를 stbi_load를 통해 불러오게 되면, unsigned char 주소값을 저장한 형태로.
각각의 주소에 rgb 값이 저장되어 있었다. 이것을 255f로 나누면, 선형공식에 따라 0~1로
바꿀 수 있게 된다.
위 사진처럼 직접 디버깅을 통해. 값이 어떻게 들어오고 변환되는지를 확인하니 이해하기 쉬웠다.
변환된 rgb 값에 가중치를 적용.
Vec4 LuminanceY{ 0.2126f,0.7152f,0.0722,0.0f }; for (int j = 0; j < height; j ++) for (int i = 0; i < width; i++) { auto& c = this->GetPixel(i, j); const float relativeLuminance = c.v[0] * LuminanceY.v[0] + c.v[1] * LuminanceY.v[1]+ c.v[2] * LuminanceY.v[2]; if (relativeLuminance < th) { c.v[0] = 0.0f; c.v[1] = 0.0f; c.v[2] = 0.0f; } }
앞서 루미넨스 공식을 모든 픽셀에 더해주어야 하는데 th보다 낮을 경우 강제로 0으로 ( 어둡게 )
설정해준다.
중간결과
그럼 아까 이론에서 보았던 이미지와 같은 결과값이 나온다.
가중치에 적용된 값을 합산하여 상대적 밝기를 적용한다.
일단 위 단계를 적용하기 전에, 가우시안 블러를 통해. 밝은색들이 주변에 퍼져나가도록 해줘야 한다.
//// 밝은 부분만 Blur for (int i = 0; i < numRepeat; i++) { this->GaussianBlur5(); }
중간결과
마지막으로 가중치를 곱해서 강도를 조절해주고. 원본 이미지를 더해준다.
//// 밝은 부분만 Blur한 것과 원본 이미지를 더하기 (밝은 부분 Blur에 weight 곱해서 강도 조절) for (int i = 0; i < pixelsBackup.size(); i++) { this->pixels[i].v[0] = std::clamp(pixels[i].v[0] * weight + pixelsBackup[i].v[0], 0.0f, 1.0f); this->pixels[i].v[1] = std::clamp(pixels[i].v[1] * weight + pixelsBackup[i].v[1], 0.0f, 1.0f); this->pixels[i].v[2] = std::clamp(pixels[i].v[2] * weight + pixelsBackup[i].v[2], 0.0f, 1.0f); }
최종적으로 위 사진과 같이 블룸효과가 적용되었다.
마치며
이로써, 드디어 이미지 처리 기초 부분이 끝이 났다.
그래픽스를 수업 하면서, 이미지 픽셀 값에 수학적 공식을 더해줌으로 써, 다양한 효과를 낼 수 있다는 것이 신기하면서도, 재미가 있었다.
해당 내용은 아무래도 유료 강의이다 보니, 자세한 내용은 아래 출처를 통해 알아보도록 하자.
출처 : 홍정모 그래픽스 새싹강의1 편.