Problem
I'm currently working on multi UV support for glTF-models, and after getting a first version up and running (checking against glTF's multi-uv-test) I checked if the other models are still running fine.
I noticed some very visible artifacts (left image) for the Sponza model which has only one set of texture coordinates.
I could at least find the code that seems to cause this issue in a fragment shader (the condition should always be true for Sponza):
vec2 getBaseColorTexCoords() {
return u_material.baseColorTexCoordSet == 0 ? o_texCoords0 : o_texCoords1;
// return o_texCoords0;
}
When I return the second line instead, I get the desired output (right image).
Some additional stuff:
- If I change the condition to
true
it works fine. I guess the branching is just optimized away in that case - If I change the condition to
false
I get a black screen (texCoords1
attribute disabled for Sponza) - If I pass
o_texCoords0
on both sides of the colon:
I still get these artifacts, but not if I just returno_texCoords0
in analogous functions for other texture maps (eg. normal or metallic-roughness)
The only place where I use this function:
vec4 getBaseColor() {
vec4 baseColor = u_material.baseColor;
if(u_hasBaseColorTexture) {
vec2 texCoords = getBaseColorTexCoords();
baseColor *= srgbToLinear4fv(texture(u_baseColorTexture, texCoords));
}
if(u_hasVertexColors) {
baseColor *= o_color;
}
return baseColor;
}
Analogous functions:
vec2 getMetallicRoughnessTexCoords() {
return u_material.metallicRoughnessTexCoordSet == 0 ? o_texCoords0 : o_texCoords1;
// return o_texCoords0;
}
vec2 getNormalTexCoords() {
return u_material.normalTexCoordSet == 0 ? o_texCoords0 : o_texCoords1;
// return o_texCoords0;
}
Additional Code Snippets
First I suspected that perhaps u_material.baseColorTexCoordSet
somehow somewhere gets assigned a value that is not equal to 0 on the CPU side. But that is not the case and to make doubly sure I'm passing in the constant 0:
val baseColorTexCoordSetLoc = glGetUniformLocation(shaderProgram, "u_material.baseColorTexCoordSet")
// glUniform1i(baseColorTexCoordSetLoc, material.baseColorTexCoordSet)
glUniform1i(baseColorTexCoordSetLoc, 0)
I don't think it is the cause, but here are some code snippets related to o_texCoords1
, which gets its value from a disabled vertex attribute:
GPU:
// Vertex shader
layout (location = 1) in vec2 texCoords0;
layout (location = 2) in vec2 texCoords1;
out vec2 o_texCoords0;
out vec2 o_texCoords1;
// Inside main()...
o_texCoords0 = texCoords0;
o_texCoords1 = texCoords1;
// Fragment shader
in vec2 o_texCoords0;
in vec2 o_texCoords1;
CPU:
if(primitives.texCoords0.isNotEmpty()) {
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, offset)
offset += primitives.texCoords0.size * Float.SIZE_BYTES
}
// This is empty for the Sponza model
if(primitives.texCoords1.isNotEmpty()) {
glEnableVertexAttribArray(2)
glVertexAttribPointer(2, 2, GL_FLOAT, false, 0, offset)
offset += primitives.texCoords1.size * Float.SIZE_BYTES
}
"Question"
Currently I'm suspecting that this is some GPU branching issue.
I guess this might be difficult to answer, but any clues or hints on how to best tackle this problem are welcomed.
Edit
Changing the precision to highp float
helps resolve the issue (not sure why), though that's not desirable on mobile.
Edit 2
I've decided to replace my uniform branches with preprocessor directives:
vec2 getBaseColorTexCoords() {
vec2 texCoords;
#ifdef BASE_COLOR_TEX_COORD_SET_0
texCoords = o_texCoords0;
#else
texCoords = o_texCoords1;
#endif
return texCoords;
}
Cons: more cumbersome to maintain (perhaps also program switches, but I haven't benchmarked yet and it seems to run just fine)
Pros: no artifacts, don't have to use highp
precision, recommended by Mali:
Use #defines at compile time in OpenGL ES, and specialization constants in Vulkan for all control flow. Doing so allows the compilation to completely remove unused code blocks and statically unroll loops.
So I'll roll with this for now.
u_material
is a uniform. And thanks for the pointer, I'll see if I can read up something. – Beko May 24 '22 at 22:46GL_LINEAR_MIPMAP_LINEAR
as the min filter. Changing this and disabling mipmapping has no effect on the issue, however. – Beko May 25 '22 at 15:52return t0;
worked butreturn cond ? t0 : t0
didn't, because in the latter case the varying is involved in some control-flow logic and thereby some precision is lost. I guess in thehighp
case there was enough precision even after some loss? Anyway, considering that it also works with preprocessor directives I'd say that I indeed get better precision when using them directly. Is there an actual way to measure/debug this? – Beko May 26 '22 at 19:42highp
for textures greater than 512x512 (Sponza uses 1024x1024 textures). Maybe two more questions before I let go: 1. do you have a link where I could read about that hardware optimization for varying tex coords and 2. do method calls also influence this? So is it ok to access them viagetTexCoords()
if the method just does areturn texCoords
? Thanks a lot! – Beko May 27 '22 at 18:23