制作发光效果 - alpha 值问题
Making a glow effect - Problems with alpha values
我想为我的游戏创建发光效果。为了保持这种简约,假设我想要发光图像。 :)
从这个开始:
得到这样的东西:
这是一个三步走的方法。
- 保存场景中的所有亮像素(=发光)
- 对这些像素应用模糊效果(=模糊)
- 在上面绘制原始图片和模糊纹理(= assemble)
第 1 步和第 3 步没问题。模糊部分只是不想正常工作。
在我进一步解释之前,这是我的发光结果:
(阈值 = 0.67f)
现在当我模糊这个时,我得到了一些不幸的结果:
这个黑边是因为 any 透明颜色是黑色 vec4(0.0, 0.0, 0.0, 0.0)
。这不是 SFML/GLSL 中的未知问题,建议为此使用 SFML 的 sf::BlendMode
并将片段着色器中最终像素颜色的 .rgb
值与其 alpha 值相乘。所以我做了,现在这是我的结果:
更好,但绝对不好。模糊着色器现在还会平均化发光蒙版的周围像素。组装后只是一张模糊的图片:
.. 我通过检查像素的 alpha 是否为零在着色器文件中尝试了 "fixing"。这样我在出去玩时就不会看重它们。但是由于 sf::BlendMode 被激活,我不知道 alpha 现在的行为如何 - 所以我停用了混合模式,但我仍然有奇怪的结果。 (在这个问题的最后,我提供了代码和这次尝试的结果)
none 我尝试修复这项工作。我真的需要一些帮助。也许我在着色器中做了一些根本性的错误......这是完整的代码 -
如果要编译它,请使用 2 个片段着色器和 background.jpg(在 1280x720 中)创建一个文件夹资源。
luminescence.frag
#version 120
uniform sampler2D texture;
uniform float threshold;
void main(void){
vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
vec4 pixel = vec4(current_color.rgb, 0.0);
float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness >= threshold){
pixel = texture2D(texture, gl_TexCoord[0].xy);
}
gl_FragColor = pixel;
}
boxblur.frag
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
vec4 sum = texture2D(texture, gl_TexCoord[0].xy);
for (int i = 0; i < blur_radius; ++i){
sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
}
vec4 pixel = vec4(sum / (blur_radius * 2 + 1));
pixel.rgb *= pixel.a;
gl_FragColor = pixel;
}
main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <exception>
void run() {
const sf::Vector2f SIZE(1280, 720);
sf::Texture background_tex;
background_tex.loadFromFile("resources/background.jpg");
sf::Sprite background(background_tex);
sf::Shader luminescence;
luminescence.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
luminescence.setUniform("texture", sf::Shader::CurrentTexture);
luminescence.setUniform("threshold", 0.67f);
sf::Shader blur;
blur.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
blur.setUniform("texture", sf::Shader::CurrentTexture);
blur.setUniform("texture_inverse", 1.0f / SIZE.x);
sf::RenderStates shader_states;
shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
sf::ContextSettings context_settings;
context_settings.antialiasingLevel = 12;
//draws background
sf::RenderTexture scene_render;
scene_render.create(SIZE.x, SIZE.y, context_settings);
//draws luminescence and blur
sf::RenderTexture shader_render;
shader_render.create(SIZE.x, SIZE.y, context_settings);
sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
scene_render.clear();
scene_render.draw(background);
scene_render.display();
//apply luminescence
shader_states.shader = &luminescence;
shader_render.clear(sf::Color::Transparent);
shader_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
shader_render.display();
//apply two pass gaussian blur 3 times to simulate gaussian blur.
shader_states.shader = &blur;
float blur_radius = 30.0f;
for (int i = 0; i < 3; ++i) {
blur.setUniform("blur_radius", static_cast<int>(blur_radius));
//vertical blur
blur.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
shader_render.display();
//horizontal blur
blur.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
shader_render.display();
//decrease blur_radius to simulate a gaussian blur
blur_radius *= 0.45f;
}
//assembly
window.clear();
window.draw(sf::Sprite(scene_render.getTexture()));
window.draw(sf::Sprite(shader_render.getTexture()));
window.display();
}
}
int main() {
try {
run();
}
catch (std::exception e) {
std::cerr << "caught exception - - - " << e.what() << '\n';
return 1;
}
return 0;
}
这是我试图排除零 alpha 值的 boxblur.frag:(当然,我在 main.cpp 的第 29 行删除了 shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
):
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
float div = 0.0;
vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 temp_color = texture2D(texture, gl_TexCoord[0].xy);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
for (int i = 0; i < blur_radius; ++i){
temp_color = texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
temp_color = texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
}
vec4 pixel;
if (div == 0.0){
pixel = vec4(texture2D(texture, gl_TexCoord[0].xy).rgb, 0.0);
}
else{
pixel = vec4(sum / div);
}
gl_FragColor = pixel;
}
导致:
[我正在使用 Visual Studio 2017 社区] - 感谢您的帮助!
试着做一个你只想平均两个像素的小例子。左 (L) 和右 (R)。那么左边的像素由 R(L)、G(L)、B(L)、A(L) 组成,右边的像素由 R(R)、G(R)、B(R) 和A(R).
如果没有 alpha,Blue 的平均值就是:
(B(L)+B(R)) / 2
考虑到 alpha,它变成:
(B(L)*A(L)+B(R)*A(R)) / (A(L)+A(R))
我们可以直接看到,在完全实心像素(alpha=1)的情况下,得到和上面完全一样的公式:
(B(L)*1+B(R)*1) / (1+1) = (B(L)+B(R)) / 2
另外,假设右边的像素点是完全透明的,左边的是实心的,那么右边像素点的颜色分量不会有任何影响,结果和左边的像素点一模一样,这正是我们想要的:
(B(L)*1+B(R)*0) / (1+0) = (B(L)) / 1 = B(L)
两个像素都完全透明成为退化的情况,必须以某种优雅的方式处理。
现在您所要做的就是将其扩展到两个像素以上。 :-)
我也在 en.sfml-dev.org 上发布了这个问题(here) and fallahn 向我展示了正确的方法。
在解决这个问题之前,这里是图片结果:
发光(阈值 = 0.24f):
模糊(4层):
拼装:
耶!解决方案是将所有透明像素设置为纯黑色 vec4(0.0, 0.0, 0.0, 1.0)
,然后在它们被模糊后,将它们添加到场景顶部:
vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy);
gl_FragColor = tex_color + add_color;
这样,如果 add_color
是黑色 ("transparent"),我们添加 tex_color + vec4(0.0, 0.0, 0.0, 1.0)
结果没有变化!
太棒了,因为现在您可以完全忽略 alpha 通道了。
要了解为什么我觉得这很棒,您可以在这里阅读这篇小文章(随意跳过):
在不担心 alpha 的情况下,您可以忽略任何 sf::BlendMode
,例如令人困惑的 sf::BlendMode::OneMinusSrcAlpha
,这让我头疼了整整 2 天。当您知道它们都已预乘时,请尝试计算任何合理的 "true" alpha 值。当然,您还必须将 all rgb 值与像素的 alpha 相乘以反转 prumultiplication……从这里开始,公式会很快升级。还要从 alpha 中减去 1,因为它是 OneMinusSrcAlpha
... 并且不要忘记检查所有 alpha 的总和(是的,您需要求和)为 0 的情况(或者在 OneMinusSrcAlpha
中,其他东西),因为否则你会被 0 除(或者在 OneMinusSrcAlpha
问题中,当所有周围的像素都是实心时,除以 0)。有时奇怪的 alpha 值可能会起作用,但仅适用于单次模糊,但在我的情况下,我有多次通过……等等。
这是最终代码:
luminescence.frag
#version 120
uniform sampler2D texture;
uniform float threshold;
void main(void){
vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0);
float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness >= threshold){
pixel = texture2D(texture, gl_TexCoord[0].xy);
}
gl_FragColor = pixel;
}
boxblur.frag
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
vec4 sum = texture2D(texture, gl_TexCoord[0].xy);
for (int i = 0; i < blur_radius; ++i){
sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
}
gl_FragColor = sum / (blur_radius * 2 + 1);
}
multiply.frag
#version 120
uniform sampler2D texture;
uniform float multiply;
void main(void){
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy) * multiply;
}
assemble.frag
#version 120
uniform sampler2D texture;
uniform sampler2D add_texture;
uniform float add_weight;
void main(void){
vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy) * add_weight;
gl_FragColor = tex_color + add_color;
}
main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <array>
void run() {
const sf::Vector2f SIZE(1280, 720);
sf::Texture background_tex;
background_tex.loadFromFile("resources/background.jpg");
sf::Sprite background(background_tex);
sf::Shader luminescence_shader;
luminescence_shader.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
luminescence_shader.setUniform("texture", sf::Shader::CurrentTexture);
luminescence_shader.setUniform("threshold", 0.24f);
sf::Shader blur_shader;
blur_shader.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
blur_shader.setUniform("texture", sf::Shader::CurrentTexture);
blur_shader.setUniform("texture_inverse", 1.0f / SIZE.x);
sf::Shader assemble_shader;
assemble_shader.loadFromFile("resources/assemble.frag", sf::Shader::Fragment);
assemble_shader.setUniform("texture", sf::Shader::CurrentTexture);
sf::Shader multiply_shader;
multiply_shader.loadFromFile("resources/multiply.frag", sf::Shader::Fragment);
multiply_shader.setUniform("texture", sf::Shader::CurrentTexture);
sf::RenderStates shader_states;
//no blendmode! we make our own - assemble.frag
sf::ContextSettings context_settings;
context_settings.antialiasingLevel = 12;
//draws background
sf::RenderTexture scene_render;
scene_render.create(SIZE.x, SIZE.y, context_settings);
sf::RenderTexture luminescence_render;
luminescence_render.create(SIZE.x, SIZE.y, context_settings);
//draws luminescence and blur
sf::RenderTexture assemble_render;
assemble_render.create(SIZE.x, SIZE.y, context_settings);
//addding multiple boxblurs with different radii looks really nice! in this case 4 layers
std::array<sf::RenderTexture, 4> blur_renders;
for (int i = 0; i < blur_renders.size(); ++i) {
blur_renders[i].create(SIZE.x, SIZE.y, context_settings);
}
const int BLUR_RADIUS_VALUES[] = { 250, 180, 125, 55 };
float blur_weight = blur_renders.empty() ? 0.0 : 1.0 / blur_renders.size();
sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
//first draw the scene
scene_render.clear();
scene_render.draw(background);
scene_render.display();
//apply luminescence
shader_states.shader = &luminescence_shader;
luminescence_render.clear();
luminescence_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
luminescence_render.display();
//apply two pass gaussian blur n times to simulate gaussian blur.
shader_states.shader = &blur_shader;
for (int i = 0; i < blur_renders.size(); ++i) {
blur_shader.setUniform("blur_radius", BLUR_RADIUS_VALUES[i]);
blur_renders[i].clear();
blur_renders[i].draw(sf::Sprite(luminescence_render.getTexture()));
blur_renders[i].display();
//vertical blur
blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
blur_renders[i].display();
//horizontal blur
blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
blur_renders[i].display();
}
//load blur_renders[0] into assemble_render so we can add the other blurs ontop of it
shader_states.shader = &multiply_shader;
multiply_shader.setUniform("multiply", blur_weight);
assemble_render.clear();
assemble_render.draw(sf::Sprite(blur_renders[0].getTexture()), shader_states);
assemble_render.display();
//adding the rest ontop creating a final blur
shader_states.shader = &assemble_shader;
assemble_shader.setUniform("add_weight", blur_weight);
for (int i = 1; i < blur_renders.size(); ++i) {
assemble_shader.setUniform("add_texture", blur_renders[i].getTexture());
assemble_render.draw(sf::Sprite(assemble_render.getTexture()), shader_states);
assemble_render.display();
}
//final result; scene + blur
assemble_shader.setUniform("add_weight", 1.0f);
assemble_shader.setUniform("add_texture", assemble_render.getTexture());
assemble_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
assemble_render.display();
window.clear();
window.draw(sf::Sprite(assemble_render.getTexture()));
window.display();
}
}
int main() {
try {
run();
}
catch (std::exception e) {
std::cerr << "caught exception - - - " << e.what() << '\n';
return 1;
}
return 0;
}
我想为我的游戏创建发光效果。为了保持这种简约,假设我想要发光图像。 :)
从这个开始:
得到这样的东西:
这是一个三步走的方法。
- 保存场景中的所有亮像素(=发光)
- 对这些像素应用模糊效果(=模糊)
- 在上面绘制原始图片和模糊纹理(= assemble)
第 1 步和第 3 步没问题。模糊部分只是不想正常工作。
在我进一步解释之前,这是我的发光结果:
现在当我模糊这个时,我得到了一些不幸的结果:vec4(0.0, 0.0, 0.0, 0.0)
。这不是 SFML/GLSL 中的未知问题,建议为此使用 SFML 的 sf::BlendMode
并将片段着色器中最终像素颜色的 .rgb
值与其 alpha 值相乘。所以我做了,现在这是我的结果:
更好,但绝对不好。模糊着色器现在还会平均化发光蒙版的周围像素。组装后只是一张模糊的图片:
none 我尝试修复这项工作。我真的需要一些帮助。也许我在着色器中做了一些根本性的错误......这是完整的代码 - 如果要编译它,请使用 2 个片段着色器和 background.jpg(在 1280x720 中)创建一个文件夹资源。
luminescence.frag
#version 120
uniform sampler2D texture;
uniform float threshold;
void main(void){
vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
vec4 pixel = vec4(current_color.rgb, 0.0);
float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness >= threshold){
pixel = texture2D(texture, gl_TexCoord[0].xy);
}
gl_FragColor = pixel;
}
boxblur.frag
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
vec4 sum = texture2D(texture, gl_TexCoord[0].xy);
for (int i = 0; i < blur_radius; ++i){
sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
}
vec4 pixel = vec4(sum / (blur_radius * 2 + 1));
pixel.rgb *= pixel.a;
gl_FragColor = pixel;
}
main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <exception>
void run() {
const sf::Vector2f SIZE(1280, 720);
sf::Texture background_tex;
background_tex.loadFromFile("resources/background.jpg");
sf::Sprite background(background_tex);
sf::Shader luminescence;
luminescence.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
luminescence.setUniform("texture", sf::Shader::CurrentTexture);
luminescence.setUniform("threshold", 0.67f);
sf::Shader blur;
blur.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
blur.setUniform("texture", sf::Shader::CurrentTexture);
blur.setUniform("texture_inverse", 1.0f / SIZE.x);
sf::RenderStates shader_states;
shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
sf::ContextSettings context_settings;
context_settings.antialiasingLevel = 12;
//draws background
sf::RenderTexture scene_render;
scene_render.create(SIZE.x, SIZE.y, context_settings);
//draws luminescence and blur
sf::RenderTexture shader_render;
shader_render.create(SIZE.x, SIZE.y, context_settings);
sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
scene_render.clear();
scene_render.draw(background);
scene_render.display();
//apply luminescence
shader_states.shader = &luminescence;
shader_render.clear(sf::Color::Transparent);
shader_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
shader_render.display();
//apply two pass gaussian blur 3 times to simulate gaussian blur.
shader_states.shader = &blur;
float blur_radius = 30.0f;
for (int i = 0; i < 3; ++i) {
blur.setUniform("blur_radius", static_cast<int>(blur_radius));
//vertical blur
blur.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
shader_render.display();
//horizontal blur
blur.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
shader_render.display();
//decrease blur_radius to simulate a gaussian blur
blur_radius *= 0.45f;
}
//assembly
window.clear();
window.draw(sf::Sprite(scene_render.getTexture()));
window.draw(sf::Sprite(shader_render.getTexture()));
window.display();
}
}
int main() {
try {
run();
}
catch (std::exception e) {
std::cerr << "caught exception - - - " << e.what() << '\n';
return 1;
}
return 0;
}
这是我试图排除零 alpha 值的 boxblur.frag:(当然,我在 main.cpp 的第 29 行删除了 shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
):
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
float div = 0.0;
vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 temp_color = texture2D(texture, gl_TexCoord[0].xy);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
for (int i = 0; i < blur_radius; ++i){
temp_color = texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
temp_color = texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
}
vec4 pixel;
if (div == 0.0){
pixel = vec4(texture2D(texture, gl_TexCoord[0].xy).rgb, 0.0);
}
else{
pixel = vec4(sum / div);
}
gl_FragColor = pixel;
}
导致:
[我正在使用 Visual Studio 2017 社区] - 感谢您的帮助!
试着做一个你只想平均两个像素的小例子。左 (L) 和右 (R)。那么左边的像素由 R(L)、G(L)、B(L)、A(L) 组成,右边的像素由 R(R)、G(R)、B(R) 和A(R).
如果没有 alpha,Blue 的平均值就是:
(B(L)+B(R)) / 2
考虑到 alpha,它变成:
(B(L)*A(L)+B(R)*A(R)) / (A(L)+A(R))
我们可以直接看到,在完全实心像素(alpha=1)的情况下,得到和上面完全一样的公式:
(B(L)*1+B(R)*1) / (1+1) = (B(L)+B(R)) / 2
另外,假设右边的像素点是完全透明的,左边的是实心的,那么右边像素点的颜色分量不会有任何影响,结果和左边的像素点一模一样,这正是我们想要的:
(B(L)*1+B(R)*0) / (1+0) = (B(L)) / 1 = B(L)
两个像素都完全透明成为退化的情况,必须以某种优雅的方式处理。
现在您所要做的就是将其扩展到两个像素以上。 :-)
我也在 en.sfml-dev.org 上发布了这个问题(here) and fallahn 向我展示了正确的方法。 在解决这个问题之前,这里是图片结果:
发光(阈值 = 0.24f):
模糊(4层):
拼装:
耶!解决方案是将所有透明像素设置为纯黑色 vec4(0.0, 0.0, 0.0, 1.0)
,然后在它们被模糊后,将它们添加到场景顶部:
vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy);
gl_FragColor = tex_color + add_color;
这样,如果 add_color
是黑色 ("transparent"),我们添加 tex_color + vec4(0.0, 0.0, 0.0, 1.0)
结果没有变化!
太棒了,因为现在您可以完全忽略 alpha 通道了。
要了解为什么我觉得这很棒,您可以在这里阅读这篇小文章(随意跳过):
在不担心 alpha 的情况下,您可以忽略任何 sf::BlendMode
,例如令人困惑的 sf::BlendMode::OneMinusSrcAlpha
,这让我头疼了整整 2 天。当您知道它们都已预乘时,请尝试计算任何合理的 "true" alpha 值。当然,您还必须将 all rgb 值与像素的 alpha 相乘以反转 prumultiplication……从这里开始,公式会很快升级。还要从 alpha 中减去 1,因为它是 OneMinusSrcAlpha
... 并且不要忘记检查所有 alpha 的总和(是的,您需要求和)为 0 的情况(或者在 OneMinusSrcAlpha
中,其他东西),因为否则你会被 0 除(或者在 OneMinusSrcAlpha
问题中,当所有周围的像素都是实心时,除以 0)。有时奇怪的 alpha 值可能会起作用,但仅适用于单次模糊,但在我的情况下,我有多次通过……等等。
这是最终代码:
luminescence.frag
#version 120
uniform sampler2D texture;
uniform float threshold;
void main(void){
vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0);
float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness >= threshold){
pixel = texture2D(texture, gl_TexCoord[0].xy);
}
gl_FragColor = pixel;
}
boxblur.frag
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
vec4 sum = texture2D(texture, gl_TexCoord[0].xy);
for (int i = 0; i < blur_radius; ++i){
sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
}
gl_FragColor = sum / (blur_radius * 2 + 1);
}
multiply.frag
#version 120
uniform sampler2D texture;
uniform float multiply;
void main(void){
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy) * multiply;
}
assemble.frag
#version 120
uniform sampler2D texture;
uniform sampler2D add_texture;
uniform float add_weight;
void main(void){
vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy) * add_weight;
gl_FragColor = tex_color + add_color;
}
main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <array>
void run() {
const sf::Vector2f SIZE(1280, 720);
sf::Texture background_tex;
background_tex.loadFromFile("resources/background.jpg");
sf::Sprite background(background_tex);
sf::Shader luminescence_shader;
luminescence_shader.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
luminescence_shader.setUniform("texture", sf::Shader::CurrentTexture);
luminescence_shader.setUniform("threshold", 0.24f);
sf::Shader blur_shader;
blur_shader.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
blur_shader.setUniform("texture", sf::Shader::CurrentTexture);
blur_shader.setUniform("texture_inverse", 1.0f / SIZE.x);
sf::Shader assemble_shader;
assemble_shader.loadFromFile("resources/assemble.frag", sf::Shader::Fragment);
assemble_shader.setUniform("texture", sf::Shader::CurrentTexture);
sf::Shader multiply_shader;
multiply_shader.loadFromFile("resources/multiply.frag", sf::Shader::Fragment);
multiply_shader.setUniform("texture", sf::Shader::CurrentTexture);
sf::RenderStates shader_states;
//no blendmode! we make our own - assemble.frag
sf::ContextSettings context_settings;
context_settings.antialiasingLevel = 12;
//draws background
sf::RenderTexture scene_render;
scene_render.create(SIZE.x, SIZE.y, context_settings);
sf::RenderTexture luminescence_render;
luminescence_render.create(SIZE.x, SIZE.y, context_settings);
//draws luminescence and blur
sf::RenderTexture assemble_render;
assemble_render.create(SIZE.x, SIZE.y, context_settings);
//addding multiple boxblurs with different radii looks really nice! in this case 4 layers
std::array<sf::RenderTexture, 4> blur_renders;
for (int i = 0; i < blur_renders.size(); ++i) {
blur_renders[i].create(SIZE.x, SIZE.y, context_settings);
}
const int BLUR_RADIUS_VALUES[] = { 250, 180, 125, 55 };
float blur_weight = blur_renders.empty() ? 0.0 : 1.0 / blur_renders.size();
sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
//first draw the scene
scene_render.clear();
scene_render.draw(background);
scene_render.display();
//apply luminescence
shader_states.shader = &luminescence_shader;
luminescence_render.clear();
luminescence_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
luminescence_render.display();
//apply two pass gaussian blur n times to simulate gaussian blur.
shader_states.shader = &blur_shader;
for (int i = 0; i < blur_renders.size(); ++i) {
blur_shader.setUniform("blur_radius", BLUR_RADIUS_VALUES[i]);
blur_renders[i].clear();
blur_renders[i].draw(sf::Sprite(luminescence_render.getTexture()));
blur_renders[i].display();
//vertical blur
blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
blur_renders[i].display();
//horizontal blur
blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
blur_renders[i].display();
}
//load blur_renders[0] into assemble_render so we can add the other blurs ontop of it
shader_states.shader = &multiply_shader;
multiply_shader.setUniform("multiply", blur_weight);
assemble_render.clear();
assemble_render.draw(sf::Sprite(blur_renders[0].getTexture()), shader_states);
assemble_render.display();
//adding the rest ontop creating a final blur
shader_states.shader = &assemble_shader;
assemble_shader.setUniform("add_weight", blur_weight);
for (int i = 1; i < blur_renders.size(); ++i) {
assemble_shader.setUniform("add_texture", blur_renders[i].getTexture());
assemble_render.draw(sf::Sprite(assemble_render.getTexture()), shader_states);
assemble_render.display();
}
//final result; scene + blur
assemble_shader.setUniform("add_weight", 1.0f);
assemble_shader.setUniform("add_texture", assemble_render.getTexture());
assemble_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
assemble_render.display();
window.clear();
window.draw(sf::Sprite(assemble_render.getTexture()));
window.display();
}
}
int main() {
try {
run();
}
catch (std::exception e) {
std::cerr << "caught exception - - - " << e.what() << '\n';
return 1;
}
return 0;
}