bloom特效

      由于之前在各种场合看到别人贴出的bloom特效做的图片,一开始还以为是用的HDR技术,后来一研究才发现绝大部分都仅仅是一个bloom特效而已,遂打算学习一番。其实bloom是一个非常简单的后期图像处理过程,之所以称其为图像处理过程,是因为它是一种可以在图片生成完毕后再使用的后处理过程。那么它到底是什么样的一种过程呢?简单地说就是:

Step1. 先对图片每一像素点进行一个亮度值检测,若大于某一个阈值就保留其原始颜色值,否则置为黑色;

Step2. 对上一步结果做高斯模糊;

Step3. 将模糊后的图片和原图片做一个加权和(权值视具体情况而定);

 

这里面涉及到的几个关键知识点有必要简单地说一下:

1.所谓一个像素的亮度值是什么?

亮度值并不是(R+G+B)/3,而是另外一种颜色格式(YUV颜色格式)中的Y值,YUV格式是欧洲电视系统所采用的一种颜色编码方式,其中"Y"表示明亮度,"U"和"V"表示色彩和饱和度。YUV格式在图像处理中运用广泛,比如大家耳熟能详的灰度图其实就是存放的每个像素对应的"Y"值。YUV和RGB之间有一个线性的转换关系:

Y=0.299R+0.587G+0.114B;

U=-0.147R-0.289G+0.436B;

V=0.615R-0.515G-0.100B;

可见G的值对亮度的贡献最大,而B贡献最小,这也符合人类视觉系统对于亮度的敏感程度值,因此Step1中的亮度值检测实际就是判断(0.299R+0.587G+0.114B)是否大于一个既定阈值。

2.高斯模糊是什么?

图像模糊的原理很简单,就是一张图片中每个像素都取周边一定范围内像素的加权和,最简单的权值设置方法就是均匀求权,即每个像素是这个范围内所有像素的平均值,如果范围是3*3,那么权值矩阵为:,而高斯模糊则是利用二维正态分布来生成这个权值矩阵,比如设二维正态分布函数为G(x,y),我们可以让矩阵正中心那个点值为G(0,0),中心偏左的点为G(-1,0),以此类推,3*3的权值矩阵可以是:。当然,这里只是随便举个例子,实际运用中肯定会比这个复杂得多,因为还要涉及到方差σ的选取等问题。实际上高斯模糊假设了在图像空间中像素的变化是缓慢的,因此相邻像素之间的变化不会太明显,但是随机的两个像素间就可能形成很大的像素差,这样也使高斯模糊能在保留信号的条件下减小噪声。但是这种方法在接近物体边缘的时候就无效了,因为图像中物体边缘两个点的颜色差值往往会比较大,所以高斯模糊会把边缘平滑掉。而另一种双边滤波方式则不会把边缘磨平,但是在bloom中,恰恰相反,我们希望得到一种看起来"颜色外溢"的效果,所以高斯模糊是个理想的选择。

      只要对以上两点了解后,编写一个bloom特效程序简直就是分分钟的事了,下面是我用OPENCV写的一个对图片进行bloom处理的简单程序:

#include <cv.h>
#include <highgui.h>

#define THRESHOLD_COLOR 0x38

#define A_VALUE 1.0f

#define B_VALUE 1.0f

IplImage *img;

int main(int argc,char** argv) {

 IplImage *img = cvLoadImage("chinesedragon.jpg"); 

 cvNamedWindow("Image-in",CV_WINDOW_AUTOSIZE); 

 //先显示原jpg图 

 cvShowImage("Image-in",img);     // 灰度化

 IplImage *imggrey=cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1); 

 cvCvtColor(img,imggrey,CV_BGR2GRAY);

 cvShowImage("Image-grey",imggrey); 

 IplImage *tmp=cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3); 

 cvZero(tmp);

 for (int h=0;h<tmp->height;h++)  { 

  uchar *p=(uchar*)(imggrey->imageData+h*imggrey->widthStep); 

  uchar *ptr_s = cvPtr2D(img, h, 0, NULL);

  uchar *ptr_d = cvPtr2D(tmp, h, 0, NULL);

  for (int w=0;w<tmp->width;w++)   { 

    if (p[w] >= THRESHOLD_COLOR)    {

    ptr_d[w*3] = ptr_s[w*3];

    ptr_d[w*3+1] = ptr_s[w*3+1];

    ptr_d[w*3+2] = ptr_s[w*3+2];

    }        

  }     

}

 cvShowImage("Image-threshold",tmp); 

 //分配空间存储处理后的图像 

 IplImage *blur=cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3); 

 cvSmooth(tmp,blur,CV_GAUSSIAN,7,7); 

 cvShowImage("Image-gaussianblur",blur); 

 // 图像相加

 for (int h=0;h<tmp->height;h++)  { 

  uchar *ptr_s = cvPtr2D(img, h, 0, NULL);

  uchar *ptr_d = cvPtr2D(blur, h, 0, NULL);

  for (int w=0;w<tmp->width;w++)   {

    unsigned int r1 = A_VALUE * ptr_s[w*3] + B_VALUE * ptr_d[w*3];// * ptr_d[w*3] / 0xff;

    if (r1 > 0xff) r1 = 0xff;

    ptr_d[w*3] = r1;

    unsigned int r2 = A_VALUE * ptr_s[w*3+1] + B_VALUE * ptr_d[w*3+1];// * ptr_d[w*3+1] / 0xff;

    if (r2 > 0xff) r2 = 0xff;

    ptr_d[w*3+1] = r2;

    unsigned int r3 = A_VALUE * ptr_s[w*3+2] + B_VALUE * ptr_d[w*3+2];// * ptr_d[w*3+2] / 0xff;

    if (r3 > 0xff) r3 = 0xff;

    ptr_d[w*3+2] = r3;

  }  

}

 cvShowImage("Image-bloom",blur); 

 //清除垃圾 

 cvReleaseImage(&imggrey); 

 cvReleaseImage(&img); 

 cvReleaseImage(&tmp);  

 cvWaitKey(); 

 //销毁窗口

 cvDestroyWindow("Image-in");  

 cvDestroyWindow("Image-grey"); 

 cvDestroyWindow("Image-threshold"); 

 cvDestroyWindow("Image-gaussianblur");

 cvDestroyWindow("Image-bloom");

 return 0;

}

       最后是我用这段程序对我以前渲染的中国龙进行bloom后的效果(下面两张图分别是bloom效果图和原始图片):

 

后记:我发现在很多游戏中阈值似乎设置的是0(也可能是它们用了更为复杂的方法),这样可以避免刚好处于阈值两端的像素点间颜色不连续的问题。

在网上有一篇讲bloom和hdr区别的帖子中(可参考http://game.ali213.net/thread-2075708-1-1.html)用了一幅毁灭战士3中的图片,下面是效果比较

(1:游戏中未开启bloom的效果,2:游戏中开启bloom的效果,3:图1经过我的程序跑出来的bloom效果(阈值设为0,A,B两个value都设为1.0))

 

 

可以看出游戏中的泛光现象更加明显,可能是它用了更宽范围的高斯模糊(或类似算法)。

posted @ 2013-04-07 23:24  星光下的守望者  阅读(2911)  评论(3编辑  收藏  举报