【问题标题】:Blur kernel separation for two pass rendering两遍渲染的模糊内核分离
【发布时间】:2018-06-19 08:37:07
【问题描述】:

我有必要计算两遍模糊的内核。假设有一个 5x5 的模糊核,生成如下:

public static float[][] getKernelf(int size, float sigma) {
    float[][] kernel = new float[size][size];
    int mean = size / 2;
    float sum = 0; // For accumulating the kernel values
    for (int x = 0; x < size; x++)  {
        for (int y = 0; y < size; y++) {
            kernel[x][y] = (float) (Math.exp(-0.5 * (Math.pow((x - mean) / sigma, 2.0) + 
                    Math.pow((y - mean) / sigma, 2.0))) / (2 * Geometry.PI * sigma * sigma));
            // Accumulate the kernel values
            sum += kernel[x][y];
        }
    }

    // Normalize the kernel
    for (int x = 0; x < size; x++) 
        for (int y = 0; y < size; y++)
            kernel[x][y] /= sum;

    return kernel;
}

当 sigma = 1 和 size = 5 时,我们有以下内核:

0.0029690173  0.013306212  0.021938235  0.013306212  0.0029690173
0.013306212   0.059634306  0.09832035   0.059634306  0.013306212
0.021938235   0.09832035   0.16210285   0.09832035   0.021938235
0.013306212   0.059634306  0.09832035   0.059634306  0.013306212
0.0029690173  0.013306212  0.021938235  0.013306212  0.0029690173

问题是如何将这个内核带入适合二次渲染的视图(水平和垂直,OpenGL 中的实时渲染)

编辑:

书中给出的内核:0.227027 0.1945946 0.1216216 0.054054 0.016216

我的完整fragment_blur_shader.glsl:

#version 330

out vec4 fragColor;
in vec2 texCoords;

uniform sampler2D image;
uniform bool isHorizontal;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);

void main() {
    vec2 texOffset = 1.0 / textureSize(image, 0); // gets size of single texel
    vec3 result = texture(image, texCoords).rgb * weight[0]; // current fragment’s contribution
    if(isHorizontal) {
        for(int i = 1; i < 5; i++) {
            result += texture(image, texCoords + vec2(texOffset.x * i, 0.0)).rgb * weight[i];
            result += texture(image, texCoords - vec2(texOffset.x * i, 0.0)).rgb * weight[i];
        }
    } else {
        for(int i = 1; i < 5; ++i) {
            result += texture(image, texCoords + vec2(0.0, texOffset.y * i)).rgb * weight[i];
            result += texture(image, texCoords - vec2(0.0, texOffset.y * i)).rgb * weight[i];
        }
    }
    fragColor = vec4(result, 1.0);
}

我还发现下图展示了从 1D 内核接收 2D 内核以进行两阶段渲染:

但我不知道如何获得这个一维核心。希望能得到你的帮助。

编辑:

我明白了如何得到我需要的内核,但我还是不明白为什么书上会以这种形式给出这个内核

【问题讨论】:

  • 看起来您正在尝试使用 Java 和 CPU 实现着色器代码。
  • float mean = size / 2; 是自找麻烦(这里应该使用浮点运算)
  • 不确定我是否在关注,您只需要生成一个长度为5 的一维向量,对应于数组的中间列或中间行。然后,您可以将其用作两个(水平和垂直)渲染通道的内核。
  • 或者换句话说你需要计算一维高斯,如果你已经做了二维版本应该很简单
  • 关于你的最后一个问题,我在这里解释一下高斯滤波和关于将 2D 内核分离为 1D 内核:crisluengo.net/index.php/archives/22

标签: java opengl image-processing blur gaussianblur


【解决方案1】:

从您拥有的 2D 高斯内核出发:

float[][] kernel = new float[size][size];
int mean = size / 2;
float sum = 0; // For accumulating the kernel values
for (int x = 0; x < size; x++)  {
    for (int y = 0; y < size; y++) {
        kernel[x][y] = (float) (Math.exp(-0.5 * (Math.pow((x - mean) / sigma, 2.0) + 
                Math.pow((y - mean) / sigma, 2.0))) / (2 * Geometry.PI * sigma * sigma));
        // Accumulate the kernel values
        sum += kernel[x][y];
    }
}

// Normalize the kernel
for (int x = 0; x < size; x++) 
    for (int y = 0; y < size; y++)
        kernel[x][y] /= sum;

对于一维高斯内核,只需删除y 上的循环(以及对y 的所有进一步引用):

float[] kernel = new float[size];
int mean = size / 2;
float sum = 0; // For accumulating the kernel values
for (int x = 0; x < size; x++)  {
    kernel[x] = (float) Math.exp(-0.5 * Math.pow((x - mean) / sigma, 2.0));
    // Accumulate the kernel values
    sum += kernel[x];
}

// Normalize the kernel
for (int x = 0; x < size; x++) 
    kernel[x] /= sum;

更多提示:

  • 正如您已经注意到的,您的着色器只使用了这个内核的一半(x-mean &gt;= 0 的部分)。既然是对称的,那另一半就多余了。

  • 无需使用2 * Geometry.PI * sigma * sigma 缩放kernel 值,因为您稍后会标准化内核,此缩放无关紧要。

  • 将此内核与转置后的内核相乘产生第一个代码生成的二维内核(如您在问题中包含的图中所示)。

【讨论】:

    【解决方案2】:

    所以,我得出了最终答案。感谢我找到的图片和您的建议,我终于写出了获得一维二次内核的最终函数:

    public static float[] getTwoPassKernelf(int size, float sigma) {
        float[] kernel = new float[size];
        float[][] fkernel = getKernelf(size, sigma);
    
        for (int i = 0; i < size; i++) {
            kernel[i] = (float) Math.sqrt(fkernel[i][i]);
        }
    
        return kernel;
    }
    
    public static float[][] getKernelf(int size, float sigma) {
        float[][] kernel = new float[size][size];
        int mean = size / 2;
        float sum = 0; // For accumulating the kernel values
        for (int x = 0; x < size; x++)  {
            for (int y = 0; y < size; y++) {
                kernel[x][y] = (float) (Math.exp(-0.5 * (Math.pow((x - mean) / sigma, 2.0) + 
                        Math.pow((y - mean) / sigma, 2.0))) / (2 * Geometry.PI * sigma * sigma));
                // Accumulate the kernel values
                sum += kernel[x][y];
            }
        }
    
        // Normalize the kernel
        for (int x = 0; x < size; x++) 
            for (int y = 0; y < size; y++)
                kernel[x][y] /= sum;
    
        return kernel;
    }
    

    总之,着色器需要传输的不是所有内核,而是它的一部分,包括中心(在我的例子中)。因此,我在问题本身中引用的核心不是 5x5 而是 10x10。

    例如:getTwoPassKernelf(10, 2f) 返回

    0.008890575, 0.027384898, 0.065692954, 0.1227306, 0.17857197, 0.20234855, 0.17857197, 0.1227306, 0.065692954, 0.027384898

    但就我而言,我只能选择0.20234855, 0.17857197, 0.1227306, 0.065692954, 0.027384898 - 这正是这本书所提供的

    一张图解释了两次渲染获得内核的本质

    Selected - 内核元素的平方

    我希望答案对某人有用

    【讨论】:

    • 好的,但这很神秘,为什么不首先制作一个一维高斯核呢?整个 2D 步骤是不必要的。制作一维内核更容易
    • 这毫无意义。对角线是一维内核,水平或垂直也是。二维高斯是完全各向同性的。沿对角线的样本,因为它们间隔更远,将导致一维内核的 sigma 更小(因子sqrt(2))。
    猜你喜欢
    • 2011-06-09
    • 1970-01-01
    • 2015-03-11
    • 2017-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    相关资源
    最近更新 更多