WebGL:循环索引无法与非常量表达式进行比较

时间:2016-08-16 23:58:33

标签: glsl webgl

我有一个webgl模糊着色器:

precision mediump float;
precision mediump int;

uniform sampler2D u_image;
uniform float blur;       
uniform int u_horizontalpass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma;        // The sigma value for the gaussian function: higher value means more blur
                            // A good value for 9x9 is around 3 to 5
                            // A good value for 7x7 is around 2.5 to 4
                            // A good value for 5x5 is around 2 to 3.5
                            // ... play around with this based on what you need :)

varying vec4 v_texCoord;

const vec2 texOffset = vec2(1.0, 1.0);
// uniform vec2 texOffset;
const float PI = 3.14159265;

void main() {  
  vec2 p = v_texCoord.st;
  float numBlurPixelsPerSide = blur / 2.0; 

  // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
  vec3 incrementalGaussian;
  incrementalGaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma);
  incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
  incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;

  vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
  float coefficientSum = 0.0;

  // Take the central sample first...
  avgValue += texture2D(u_image, p) * incrementalGaussian.x;
  coefficientSum += incrementalGaussian.x;
  incrementalGaussian.xy *= incrementalGaussian.yz;

  // Go through the remaining 8 vertical samples (4 on each side of the center)
  for (float i = 1.0; i <= numBlurPixelsPerSide; i += 1.0) { 
    avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;         
    avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;         
    coefficientSum += 2.0 * incrementalGaussian.x;
    incrementalGaussian.xy *= incrementalGaussian.yz;
  }

  gl_FragColor = avgValue / coefficientSum;
}

当我构建时,我收到以下错误消息:

  

webgl-renderer.js?2eb3:137 Uncaught无法编译着色器:错误:   0:38:&#39;我&#39; :循环索引不能与非常量表达式进行比较

我还尝试使用 统一浮点模糊来比较i。有没有什么办法解决这一问题?

问题在此处进一步详细说明:https://www.khronos.org/webgl/public-mailing-list/archives/1012/msg00063.php

我找到的解决方案是在比较循环var时仅使用常量表达式。这与我需要做的事情不相符,这取决于我根据模糊半径进行循环的次数。

对此有何想法?

7 个答案:

答案 0 :(得分:6)

这是因为在某些硬件上,GLSL循环被解压缩到本机GPU指令中。这意味着通过for循环的次数需要有一个硬上限,它决定了循环内部代码的生成副本数量。如果用numBlurPixelsPerSideconst float指令替换#define,然后着色器编译器可以确定编译时的传递次数,并相应地生成代码。但是在那里有一个制服,在编译时不知道上限。

这条规则中有一个有趣的问题:你被允许break或从return循环中调用早期的for,即使在编译时必须能够辨别出最大迭代次数时间。例如,考虑this tiny Mandelbrot shader。这不是GLSL沙盒上最漂亮的分形,但我选择它的尺寸很小:

precision mediump float;
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
varying vec2 surfacePosition;

const float max_its = 100.;

float mandelbrot(vec2 z){
    vec2 c = z;
    for(float i=0.;i<max_its;i++){     // for loop is here.
        if(dot(z,z)>4.) return i;      // conditional early return here.
        z = vec2(z.x*z.x-z.y*z.y,2.*z.x*z.y)+c;
    }
    return max_its;
}


void main( void ) {
    vec2 p = surfacePosition;
    gl_FragColor = vec4(mandelbrot(p)/max_its);
}

在此示例中,max_itsconst,因此编译器知道上限并且可以在需要时解压缩此循环。在循环内部,return语句提供了一种方法,可以提前为Mandelbrot集之外的像素保留循环。

您仍然不希望将最大迭代次数设置得太高,因为这会产生大量GPU指令并可能会损害性能。

答案 1 :(得分:5)

尝试这样的事情:

const float MAX_ITERATIONS = 100.0;

// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= MAX_ITERATIONS; i += 1.0) { 
    if (i >= numBlurPixelsPerSide){break;}
    avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;         
    avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;         
    coefficientSum += 2.0 * incrementalGaussian.x;
    incrementalGaussian.xy *= incrementalGaussian.yz;
}

答案 2 :(得分:2)

有时候,您可以使用我非常简单的问题解决方法。

我的着色器源代码片段:

const int cloudPointsWidth = %s;
for ( int i = 0; i < cloudPointsWidth; i++ ) {
   //TO DO something
}

您可以在上面看到'%'语法错误。但是在使用着色器之前,我将%s替换为我的javascript代码中的数字。例如:

vertexCode = vertexCode.replace( '%s', 10 );

vertexCode是我的着色器源代码。

每次我想更改cloudPointsWidth时,都会破坏旧的着色器,并使用新的cloudPointsWidth创建新的着色器。

希望有时我的解决方案可以为您提供帮助。

答案 3 :(得分:0)

我在图像下采样着色器方面遇到了类似的问题。代码基本相同:

for (int dx = -2 * SCALE_FACTOR; dx < 2 * SCALE_FACTOR; dx += 2) {
    for (int dy = -2 * SCALE_FACTOR; dy < 2 * SCALE_FACTOR; dy += 2) {
        /* accumulate fragment's color */
    }
}

我最终做的是使用预处理器并为每个SCALE_FACTOR创建单独的着色器程序(幸运的是,只需要4个)。为此,我们实现了一个小帮助函数,将#define ...语句添加到着色器代码中:

function insertDefines (shaderCode, defines) {
    var defineString = '';

    for (var define in defines) {
        if (defines.hasOwnProperty(define)) {
            defineString +=
                '#define ' + define + ' ' + defines[define] + '\n';
        }
    }

    var versionIdx = shaderCode.indexOf('#version');

    if (versionIdx == -1) {
        return defineString + shaderCode;
    }

    var nextLineIdx = shaderCode.indexOf('\n', versionIdx) + 1;

    return shaderCode.slice(0, nextLineIdx) +
        defineString +
        shaderCode.slice(nextLineIdx);
}

实现有点棘手,因为如果代码中已经有#version预处理器语句,则所有其他语句都必须遵循它。

然后我添加了对SCALE_FACROR定义的检查:

#ifndef SCALE_FACTOR
#   error SCALE_FACTOR is undefined
#endif

在我的javascript代码中,我做过类似的事情:

var SCALE_FACTORS = [4, 8, 16, 32],
    shaderCode, // the code of my shader
    shaderPrograms = SCALE_FACTORS.map(function (factor) {
        var codeWithDefines = insertDefines(shaderCode, { SCALE_FACTOR: factor });
        /* compile shaders, link program, return */
    });

答案 4 :(得分:0)

我在android上使用opengl es3并通过在程序开头之上使用扩展来解决这个问题,如下所示:

#extension GL_EXT_gpu_shader5 : require

我不知道它是否适用于webGL,但你可以尝试一下。 希望它可以提供帮助。

答案 5 :(得分:0)

您可以使用一个常数较大的for循环并使用一个break。

for(int i = 0; i < 1000000; ++i) 
{ 
    // your code here
    if(i >= n){
        break;
    }
}

答案 6 :(得分:0)

您还可以使用模板文字设置循环的长度

onBeforeCompile(shader) {
  const array = [1,2,3,4,5];
  shader.uniforms.myArray = { value: array };

  let token = "#include <begin_vertex>";
  const insert = `
    uniform float myArray[${array.length}];
    for ( int i = 0; i < ${array.length}; i++ ) {
       float test = myArray[ i ];
    }
  `;
  shader.vertexShader = shader.vertexShader.replace(token, token + insert);
}