【问题标题】:GLSL change uniform texture for each objectGLSL 更改每个对象的统一纹理
【发布时间】:2011-06-27 13:57:19
【问题描述】:

我目前正在尝试使用不同的纹理(使用 C# 和 OpenTK)绘制简单的网格。我阅读了很多有关 TextureUnit 和绑定的信息,这就是我当前的实现(未按预期工作):

private void ApplyOpaquePass()
{
    GL.UseProgram(this.shaderProgram);
    GL.CullFace(CullFaceMode.Back);

    while (this.opaqueNodes.Count > 0)
        Draw(this.opaqueNodes.Pop());

    GL.UseProgram(0);
}

还有我的绘制方法:

private void Draw(Assets.Model.Geoset geoset)
{
    GL.ActiveTexture(TextureUnit.Texture1);
    GL.BindTexture(TextureTarget.Texture2D, geoset.TextureId /*buffer id returned by GL.GenTextures*/ );
    GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, "Texture1"), 1 /*see note below*/ );

    //Note: if I'm correct, it should be 1 when using TextureUnit.Texture1
    //      (2 for Texture2...), note that doesn't seem to work since no
    //      texture texture at all is sent to the shader, however a texture
    //      is shown when specifying any other number (0, 2, 3...)

    // Draw vertices & indices buffers...
}

还有我的着色器代码(这应该不是问题,因为 uv 映射没问题):

uniform sampler2D Texture1;

void main(void)
{
    gl_FragColor = texture2D(Texture1, gl_TexCoord[0].st);
}


有什么问题:

  • 由于 geoset.TextureId 可以从一个 geoset 到另一个 geoset 不同,我希望将不同的纹理发送到着色器。

  • 相反,始终将相同的纹理应用于所有对象(geoset)。


想法:

  • 为每个纹理使用不同的 TextureUnit(效果很好),但是如果我们有 2000 个不同的纹理会发生什么?如果我的理解是正确的,只有当我们想在着色器中同时使用多个纹理时,我们才必须使用多个 TextureUnit。

  • 我最初认为制服一旦定义就无法更改,但使用布尔制服的测试告诉我这实际上是可能的。

    private void Draw(Assets.Model.Geoset geoset)
    {
        GL.ActiveTexture(TextureUnit.Texture1);
        GL.BindTexture(TextureTarget.Texture2D, geoset.TextureId);
        GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, "Texture1"), 1 );
    
        //added line...
        GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, "UseBaseColor"), (geoset.Material.FilterMode == Assets.Model.Material.FilterType.Blend) ? 1: 0);
    
        // Draw vertices & indices buffers...
    }
    

    着色器代码:

    uniform sampler2D Texture1;
    uniform bool UseBaseColor;
    
    void main(void)
    {
        gl_FragColor = texture2D(Texture1, gl_TexCoord[0].st);
    
        if (UseBaseColor)
            gl_FragColor = mix(vec4(0,1,1,1), gl_FragColor , gl_FragColor .a);
    }
    

    这段代码效果很好,用基色而不是透明度绘制了一些 geoset,这(应该?)证明可以在此处更改制服。为什么这不适用于我的纹理?

  • 我应该为每个 geoset 使用不同的着色器程序吗?



提前感谢您的回答 :)

问候,
布鲁斯

编辑:这就是我在渲染器中生成纹理的方式:

override public uint GenTexture(Bitmap bmp)
{
    uint texture;
    GL.GenTextures(1, out texture);

        //I disabled this line because I now bind the texture before drawing a geoset
        //Anyway, uncommenting this line doesn't show a better result
        //GL.BindTexture(TextureTarget.Texture2D, texture);

    System.Drawing.Imaging.BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
        OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
    bmp.UnlockBits(data);

    //temp settings
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);

    return texture;
}

【问题讨论】:

  • 您发布的代码似乎是有道理的,正如您假设可以在绘图调用之间更改制服并且可以为不同的纹理重用纹理单元,只要对于给定的绘制调用/着色器,您不需要超过一个。要么我遗漏了什么,要么问题出在其他地方,可能是你的纹理创建方式?
  • 是的;您确定在 Draw() 中正确设置了 geoset.TextureID 吗?在 GenTexture 中编辑,在调用 texImage2d 之前绑定纹理非常重要,否则您将覆盖之前绑定的纹理而不是新的纹理
  • 如果我中断 Draw 方法,我可以看到 geoset.TextureID 的不同值(用于生成纹理的材料数据也是正确的,即文件名......),但所有结果通过着色器渲染相同的纹理。 (请注意,使用标准 glEnable(TEXTURE_2D) 方法正确淹没了纹理)我取消注释 GenTexture 方法中的 BindTexture 调用,但没有明显变化。
  • 我担心您代码中的第一个注释。你能进一步详细说明吗?如果 1 不起作用而所有其他值都起作用,那么您在代码中传递的实际值是多少?使用其他数字时您看到的纹理是什么?
  • 最初,我遵循了一个教程 (opentk.com/node/2559),每个纹理使用一个纹理单元并将 Uniform 与纹理缓冲区 Id 绑定,这很方便,因为他只创建了 4 个纹理,并且可以为每个纹理映射 TextureUnit1 到 4 (以正确的顺序)。但是当纹理计数未确定时,这是不切实际的。在我的代码中,我不需要多个 TextureUnit,因为我不渲染多材质(至少目前不需要),所以我只使用 textureunit 的插槽 1(如果我保持默认 TU,则为零)。

标签: opengl glsl textures


【解决方案1】:

我终于解决了我的问题! 所有的答案都完善了我的理解,并引导我找到了解决两个主要问题的方法:

1) 正如 Calvin1602 所说,在调用 glTexImage2d 之前绑定新创建的纹理非常重要。

2) UncleZeiv 也引起了我对最后一个 GL.Uniform1 参数的注意。 OpenTK 教程非常具有误导性,因为该人将纹理对象的 id 传递给函数,这恰好在这里起作用,因为纹理的生成顺序与使用的 TextureUnit 的 id 完全匹配。 由于我不确定我的理解是否准确,我错误地将这个参数改回 geoset.TextureId。

谢谢!

【讨论】:

  • 反馈:经过一些测试,结果表明每个对象使用一个着色器程序并调用 UseProgram 在 gpu 内存和指令方面非常昂贵。但是,我不喜欢超级条件分支着色器的想法,因此我的下一个实现依赖于使用动态代码生成系统生成合理数量的着色器来使用(或不使用)着色器功能。
【解决方案2】:

如果您要更改的只是纹理,则不需要多个着色器程序。同样,统一位置在着色器程序的整个生命周期中都是恒定的,因此无需在每一帧中检索这些位置。但是,每次更改纹理时都需要重新绑定纹理,并且需要将每个不同的纹理绑定到单独的纹理 ID。

因此,我认为您发布的内容应该可以正常工作,因此问题可能出在您代码中的其他地方。

编辑:更新版本后它应该仍然可以工作。但是我担心为什么以下行被注释掉:

 //GL.BindTexture(TextureTarget.Texture2D, texture);

这应该在那里。否则,您将继续编写相同的纹理(这很荒谬)。您需要在初始化之前绑定纹理。现在完全可以想象其他东西坏了,但鉴于我现在看到的,这是我唯一跳出来的错误。

【讨论】:

  • 感谢这两个答案,他们至少证实了我对事物的理解是正确的! PS:我用纹理生成代码更新了第一篇文章。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 2018-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多