WebGL学习之HDR与Bloom (3)

在我们研究帧缓冲之前,先来实现高斯模糊的片元着色器:

#version 300 es precision highp float; uniform sampler2D image; uniform bool horizontal; in vec2 texcoord; out vec4 FragColor; const float weight[5] = float[](0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162); void main() { vec2 tex_offset = vec2(1.0 / float(textureSize(image, 0)));//每个像素的尺寸 vec3 result = texture(image, texcoord).rgb * weight[0]; if (horizontal) { for (int i = 0; i < 5; ++i) { result += texture(image, texcoord + vec2(tex_offset.x * float(i), 0.0)).rgb * weight[i]; result += texture(image, texcoord - vec2(tex_offset.x * float(i), 0.0)).rgb * weight[i]; } } else { for (int i = 0; i < 5; ++i) { result += texture(image, texcoord + vec2(0.0, tex_offset.y * float(i))).rgb * weight[i]; result += texture(image, texcoord - vec2(0.0, tex_offset.y * float(i))).rgb * weight[i]; } } FragColor = vec4 (result, 1.0); }

这里使用一个比较小的高斯权重做例子,每次我们用它来指定当前fragment的水平或垂直样本的特定权重。你会发现我们基本上是将模糊过滤器根据我们在uniform变量horizontal设置的值分割为一个水平和一个垂直部分。通过用1.0除以纹理的大小(从textureSize得到一个vec2)得到一个纹理像素的实际大小,以此作为偏移距离的根据。

接着为图像的模糊处理创建两个基本的帧缓冲,每个只有一个颜色缓冲纹理,调用上面封装好的createFramebuffer函数即可。

//2乒乓帧缓存(都只包含1颜色附件) const hFbo = createFramebuffer(gl,{informat:gl.RGBA16F, type:gl.FLOAT}); const vFbo = createFramebuffer(gl,{informat:gl.RGBA16F, type:gl.FLOAT});

得到一个HDR纹理后,我们用提取出来的亮区纹理填充一个帧缓冲,然后对其模糊处理6次(3次垂直3次水平):

/** * 乒乓帧缓存 */ gl.useProgram(pProgram.program); for(let i=0; i < 6; i++){ bindFramebufferInfo(gl, i%2 ? hFbo:vFbo); setBuffersAndAttributes(gl, pProgram, pVao); setUniforms(pProgram,{ horizontal: i%2? true:false, image: i == 0 ? fbo.textures[1]: i%2 ? vFbo.textures[0]: hFbo.textures[0], //第1次两个乒乓帧缓存都为空,因此第一次要将灯光纹理传入 }); drawBufferInfo(gl, pVao); }

每次循环根据渲染的是水平还是垂直来绑定两个缓冲其中之一,而将另一个绑定为纹理进行模糊。第一次迭代,因为两个颜色缓冲都是空的所以我们随意绑定一个去进行模糊处理。重复这个步骤6次,亮区图像就进行一个重复3次的高斯模糊了。这样我们可以对任意图像进行任意次模糊处理;高斯模糊循环次数越多,模糊的强度越大。

把两个纹理混合

有了场景的HDR纹理和模糊处理的亮区纹理,只需把它们结合起来就能实现泛光或称光晕效果了。最终的片元着色器要把两个纹理混合:

#version 300 es precision highp float; in vec2 texcoord; uniform sampler2D image; uniform sampler2D imageBlur; uniform bool bloom; out vec4 FragColor; const float exposure = 1.0; const float gamma = 2.2; void main() { vec3 hdrColor = texture(image, texcoord).rgb; vec3 bloomColor = texture(imageBlur, texcoord).rgb; if (bloom) hdrColor += bloomColor; //添加融合 //色调映射 // vec3 result = hdrColor / (hdrColor + vec3(1.0)); vec3 result = vec3 (1.0) - exp(-hdrColor * exposure); //进行gamma校正 result = pow(result, vec3 (1.0 / gamma)); FragColor = vec4(result, 1.0); }

注意要在应用色调映射之前添加泛光效果。这样添加的亮区的泛光,也会柔和转换为LDR,光照效果相对会更好。把两个纹理结合以后,场景亮区便有了合适的光晕特效:

这里只用了一个相对简单的高斯模糊过滤器,它在每个方向上只有5个样本。通过沿着更大的半径或重复更多次数的模糊,进行采样我们就可以提升模糊的效果。因为模糊的质量与泛光效果的质量正相关,提升模糊效果就能够提升泛光效果。

后记

这个HDR + Bloom的是目前为止渲染流程最复杂的一个特效了,使用了3个着色器program和3个帧缓冲区,绘制的时候要不断切换program 和 帧缓冲区。目前有个问题是,从帧缓冲渲染到正常缓冲后场景的锯齿感挺严重的,后续还得深入学习下抗锯齿(anti-aliasing)。

参考资料:
HDR
泛光

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdzjz.html