为啥我的着色器中的 GLSL 纹理坐标不是线性的?

Posted

技术标签:

【中文标题】为啥我的着色器中的 GLSL 纹理坐标不是线性的?【英文标题】:Why are the GLSL texture-coordinates in my shader not linear?为什么我的着色器中的 GLSL 纹理坐标不是线性的? 【发布时间】:2012-02-17 03:35:47 【问题描述】:

我的 android 2.3.4 Tegra 2 设备上的 glsl 着色器有问题。

我想做一些计算以在 glsl 着色器的片段着色器中创建纹理坐标,并注意到一个问题并试图缩小范围,因此制作了一个测试程序。我在屏幕上渲染了一个宽度为 256 像素到恰好 256 像素的纹理,保存帧缓冲区并逐像素检查它(与源比较),它渲染正确。根据opengl规范,当我使用从0.0到1.0的纹理坐标时,它应该对中心的每个像素(0.5/256、1.5/256、2.5/256等)进行采样,这似乎是正确的。

然后我将 x 纹理坐标本身写入纹理并检查它们。结果有些偏离,我不知道为什么。而不是得到 0..255 的梯度,我期望得到 1..128,128..255。这意味着值 0 被跳过,值 128 出现两次,这意味着它们不是完全线性的。如果该值以任何方式四舍五入,那么对我来说不是显而易见的方式。我还检查了纹理坐标的子像素值,它们是我所期望的。它们的值始终是 0.5/256,应该是半个像素,并确保纹理在像素内正确采样。

同样,用这些坐标渲染的纹理正是我想要的,但坐标本身并不是我所期望的。这对我来说是个问题,因为我想用计算出的纹理坐标进行渲染,虽然我以应该正确的方式计算它们,但生成的纹理是错误的。我需要这些坐标在像素级别上是正确的,但我不知道它们为什么会关闭。

有谁知道导致这种行为的原因以及如何纠正它?

这是正确渲染纹理的片段着色器:

precision mediump   float;
varying vec2        vtex;
uniform sampler2D   samp0;
void main()     
            gl_FragColor = texture2D(samp0, vtex);
        ;

这是创建渐变的片段着色器,纹理坐标为 1..128,128..255 而不是我预期的 0..255:

precision mediump   float;
varying vec2        vtex;
uniform sampler2D   samp0;
void main()     
            gl_FragColor = vec4(vtex.x, 0, 0, 1.0);
        ;

这是片段着色器,它为纹理坐标的子像素值创建颜色,并按照我的预期提供所有值 127 的纹理,它应该是像素的中心:

precision mediump   float;
varying vec2        vtex;
uniform sampler2D   samp0;
void main()     
            gl_FragColor = vec4(fract(vtex.x * 256.0), 0, 0, 1.0);
        ;

其余测试程序完全相同。我只更改着色器。这些是一些重要的代码 sn-ps:

String vertexshader =
    "attribute vec4 a_pos;          \n" +   //  in      position
    "attribute vec2 a_tex;          \n" +   //  in      Texture coordinates
    "varying vec2 vtex;             \n" +   //  out     Texture coordinates
    "void main(void)               \n" +
    "   gl_Position = vec4(a_pos);  \n" +
    "   vtex = a_tex;               \n" +
    "";


//  Initialization

    GLES20.glUseProgram(shader);
    //  Vertex
    vdata.position(0);
    GLES20.glVertexAttribPointer(handlepos, 2, GLES20.GL_FLOAT, false, 4*4, vdata);
    GLES20.glEnableVertexAttribArray(handlepos);
    vdata.position(2);
    GLES20.glVertexAttribPointer(handletex, 2, GLES20.GL_FLOAT, false, 4*4, vdata);
    GLES20.glEnableVertexAttribArray(handletex);
    //  Texture
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, maplookupid[1]);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST );
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST );
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT );
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT );
    GLES20.glUniform1i(handlesampler0, 0);
    GLES20.glDisable(GLES20.GL_DITHER);

//  Rendering

    GLES20.glViewport(0, 0, screenx, screeny);
    //
    vdata.put(0*4+0,     0.0f * (2.0f/screenx));
    vdata.put(0*4+1,   256.0f * (2.0f/screeny));
    vdata.put(0*4+2,     0.0f * (1.0f/256.0f));
    vdata.put(0*4+3,   256.0f * (1.0f/256.0f));
    //  
    vdata.put(1*4+0,     0.0f * (2.0f/screenx));
    vdata.put(1*4+1,     0.0f * (2.0f/screeny));
    vdata.put(1*4+2,     0.0f * (1.0f/256.0f));
    vdata.put(1*4+3,     0.0f * (1.0f/256.0f));
    //  
    vdata.put(2*4+0,   256.0f * (2.0f/screenx));
    vdata.put(2*4+1,   256.0f * (2.0f/screeny));
    vdata.put(2*4+2,   256.0f * (1.0f/256.0f));
    vdata.put(2*4+3,   256.0f * (1.0f/256.0f));
    //  
    vdata.put(3*4+0,   256.0f * (2.0f/screenx));
    vdata.put(3*4+1,     0.0f * (2.0f/screeny));
    vdata.put(3*4+2,   256.0f * (1.0f/256.0f));
    vdata.put(3*4+3,     0.0f * (1.0f/256.0f));

    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

【问题讨论】:

尝试将 GL_NEAREST 更改为 GL_LINEAR 【参考方案1】:

您似乎遇到了舍入问题。由于您显然使用的是 8 位颜色缓冲区,因此通过将颜色乘以 255 将颜色转换为像素值。因此,您的纹理值 0.5/256 将乘以 255 以产生像素值 0.498,这在理想世界中(在 GPU 上进行无限精度计算并四舍五入)将转换为 0 字节值。

事实上,从你得到 1..128,128..255 的事实来看(128 是重复值),它看起来像是将转换向上取整(因为 (127.5/256)*255 是 127.002 和 (128.5/256 )*255 是 127.998——即转换为 128 的两个像素)。如果这是正在发生的事情,我希望您的第三个测试程序在每个像素中产生 128,而不是 127(因为 0.5*255 == 127.5)

结果是,您不能指望 GPU 进行极其精确的计算 - 存在许多舍入误差来源。

【讨论】:

感谢您的回复。当我的第三个测试程序只返回 127 但这也可能是因为一些舍入错误(不确定)。我桌面上的同一个着色器正在工作并返回预期值。我可以做些什么来获得预期的行为吗?我想用(字节值-> 8 位精度)计算的纹理坐标索引纹理,但不知道如何计算它们以考虑舍入。【参考方案2】:

您可能需要更改以下代码行:

precision mediump float;

到:

precision highp float;

在您的片段着色器代码中。

【讨论】:

以上是关于为啥我的着色器中的 GLSL 纹理坐标不是线性的?的主要内容,如果未能解决你的问题,请参考以下文章

在GLSL ES中的片段着色器上旋转纹理

顶点着色器 glsl qt 中的纹理映射

OpenGL 计算着色器中的样本深度缓冲区

OpenGL、GLSL 片段着色器无法读取 Sampler2D 纹理

一次调用可以在 glsl 计算着色器中进行乒乓传播吗?

GLSL索引绘图并设置纹理案例