I tried to implement a simple test program following the OpenTK official tutorial (here) but I can't achieve any result. Following the exact coding sequence proposed my output is just a white square. I tried several patches such as avoiding the fragment shader mix() function and what I found is that the tutorial-proposed code transferred to the fragment shader only the last loaded texture.
In particular, the proposed code lets you generate a texture and assign attributes without binding the texture, while I found some answers that suggest the binding should happen before assigning any attribute (for example here).
So I tried any combination of GL function calls, and I still don't have a working code, but some insights (my latest code below):
TextureUnit.Texture0
seems to reach the fragment shader and be assigned to any declared sampler2DGL.GetUniformLocation
and subsequent GL.Uniform1()
to assign the uniform indexes doesn't produce any visible change in the behaviourGL.UseProgram(shader.Handle)
; doesn't make any differencetexture(tex0, texCoord)
shows the first texture, and so the texture(tex1, texCoord)
or any other declared samplermix(texture(tex0, TexCoord), texture(tex1, TexCoord), 0.2);
fragment shader function shows always white pixels, even when mixing the same texture calling mix(texture(tex0, TexCoord), texture(tex0, TexCoord), 0.2);
(note that such texture show up instead when I call texture(tex0, texCoord)
)I run out of ideas and I can't find any resources. Thanks for helping.
Fragment Shader Source:
#version 330 core
in vec2 texCoord;
out vec4 outputColor;
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
void main() {
//outputColor = mix(texture(tex0, TexCoord), texture(tex1, TexCoord), 0.2);
outputColor = texture(tex0, texCoord);
//outputColor = vec4(1.0, 0.0, 0.0, 1.0);
}
Vertex Shader Source:
#version 330 core
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 texCoord;
void main(void)
{
texCoord = aTexCoord;
gl_Position = vec4(aPosition, 1.0);
}
Texture Class Source:
internal class Texture {
public int Handle { get; private set; }
public Texture(string sourceFile, TextureUnit unit = TextureUnit.Texture0)
{
Image<Rgba32> image = Image.Load<Rgba32>(sourceFile);
image.Mutate(x => x.Flip(FlipMode.Vertical));
var pixels = new byte[4 * image.Width * image.Height];
image.CopyPixelDataTo(pixels);
GL.ActiveTexture(unit);
this.Handle = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, this.Handle);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexImage2D(
TextureTarget.Texture2D, // Texture type
0, // Level of detail
PixelInternalFormat.Rgba, // GPU storing format
image.Width, image.Height, // Width, Height
0, // Border of the image, legacy param
PixelFormat.Rgba, // Format: ImgSharp use this
PixelType.UnsignedByte, // Pixel type
pixels); // Actual pixels
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Use()
{
GL.BindTexture(TextureTarget.Texture2D, this.Handle);
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Use(TextureUnit unit)
{
// GL.ActiveTexture(unit);
// GL.BindTexture(TextureTarget.Texture2D, this.Handle);
}
} // Texture Class
Shader Class Source (only relevant methods):
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Use()
{
GL.UseProgram(program);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetInt(string name, int value)
{
int location = GL.GetUniformLocation(program, name);
GL.Uniform1(location, value);
}
Loading method of the game class:
protected override void OnLoad()
{
GL.ClearColor(0.2f, 0.2f, 0.2f, 1.0f);
VertexArrayObject = GL.GenVertexArray();
VertexBufferObject = GL.GenBuffer();
ElementBufferObject = GL.GenBuffer();
// 1. bind Vertex Array Object
GL.BindVertexArray(VertexArrayObject);
// 2. copy our vertices array in a buffer for OpenGL to use
GL.BindBuffer(BufferTarget.ArrayBuffer, VertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
// 3. then set our vertex attributes pointers
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
GL.EnableVertexAttribArray(1);
// Indices to ElementBufferObject
GL.BindBuffer(BufferTarget.ElementArrayBuffer, ElementBufferObject);
GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw);
shader = new Shader("./ShadersSrc/vshader6_textures2.glsl", "./ShadersSrc/fshader6_textures2.glsl");
texture0 = new Texture("./Textures/src/container.jpg", TextureUnit.Texture0);
shader.SetInt("tex0", 0);
texture1 = new Texture("./Textures/src/awesomeface.png", TextureUnit.Texture1);
shader.SetInt("tex1", 1);
texture2 = new Texture("./Textures/src/wall.jpg", TextureUnit.Texture2);
shader.SetInt("tex1", 2);
base.OnLoad();
}
Frame drawing method of the game class:
protected override void OnRenderFrame(FrameEventArgs e)
{
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BindVertexArray(VertexArrayObject);
shader.Use();
//texture0.Use(TextureUnit.Texture0);
//texture1.Use(TextureUnit.Texture1);
GL.DrawElements(PrimitiveType.Triangles, indices.Length, DrawElementsType.UnsignedInt, 0);
Context.SwapBuffers();
base.OnRenderFrame(e);
}
GL.Uniform1()
sets the value of a uniform variable in the standard uniform block of the current program object. Therefore, you must install the program with GL.UseProgram
before you can set a uniform:
shader.Use();
shader.SetInt("tex0", 0);
shader.SetInt("tex1", 1);