GLSL 改变每个对象的统一纹理
我目前正在尝试使用不同的纹理(使用 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)。
想法:
为每个纹理使用不同的纹理单元(效果很好),但是如果我们有 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 ); //添加行... GL.Uniform1(GL.GetUniformLocation(this.shaderProgram, "UseBaseColor"), (geoset.Material.FilterMode == Assets.Model.Material.FilterType.Blend) ? 1: 0); // 绘制顶点 &索引缓冲区... }
着色器代码:
统一采样器2D纹理1; 统一 bool UseBaseColor; 无效主(无效) { gl_FragColor =texture2D(Texture1, gl_TexCoord[0].st); 如果(使用基色) gl_FragColor = mix(vec4(0,1,1,1), gl_FragColor , gl_FragColor .a); }
这段代码效果很好,用基色而不是透明度绘制了一些几何图形,(应该?)证明可以在这里更改制服。为什么这不适用于我的纹理?
我应该为每个 geoset 使用不同的着色器程序吗?
提前感谢您的回答:)
问候,
Bruce
编辑:这就是我在渲染器中生成纹理的方式:
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;
}
I'm currently trying to draw simple meshes using different textures (using C# and OpenTK). I read a lot about TextureUnit and bindings, and that's my current implementation (not working as expected) :
private void ApplyOpaquePass()
{
GL.UseProgram(this.shaderProgram);
GL.CullFace(CullFaceMode.Back);
while (this.opaqueNodes.Count > 0)
Draw(this.opaqueNodes.Pop());
GL.UseProgram(0);
}
And my draw method :
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...
}
And my shader code (that shouldn't be the problem since uv mapping is ok):
uniform sampler2D Texture1;
void main(void)
{
gl_FragColor = texture2D(Texture1, gl_TexCoord[0].st);
}
What's the problem :
Since geoset.TextureId can vary from one geoset to another, I'm expecting different texture to be sent to the shader.
Instead, always the same texture is applied to all objects (geosets).
Ideas :
Using different TextureUnit for each textures (working well), but what happens if we have 2000 different textures? If my understanding is right, we must use multiple TextureUnit only if we want to use multiple texture at the same time in the shader.
I first thought that uniforms couldn't be changed once defined, but a test with a boolean uniform told me that it was actually possible.
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... }
Shader code:
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); }
This code works great, drawing some geoset with a base color instead of transparency, that (should ?) prove that uniforms can be changed here. Why this isn't working with my textures ?
Should I use a different shader program per geoset ?
Thanks in advance for your answers :)
Regards,
Bruce
EDIT: that's how I generate textures in the renderer:
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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我终于解决了我的问题!
所有的答案都完善了我的理解,并引导我找到了两个主要问题的解决方案:
1)正如Calvin1602所说,在调用glTexImage2d之前绑定新创建的纹理非常重要。
2)UncleZeiv也引起了我对最后一个GL.Uniform1参数的注意。 OpenTK 教程非常具有误导性,因为该人将纹理对象的 id 传递给函数,这恰好在这里起作用,因为纹理的生成顺序与所使用的 TextureUnit 的 id 完全匹配。
由于我不确定我的理解是否准确,我错误地将这个参数改回了 geoset.TextureId。
谢谢 !
I finally solved my problem !
All the answers perfected my understanding and lead me to the solution which lied on two major problems:
1) as Calvin1602 said, this is very important to bind a newly created texture before calling glTexImage2d.
2) also UncleZeiv rose my attention about the last GL.Uniform1's parameter. The OpenTK tutorial is very misleading because the guy pass the id of the texture object to the function, that happens to work here because the order of generation of the texture exactly matches the id of used TextureUnit.
As I was unsure that my comprehension was exact, I wrongly changed this parameter back to the geoset.TextureId.
Thanks !
如果您唯一要更改的是纹理,则不需要多个着色器程序。此外,统一位置在着色器程序的整个生命周期中都是恒定的,因此无需每帧检索这些位置。但是,每次更改纹理时都需要重新绑定纹理,并且需要将每个不同的纹理绑定到单独的纹理 ID。
因此,我得出的结论是,您发布的内容应该可以工作,因此问题可能出在代码的其他地方。
编辑:更新版本后它应该仍然有效。但是我担心为什么下面一行被注释掉:
这应该在那里。否则你会继续写相同的纹理(这很荒谬)。您需要在初始化之前绑定纹理。现在完全可以想象其他东西被破坏了,但鉴于我现在所看到的,这是我跳出来的唯一错误。
You don't need multiple shader programs if the only thing you are changing is the texture. Also uniform locations are constant throughout the lifetime of a shader program, so there is no need to retrieve those each frame. However, you do need to rebind the texture each time you change it, and you will need to bind each distinct texture to a separate texture ID.
As a result, I would conclude that what you posted ought to work and so the problem is likely somewhere else in your code.
EDIT: After the updated version it should still work. However I am concerned about why the following line is commented out:
This should be in there. Otherwise you will keep over writing the same texture (which is ridiculous). You need to bind the texture before you initialize. Now it is entirely conceivable that something else is broken, but given what I see now this is the only error that jumps out at me.