WebGL:循环索引不能与非常量表达式进行比较
WebGL: Loop index cannot be compared with non-constant expression
我有一个 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 could not compile shader:ERROR:
0:38: 'i' : Loop index cannot be compared with non-constant expression
我也曾尝试使用 只是 统一的浮点模糊来与 i 进行比较。有没有什么办法解决这一问题?
问题在这里进一步详细说明:https://www.khronos.org/webgl/public-mailing-list/archives/1012/msg00063.php
我四处寻找的解决方案是在比较循环变量时只使用常量表达式。这不符合我需要做的事情,即根据模糊半径改变我循环的次数。
对此有什么想法吗?
尝试这样的事情:
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;
}
我在图像下采样着色器方面遇到了类似的问题。代码基本相同:
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 */
});
发生这种情况是因为在某些硬件上,GLSL 循环被展开为本机 GPU 指令。这意味着通过 for
循环的次数需要有一个硬性上限,它控制将生成多少个循环内部代码副本。如果将 numBlurPixelsPerSide
替换为 const float
甚至 #define
指令,着色器编译器便可以在编译时确定遍数,并相应地生成代码。但是那里有制服,编译时不知道上限。
此规则中有一个有趣的问题:您可以 break
或从 for
循环中提前调用 return
,即使最大迭代次数必须是可辨别的在编译时。例如,考虑 this tiny Mandelbrot shader。这几乎不是 GLSL Sandbox 上最漂亮的分形,但我选择它是因为它的体积小:
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_its
是一个 const
,所以编译器知道上限并且可以在需要时取消循环。在循环内部,return
语句提供了一种为 Mandelbrot 集之外的像素提早离开循环的方法。
您仍然不想将最大迭代次数设置得太高,因为这会产生大量 GPU 指令并可能损害性能。
我在 android 上使用 opengl es3 并通过在程序开头上方使用扩展来解决这个问题,如下所示:
#extension GL_EXT_gpu_shader5 : require
我不知道它是否适用于 webGL,但你可以试试。
希望对你有帮助。
你可以只做一个大常数的for循环,然后使用一个break。
for(int i = 0; i < 1000000; ++i)
{
// your code here
if(i >= n){
break;
}
}
有时您可以使用我非常简单的问题解决方法。
我的着色器源代码片段:
const int cloudPointsWidth = %s;
for ( int i = 0; i < cloudPointsWidth; i++ ) {
//TO DO something
}
您可以看到“%”:上面的语法错误。但是在使用我的着色器之前,我将 %s 替换为我的 javascript 代码中的一个数字。例如:
vertexCode = vertexCode.replace( '%s', 10 );
vertexCode
是我的着色器源代码。
每次如果我想更改 cloudPointsWidth
,我都会破坏我的旧着色器并使用新 cloudPointsWidth
创建新着色器。
希望有时我的解决能帮到你。
您还可以使用模板文字来设置循环的长度
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);
}
我有一个 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 could not compile shader:ERROR: 0:38: 'i' : Loop index cannot be compared with non-constant expression
我也曾尝试使用 只是 统一的浮点模糊来与 i 进行比较。有没有什么办法解决这一问题?
问题在这里进一步详细说明:https://www.khronos.org/webgl/public-mailing-list/archives/1012/msg00063.php
我四处寻找的解决方案是在比较循环变量时只使用常量表达式。这不符合我需要做的事情,即根据模糊半径改变我循环的次数。
对此有什么想法吗?
尝试这样的事情:
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;
}
我在图像下采样着色器方面遇到了类似的问题。代码基本相同:
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 */
});
发生这种情况是因为在某些硬件上,GLSL 循环被展开为本机 GPU 指令。这意味着通过 for
循环的次数需要有一个硬性上限,它控制将生成多少个循环内部代码副本。如果将 numBlurPixelsPerSide
替换为 const float
甚至 #define
指令,着色器编译器便可以在编译时确定遍数,并相应地生成代码。但是那里有制服,编译时不知道上限。
此规则中有一个有趣的问题:您可以 break
或从 for
循环中提前调用 return
,即使最大迭代次数必须是可辨别的在编译时。例如,考虑 this tiny Mandelbrot shader。这几乎不是 GLSL Sandbox 上最漂亮的分形,但我选择它是因为它的体积小:
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_its
是一个 const
,所以编译器知道上限并且可以在需要时取消循环。在循环内部,return
语句提供了一种为 Mandelbrot 集之外的像素提早离开循环的方法。
您仍然不想将最大迭代次数设置得太高,因为这会产生大量 GPU 指令并可能损害性能。
我在 android 上使用 opengl es3 并通过在程序开头上方使用扩展来解决这个问题,如下所示:
#extension GL_EXT_gpu_shader5 : require
我不知道它是否适用于 webGL,但你可以试试。 希望对你有帮助。
你可以只做一个大常数的for循环,然后使用一个break。
for(int i = 0; i < 1000000; ++i)
{
// your code here
if(i >= n){
break;
}
}
有时您可以使用我非常简单的问题解决方法。
我的着色器源代码片段:
const int cloudPointsWidth = %s;
for ( int i = 0; i < cloudPointsWidth; i++ ) {
//TO DO something
}
您可以看到“%”:上面的语法错误。但是在使用我的着色器之前,我将 %s 替换为我的 javascript 代码中的一个数字。例如:
vertexCode = vertexCode.replace( '%s', 10 );
vertexCode
是我的着色器源代码。
每次如果我想更改 cloudPointsWidth
,我都会破坏我的旧着色器并使用新 cloudPointsWidth
创建新着色器。
希望有时我的解决能帮到你。
您还可以使用模板文字来设置循环的长度
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);
}