多种纹理和纹理单元
你可能想知道为什么在我们甚至没有用 GL.Uniform* 给它赋值的情况下,sampler2D 变量仍然是一个 uniform。实际上,我们可以使用 GL.Uniform1 给纹理采样器分配一个位置值,这样我们就可以在一个片段着色器中同时设置多个纹理。这个纹理的位置更常被称为纹理单元。纹理的默认纹理单元是 0,这也是默认激活的纹理单元,所以在前一节中我们不需要分配位置;请注意,并不是所有的图形驱动程序都会分配默认的纹理单元,所以前一节可能没有为你渲染。
纹理单元的主要目的是允许我们在着色器中使用多个纹理。通过将纹理单元分配给采样器,我们可以在激活相应的纹理单元后同时绑定到多个纹理。就像使用 GL.BindTexture 一样,我们可以使用 GL.ActiveTexture 激活纹理单元,传递我们想要使用的纹理单元:
GL.ActiveTexture(TextureUnit.Texture0); // 在绑定纹理之前先激活纹理单元
GL.BindTexture(TextureTarget.Texture2D, texture);
激活一个纹理单元后,后续的GL.BindTexture调用将把该纹理绑定到当前激活的纹理单元。纹理单元Texture0总是默认激活的,所以在前面的例子中使用GL.BindTexture时我们不需要激活任何纹理单元。
OpenGL 至少应该有 16 个纹理单元供您使用,您可以使用
Texture0到Texture15来激活它们。
我们仍然需要编辑片段着色器以接受另一个采样器。这现在应该相对简单:
#version 330 core
/* ... */
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
最终输出颜色现在是两个纹理查找的组合。GLSL 内置的 mix 函数接受两个值作为输入,并根据其第三个参数在这两个值之间进行线性插值。如果第三个值是 0.0,则返回第一个输入;如果是 1.0,则返回第二个输入值。一个 0.2 的值将返回第一个输入颜色的 80% 和第二个输入颜色的 20%,从而产生我们两个纹理的混合。
我们现在希望加载并创建另一个纹理;你应该对这些步骤很熟悉了。确保创建另一个纹理对象,加载图像并使用 GL.TexImage2D 生成最终纹理。对于第二个纹理,我们将使用一张你在学习 OpenGL 时的表情图片:你在学习OpenGL时的表情。
要使用第二个纹理(以及第一个纹理),我们不得不稍微改变渲染过程,通过将两个纹理绑定到相应的纹理单元。首先,进入 Texture.cs,并修改 Use 函数如下:
void Use(TextureUnit unit = TextureUnit.Texture0)
{
GL.ActiveTexture(unit);
GL.BindTexture(TextureTarget.Texture2D, Handle);
}
然后,添加一个新属性来保存新纹理,并在 OnLoad 中加载它。
Texture texture2;
...
texture2 = new Texture("awesomeface.png");
新的纹理已可用,现在我们必须设置着色器的uniform。在Shader.cs中添加以下函数:
public void SetInt(string name, int value)
{
int location = GL.GetUniformLocation(Handle, name);
GL.Uniform1(location, value);
}
这个函数只是简化了设置着色器统一变量的过程。采样器在CPU中表示为整数;将统一变量设置为整数,它会检查纹理单元。
在OnLoad中创建纹理后立即添加以下几行:
shader.SetInt("texture1", 0);
shader.SetInt("texture2", 1);
这将uniform texture1 设置为使用纹理单元0中的内容,而 texture2 使用纹理单元1中的内容。
最后,将 OnRenderFrame 修改为以下内容:
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BindVertexArray(VertexArrayObject);
texture.Use(TextureUnit.Texture0);
texture2.Use(TextureUnit.Texture1);
shader.Use();
GL.DrawElements(PrimitiveType.Triangles, indices.Length, DrawElementsType.UnsignedInt, 0);
Context.SwapBuffers();
base.OnRenderFrame(e);
现在你应该看到以下内容:纹理已绑定且着色器设置正确。