泛光

泛光

摊牌

  • 表现光源的一种特效
  • 使光源的颜色向周围发散
  • 也叫做光晕

实现过程

  • 将场景渲染到指定的帧缓冲,并从中提取出光源的部分
  • 将光源的部分进行模糊操作
  • 将模糊后的光源与第一次场景渲染的结果混合,得到最终的结果
  • 需要注意的是:因为要提取光源的部分,为了这一步好处理,我们利用之前提到的HDR,将光源的颜色设置为大于1.0的值,其他物体的颜色都是正常的小于1.0的值;因此使用HDR的原因是为了方便提取光源
  • 从上面的步骤看,实现这个效果并不复杂,关键是模糊算法

关键步骤

  • 上面加粗那句话说看似直白其实另有玄机,翻译翻译就是:渲染场景一次,但得到两副纹理贴图;一副是场景的纹理贴图,另一副是只有光源的纹理贴图
  • 要达到这个目的,需要get一个新的技巧——多渲染目标(Multiple Render Targets,MRT)

多渲染目标(MRT)

  • 把他翻译翻译就是:在片段着色器中指定多个输出结果,做到一次渲染得到多个图片
  • 需要注意的地方:
    • 在客户端,已经有多个颜色缓冲附加到了当前绑定的帧缓冲对象上
    • 并且显式的告诉OpenGL我们要渲染到多个颜色缓冲,否则OpenGL只会渲染到帧缓冲的第一个颜色附件,而忽略所有其他的。我们通过传递多个颜色附件的枚举来做这件事
    • 在片段着色器中,通过指定一个布局location标识符,来控制结果写入到哪个颜色缓冲
    • 这三条分别对应下面三段代码
for (GLuint i = 0; i < 2; i++) 
{ 
    glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL ); 
    //...
    glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0 ); 
}
GLuint attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers(2, attachments);
layout (location = 0) out vec4 FragColor; 
layout (location = 1) out vec4 BrightColor;
void main()
{
    //...
    FragColor = vec4(lighting, 1.0);
    float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722));
    if(brightness > 1.0)
        BrightColor = vec4(FragColor.rgb, 1.0);
}
  • 第三段代码有必要说明一点:根据亮度提取光源颜色时,通常需要把颜色转化为灰度
  • sRGB空间的灰阶转换公式为:
    G r a y = ( R 2.2 ∗ 0.2126 + G 2.2 ∗ 0.7152 + B 2.2 ∗ 0.0722 ) 1 2.2 Gray = (R^{2.2} * 0.2126 + G^{2.2} * 0.7152 + B^{2.2} * 0.0722)^{\frac{1}{2.2}} Gray=(R2.20.2126+G2.20.7152+B2.20.0722)2.21
  • 看过之前的“Gamma校正”应该可以知道,在当前情况下转换灰阶时不需要乘以2.2次幂,最后又没有乘以2.2分之一次幂是因为当前不需要进行gamma校正

高斯模糊

  • 模糊算法的质量决定泛光效果的质量,所以这里选用一个相对既简单又优质的模糊算法——高斯模糊
  • 高斯模糊基于高斯曲线,高斯曲线换个名字你肯定熟悉,那就是二年学的正太分布函数
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0NOLwG2P-1627560490403)(en-resource://database/1450:1)]
  • 他背后的数学原理复杂,这里忽略不计。。。
  • 在接下来,我们将用到高斯曲线的特性是:x越靠近0,他的y值就越接近最大值;x离0越远,他的y值会快速减小
  • 在用法上是:对高斯曲线采样,将采样值组成一个n x n二维高斯核,这里说的核在之前“帧缓冲”中有提到过,就是用来进行卷积计算的
  • 由于高斯模糊的广泛应用,人们总结出了一种更快更节省资源的计算方法:两步高斯模糊,即用一个一维的高斯核,分别在水平和垂直方向上进行卷积计算
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UFuH40Is-1627560490409)(en-resource://database/1462:1)]
  • 上图可以理解为水平方向上的卷积结果(实际不会这么明显,这个图夸张了)
  • 需要注意的是:核中的权重之和要接近1.0,并且不能大于1.0
片段着色器
uniform sampler2D image;
uniform bool horizontal;
uniform float weight[5] = float[] (0.227, 0.194, 0.121, 0.054, 0.016);

void main()
{             
     vec2 tex_offset = 1.0 / textureSize(image, 0);//获取纹素的大小
     vec3 result = texture(image, TexCoords).rgb * weight[0];

     if(horizontal)
     {
         for(int i = 1; i < 5; ++i)
         {
            result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
            result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
         }
     }
     else
     {
         for(int i = 1; i < 5; ++i)
         {
             result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
             result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
         }
     }
     FragColor = vec4(result, 1.0);
}
客户端程序
  • 为了分别实现水平和垂直方向上的卷积计算,需要另外创建两个新的帧缓冲,并附加上颜色缓冲
  • 第一次传入光源的贴图,即需要进行模糊处理的贴图
  • 之后在水平方向和垂直方向上交替进行高斯模糊
for (GLuint i = 0; i < 2; i++)
{ 
    glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
    glBindTexture(GL_TEXTURE_2D, pingpongBuffer[i]);
    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL ); 
    //...
    glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffer[i], 0 ); 
}
//...
bool horizontal = true;
bool first = true; 
shaderBlur.Use(); 
for (GLuint i = 0; i < 10; i++) 
{ 
    glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
    shaderBlur.setValue("horizontal", horizontal);
    glBindTexture( GL_TEXTURE_2D, first ? lightBuffer : pingpongBuffers[!horizontal] ); 
    Render(); 
    horizontal = !horizontal; 
    if (first) 
        first = false; 
} 

混合

  • 将光源的模糊贴图与原始的场景混合
  • 在混合时需要注意:由于前面为了方便提取光源颜色,使用了HDR,所以不能忘了色调映射。色调映射之后为了显示效果更好,还需要进行gamma校正
uniform sampler2D scene;
uniform sampler2D bloomBlur;
uniform float exposure;
void main() 
{
    vec3 hdrColor = texture(scene, TexCoords).rgb;
    vec3 bloomColor = texture(bloomBlur, TexCoords).rgb;
    hdrColor += bloomColor;
    
    vec3 result = vec3(1.0) - exp(-hdrColor * exposure);//色调映射
    const float gamma = 2.2;
    result = pow(result, vec3(1.0 / gamma));
    FragColor = vec4(result, 1.0f);
}
  • 效果
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyMLcHxV-1627560490411)(en-resource://database/1467:1)]
  • 下面的图这是用模版缓冲和高斯模糊做成的效果
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FcSkOZzY-1627560490413)(en-resource://database/1472:1)]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值