Opengl es 简单的GLSL卷积着色器非常慢
我正在尝试在OpenGLES2.0foriOS中实现一个2D轮廓着色器。它慢得令人发狂。以5fps的速度。我追踪到了texture2D()调用。但是,如果没有这些,任何卷积着色器都是可撤消的。我试着用lowp而不是mediump,但是所有的东西都是黑色的,虽然它能提供另外5fps,但仍然无法使用 这是我的片段着色器Opengl es 简单的GLSL卷积着色器非常慢,opengl-es,filter,opengl-es-2.0,glsl,convolution,Opengl Es,Filter,Opengl Es 2.0,Glsl,Convolution,我正在尝试在OpenGLES2.0foriOS中实现一个2D轮廓着色器。它慢得令人发狂。以5fps的速度。我追踪到了texture2D()调用。但是,如果没有这些,任何卷积着色器都是可撤消的。我试着用lowp而不是mediump,但是所有的东西都是黑色的,虽然它能提供另外5fps,但仍然无法使用 这是我的片段着色器 varying mediump vec4 colorVarying; varying mediump vec2 texCoord; uniform bool
varying mediump vec4 colorVarying;
varying mediump vec2 texCoord;
uniform bool enableTexture;
uniform sampler2D texture;
uniform mediump float k;
void main() {
const mediump float step_w = 3.0/128.0;
const mediump float step_h = 3.0/128.0;
const mediump vec4 b = vec4(0.0, 0.0, 0.0, 1.0);
const mediump vec4 one = vec4(1.0, 1.0, 1.0, 1.0);
mediump vec2 offset[9];
mediump float kernel[9];
offset[0] = vec2(-step_w, step_h);
offset[1] = vec2(-step_w, 0.0);
offset[2] = vec2(-step_w, -step_h);
offset[3] = vec2(0.0, step_h);
offset[4] = vec2(0.0, 0.0);
offset[5] = vec2(0.0, -step_h);
offset[6] = vec2(step_w, step_h);
offset[7] = vec2(step_w, 0.0);
offset[8] = vec2(step_w, -step_h);
kernel[0] = kernel[2] = kernel[6] = kernel[8] = 1.0/k;
kernel[1] = kernel[3] = kernel[5] = kernel[7] = 2.0/k;
kernel[4] = -16.0/k;
if (enableTexture) {
mediump vec4 sum = vec4(0.0);
for (int i=0;i<9;i++) {
mediump vec4 tmp = texture2D(texture, texCoord + offset[i]);
sum += tmp * kernel[i];
}
gl_FragColor = (sum * b) + ((one-sum) * texture2D(texture, texCoord));
} else {
gl_FragColor = colorVarying;
}
}
改变mediump vec4颜色变化;
可变mediump vec2 texCoord;
均匀的布尔使能纹理;
二维纹理均匀;
均匀中泵浮动k;
void main(){
常量中间泵浮动步进w=3.0/128.0;
常数-中间泵浮动步长h=3.0/128.0;
const mediump vec4 b=vec4(0.0,0.0,0.0,1.0);
const mediump vec4 one=vec4(1.0,1.0,1.0,1.0);
mediump vec2偏移量[9];
mediump浮点内核[9];
偏移量[0]=vec2(-step_w,step_h);
偏移量[1]=vec2(-step_w,0.0);
偏移量[2]=vec2(-step\u w,-step\u h);
偏移量[3]=vec2(0.0,步长h);
偏移量[4]=vec2(0.0,0.0);
偏移量[5]=vec2(0.0,-步长h);
偏移量[6]=vec2(步骤w,步骤h);
偏移量[7]=vec2(步长w,0.0);
偏移量[8]=vec2(步骤w,-步骤h);
内核[0]=内核[2]=内核[6]=内核[8]=1.0/k;
内核[1]=内核[3]=内核[5]=内核[7]=2.0/k;
内核[4]=-16.0/k;
如果(启用纹理){
mediump vec4 sum=vec4(0.0);
对于(int i=0;i我所知道的减少此着色器中所用时间的唯一方法是减少纹理提取的数量。由于着色器从中心像素周围的等间距点采样纹理并将其线性组合,因此可以通过使用可用于纹理采样的GL_线性模式来减少提取的数量
基本上,不是在每个texel上采样,而是在一对texel之间采样,直接得到一个线性加权和
让我们把偏移量(-stepw,-steph)和(-stepw,0)处的采样分别称为x0和x1,那么你的和就是
sum=x0*k0+x1*k1
现在,如果在这两个texel之间采样,距离为
k0/(k0+k1)
来自x0,因此k1/(k0+k1)
来自x1,然后GPU将在提取期间执行线性加权,并给您
y=x1*k1/(k0+k1)+x0*k0/(k1+k0)
因此,总和可以计算为
sum=y*(k0+k1)
仅从一次提取
如果对其他相邻像素重复此操作,最终将对每个相邻偏移执行4次纹理提取,并对中心像素执行一次额外纹理提取
对这一点的解释更好我自己也做过这件事,我看到了一些可以优化的地方
首先,我要删除enableTexture
条件,而是将着色器拆分为两个程序,一个用于此的真实状态,另一个用于false。条件在iOS片段着色器中非常昂贵,尤其是那些具有纹理读取的着色器
其次,这里有九个依赖纹理读取。这些是纹理读取,其中纹理坐标在片段着色器中计算。依赖纹理读取在iOS设备内的PowerVR GPU上非常昂贵,因为它们阻止硬件使用缓存等优化纹理读取。因为您是从对于8个周围像素和一个中心像素的固定偏移量,这些计算应上移到顶点着色器中。这也意味着这些计算不必对每个像素执行,每个顶点只执行一次,然后硬件插值将处理其余的
第三,到目前为止,iOS着色器编译器还没有很好地处理for()循环,因此我倾向于尽可能避免使用这些循环
如前所述,我在开源iOS框架中使用过类似的卷积着色器。对于通用卷积过滤器,我使用以下顶点着色器:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
uniform highp float texelWidth;
uniform highp float texelHeight;
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
void main()
{
gl_Position = position;
vec2 widthStep = vec2(texelWidth, 0.0);
vec2 heightStep = vec2(0.0, texelHeight);
vec2 widthHeightStep = vec2(texelWidth, texelHeight);
vec2 widthNegativeHeightStep = vec2(texelWidth, -texelHeight);
textureCoordinate = inputTextureCoordinate.xy;
leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;
rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;
topTextureCoordinate = inputTextureCoordinate.xy - heightStep;
topLeftTextureCoordinate = inputTextureCoordinate.xy - widthHeightStep;
topRightTextureCoordinate = inputTextureCoordinate.xy + widthNegativeHeightStep;
bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;
bottomLeftTextureCoordinate = inputTextureCoordinate.xy - widthNegativeHeightStep;
bottomRightTextureCoordinate = inputTextureCoordinate.xy + widthHeightStep;
}
以及以下片段着色器:
precision highp float;
uniform sampler2D inputImageTexture;
uniform mediump mat3 convolutionMatrix;
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
void main()
{
mediump vec4 bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate);
mediump vec4 bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate);
mediump vec4 bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate);
mediump vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);
mediump vec4 leftColor = texture2D(inputImageTexture, leftTextureCoordinate);
mediump vec4 rightColor = texture2D(inputImageTexture, rightTextureCoordinate);
mediump vec4 topColor = texture2D(inputImageTexture, topTextureCoordinate);
mediump vec4 topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate);
mediump vec4 topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate);
mediump vec4 resultColor = topLeftColor * convolutionMatrix[0][0] + topColor * convolutionMatrix[0][1] + topRightColor * convolutionMatrix[0][2];
resultColor += leftColor * convolutionMatrix[1][0] + centerColor * convolutionMatrix[1][1] + rightColor * convolutionMatrix[1][2];
resultColor += bottomLeftColor * convolutionMatrix[2][0] + bottomColor * convolutionMatrix[2][1] + bottomRightColor * convolutionMatrix[2][2];
gl_FragColor = resultColor;
}
texelWidth
和texelHeight
统一是输入图像的宽度和高度的反比,而卷积矩阵
统一指定卷积中各种样本的权重
在iPhone 4上,对于640x480帧的摄像头视频,这一过程的运行时间为4-8毫秒,足以以60 FPS的速度渲染该图像大小。如果您只需要执行边缘检测之类的操作,您可以简化上述步骤,在预处理过程中将图像转换为亮度,然后仅从一个颜色通道进行采样。速度更快,大约为每帧2毫秒“我试着用一个稳定的vec4替换循环中的texture2D()调用,它运行没有问题”这意味着什么?它变快了吗?它没有改变性能吗?发生了什么事?“我根本不明白这为什么会引起任何问题。”你每次调用着色器都要进行十次纹理访问,但你看不出是什么导致了问题?此外,你还访问了两次中心纹理。在没有纹理查找的情况下,我得到了稳定的60fps(不包括最后一次).正如我所说,它没有经过优化,但无法避免这些纹理调用。否则,过滤器无法工作。但我见过很多游戏,无论是移动游戏还是非移动游戏,都使用基于卷积过滤器的效果,它们似乎没有任何问题。除非有什么技巧可以避免它们?很好的示例。tl;dr:避免依赖纹理读取。此外,通过在两次过程中渲染来测试可分离卷积,以减少回迁次数(尽管对于9这样的示例,它不会减少到不到一半,因此在这种情况下,两次过程方法可能是个坏主意)@StevenLu-在许多GPU上,当一次读取超过9次纹理时,性能会出现惊人的急剧下降