Texture is rendering as white at some specific orthographic camera rotations

Hello!

Im having a curious problem with my textures that a searched literally in all related topics and can’t solve by myself. Im really new to opengl and want some help, because i really want to solve this problem before continue going on my code.
My rendering seems to put a white texture instead of the desired textures at some camera rotations. The textures im using are the checkerboard and the stairs.

See: https://media.giphy.com/media/Kccb4nH3Xcd4PTK84j/giphy.gif

Vertex shader code:

#version 450 core
			
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec4 a_color;
layout(location = 2) in vec2 a_textureCoord;
layout(location = 3) in float a_textureIndex;
layout(location = 4) in float a_tilingFactor;

uniform mat4 u_viewProjection;

out vec2 v_textureCoord;
out vec4 v_color;
out float v_textureIndex;
out float v_tilingFactor;

void main()
{
	v_textureCoord = a_textureCoord;
	v_color = a_color;
	v_textureIndex = a_textureIndex;
	v_tilingFactor = a_tilingFactor;
    gl_Position = u_viewProjection * vec4(a_position, 1.0);	
}

Fragment shader code:

#version 450 core

layout(location = 0) out vec4 color;

uniform sampler2D u_textures[32];

in vec2 v_textureCoord;
in vec4 v_color;
in float v_textureIndex;
in float v_tilingFactor;
         
void main()
{
	color = texture(u_textures[int(v_textureIndex)], v_textureCoord * v_tilingFactor) * v_color;
}

Looking at the fragment shader, you can see that Im doing some multiplications. When I’m not using a texture (just want to render single color), the texture will be a default created white texture (1x1). When I’m using textures, v_color can be used as a tint (the checkerboard is being colored in red).

The transformation matrix, passed as a uniform, seems fine, since que vertex positions are fine all the time. The problem is that at some specific rotations, the texture component of the multiplication in the fragment shader is just a white color.

The texture binding values seem fine too.

I really don’t know what’s wrong with my code. Maybe it can be the texture creation/settings (glTextureParameteri)?

Texture2D::Texture2D(const std::string& filepath)
   : internalFormat(0), dataFormat(0)
{
   NT_PROFILE_FUNCTION();

   // stbi_load uses signed Int
   Int width, height, channels;
   stbi_set_flip_vertically_on_load(1); // flip the image loaded vertically
   auto data = stbi_load(filepath.c_str(), &width, &height, &channels, 0);
   NT_CORE_ASSERT(data, "Failed to load texture image!");

   // Converts to unsigned because OpenGL uses UInt
   this->width = width;
   this->height = height;

   if (channels == 4)
   {
      this->internalFormat = GL_RGBA8;
      this->dataFormat = GL_RGBA;
   }
   else if (channels == 3)
   {
      this->internalFormat = GL_RGB8;
      this->dataFormat = GL_RGB;
   }

   NT_CORE_ASSERT(this->internalFormat & this->dataFormat,
      "Color channel format not supported!");

   glCreateTextures(GL_TEXTURE_2D, 1, &this->rendererID);

   // Alocates storage on the gpu for the texture
   glTextureStorage2D(
      this->rendererID, 1, this->internalFormat, this->width, this->height);

   // Set linear interpolation (integer parameter) for resizing
   // (expansion or contraction) the texture in the geometry
   glTextureParameteri(this->rendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTextureParameteri(this->rendererID, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

   glTextureParameteri(this->rendererID, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTextureParameteri(this->rendererID, GL_TEXTURE_WRAP_T, GL_REPEAT);

   glTextureSubImage2D(
      this->rendererID, 0, 0, 0, this->width, this->height,
      this->dataFormat, GL_UNSIGNED_BYTE, data);

   // Deallocates the memory
   stbi_image_free(data);
}

The white default texture is created using constructor + setData, and is working fine to rendering the single color quads:

   Texture2D::Texture2D(Int width, Int height)
   : width(width), height(height), internalFormat(GL_RGBA8), dataFormat(GL_RGBA)
{
   NT_PROFILE_FUNCTION();

   NT_CORE_ASSERT(this->internalFormat & this->dataFormat,
      "Color channel format not supported!");

   glCreateTextures(GL_TEXTURE_2D, 1, &this->rendererID);

   // Alocates storage on the gpu for the texture
   glTextureStorage2D(
      this->rendererID, 1, this->internalFormat, this->width, this->height);

   // Set linear interpolation (integer parameter) for resizing
   // (expansion or contraction) the texture in the geometry
   glTextureParameteri(this->rendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTextureParameteri(this->rendererID, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

   glTextureParameteri(this->rendererID, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTextureParameteri(this->rendererID, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

void Texture2D::setData(void* data, UInt size)
{
   NT_PROFILE_FUNCTION();

   glTextureSubImage2D(
      this->rendererID, 0, 0, 0, this->width, this->height,
      this->dataFormat, GL_UNSIGNED_BYTE, data);
}

For rendering, im using batching, so that’s why im using a lot of input at vertex shader.

I would be very grateful if someone can give me some insight about what’s going on.

Thanks!

The first thing that I’d suggest is to change v_textureIndex to an int:

flat out int v_textureIndex;
..
	v_textureIndex = int(a_textureIndex);
...
flat in int v_textureIndex;

Ultimately, it would probably be better to make a_textureIndex an integer (you need to use glVertexAttribIPointer for integer attributes), but casting in the vertex shader is a smaller change and sufficient to determine whether this is related to the problem.

Arrays of samplers can only be indexed with dynamically-uniform expressions. An interpolated float cast to an int in the fragment shader isn’t necessarily dynamically-uniform. I’m not sure that it counts as dynamically-uniform even if the value happens to be the same for each fragment. Nor am I sure that the value will be the same for each fragment even if it the attribute value is the same for each vertex; because int() truncates (rounds downward) rather than rounding to the nearest value, if the attribute values are integers, even the smallest rounding error in the interpolation can change the truncated value.

If all textures have the same dimensions, format and sampling parameters (filter, wrap mode), consider using an array texture instead. Array textures don’t require that the layer be dynamically-uniform, and only use one texture unit.

1 Like

@GClements Omg, using v_textureIndex as int (plus flat), solved the problem. Thank you a lot!!

I was seeing about dynamic uniforms this afternoon, and got a lot more confused about doing the batch rendering for my engine. But now, the question that araises is: why casting “a_textureIndex” to int and passing to flat int seems to be treated as a dynamic uniform integer (it just seems to, I’m not sure).

Variables with the flat qualifier (which is either implied or required for integer variables) aren’t interpolated. Instead, the value from one vertex is used for all fragments in the primitive.

A variable is dynamically uniform if the value is the same for all invocations within a work group. Although the specification allows fragment shader work groups to be larger than a single primitive, I haven’t heard of this happening in practice.

1 Like

Mmmm, understood. So that works in pratice, even if the docs says it is undefined behavior.