OpenGL mapping texture to sphere
Asked Answered
F

2

8

I have OpenGL program that I want to texture sphere with bitmap of earth. I prepared mesh in Blender and exported it to OBJ file. Program loads appropriate mesh data (vertices, uv and normals) and bitmap properly- I have checked it texturing cube with bone bitmap.

My program is texturing sphere, but incorrectly (or in the way I don't expect). Each triangle of this sphere includes deformed copy of this bitmap. I've checked bitmap and uv seems to be ok. I've tried many sizes of bitmap (powers of 2, multiples of 2 etc).

Here's the texture:

Earth texture

Screenshot of my program (like It would ignore my UV coords):

enter image description here

Mappings of UVs in Blender I've done in this way:

enter image description here

Code setting texture after loading it (apart from code adding texture to VBO- I think it's ok):

  GLuint texID;
  glGenTextures(1,&texID);
  glBindTexture(GL_TEXTURE_2D,texID);
  glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,width,height,0,GL_BGR,GL_UNSIGNED_BYTE,(GLvoid*)&data[0]);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

Is there needed any extra code to map this texture properly?

[Edit] Initializing textures (earlier presented code is in LoadTextureBMP_custom() function)

bool Program::InitTextures(string texturePath)
{
  textureID = LoadTextureBMP_custom(texturePath);
  GLuint TBO_ID;
  glGenBuffers(1,&TBO_ID);
  glBindBuffer(GL_ARRAY_BUFFER,TBO_ID);
  glBufferData(GL_ARRAY_BUFFER,uv.size()*sizeof(vec2),&uv[0],GL_STATIC_DRAW);
  return true;
}

My main loop:

bool Program::MainLoop()
{
  bool done = false;
  mat4 projectionMatrix;
  mat4 viewMatrix;
  mat4 modelMatrix;
  mat4 MVP;

  Camera camera;
  shader.SetShader(true);

  while(!done)
  {
    if( (glfwGetKey(GLFW_KEY_ESC)))
      done = true;
    if(!glfwGetWindowParam(GLFW_OPENED))
      done = true;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Tutaj przeksztalcenia macierzy
    camera.UpdateCamera();
    modelMatrix = mat4(1.0f);
    viewMatrix = camera.GetViewMatrix();
    projectionMatrix = camera.GetProjectionMatrix();
    MVP = projectionMatrix*viewMatrix*modelMatrix;
    // Koniec przeksztalcen

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,textureID);

    shader.SetShaderParameters(MVP);

    SetOpenGLScene(width,height);

    glEnableVertexAttribArray(0); // Udostepnienie zmiennej Vertex Shadera => vertexPosition_modelspace
    glBindBuffer(GL_ARRAY_BUFFER,VBO_ID);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER,TBO_ID);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,0,(void*)0);

    glDrawArrays(GL_TRIANGLES,0,vert.size());

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    glfwSwapBuffers();
  }
  shader.SetShader(false);

  return true;
}

VS:

#version 330

layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec2 vertexUV;
out vec2 UV;

uniform mat4 MVP;

void main()
{
  vec4 v = vec4(vertexPosition,1.0f);
  gl_Position = MVP*v;
  UV = vertexUV;
}

FS:

#version 330

in vec2 UV;
out vec4 color;

uniform sampler2D texSampler; // Uchwyt tekstury

void main()
{
  color = texture(texSampler, UV);
}
Flunky answered 5/7, 2013 at 11:52 Comment(14)
That seems like a UV mapping problem. Are you sure the UV coordinates are correct in your model? That is to say, does it look OK in Blender?Clubbable
what is the range of your UV values?Roll
p.s. that texture is not suitable for mapping on a sphere. You need one that has been stretched into a cylindrical map, like this one - paulbourke.net/geometry/transformationprojection/earth.jpgRoll
@Alnitak: Actually the topology of OP's texture is better suited for a sphere than a mercator map. This has to do with the topological properties of a sphere, which can be mapped into 2 dimensional bounded manifolds only if at least two separate manifolds are used. Trying to strech a single bounded 2-manifold into a sphere will leave singularities, also knowns as poles.Excurvate
@Excurvate true if you have infinite resolution; the OP's texture loses resolution at the edges of the earth rather rapidly though. The best way I've seen to texture a sphere is to construct the sphere from a regular polyhedron that is textured per-side then the sides are deformed to form the sphere.Steddman
I understand this kind of mapping sphere is not too suitable for model of sphere, but it was only in test purposes to do the same thing on more complex models. Important is fact that in another blender models, OpenGL mapping texture in this way. Range of my UV is [0,1]. First 3 UVs is (about) (0.25,0.5,),(0.26,0.53),(0.26,0.54). I've tried divide UV's by width/height (u by width, v by height) and it not change nothing.Flunky
@Flunky well, how do you pass and/or use the UV data? Seems like the problem is either your UVs themselves, your passing of the UVs to the GPU or your usage of the UVs in the shader. We don't have information about any of those besides the UV sample you posted.Foehn
@Excurvate the link I provided isn't a mercator map, it's a cylindrical map, and it's perfect for mapping onto a sphere. It has the opposite problem to what you describe, because the singularity at the sphere's poles gets stretched into a (horizontal line) in the cylindrical map. That line gets compressed back to a point when its mapped back onto the sphere. The problem with the OP's texture is that it's actually just two pictures of a spherical globe, and doesn't even give remotely near 100% coverage.Roll
@Alnitak: A mercator map is a special variant of a cylindrical projection. There are several others, like the Mollweide projection. But they all share the principal mathematical flaw, that at the northern and southern limits they collapse into singularity. Applying that into a rasterized mesh will give very strange looking poles, because at the pole itself you can no longer determine which actual pixel from the texture to use. This is a topological property of spherical mappings and can only resolved by using more than one map.Excurvate
Main problem I see there is that texels of every mapping triangle is pixels from begin of bitmap even though UVs is for example (0.25,0.5). Clipping texture in this case is normal (I guess).Flunky
@Excurvate sure, I know that cylindrical maps aren't perfect, but IMHO they're a damned sight better than what the OP has now. In practise the collapsing at the poles isn't too horrible (see e.g. bellis.me.uk/WebGL/globe.html)Roll
@Alnitak: Instead of cylindrical texture it'll be better to use cubical map and 3d texture coordinates. This way there'll be no singularities and no seams. Cylindrical map will look bad at poles.Teazel
Procedurally generate some test UV-mapped sphere(or cube, plane or some other primitive) yourself (in your own code) and see if UV mapping works correctly your shader. IF it does work correctly, it will mean either blender exporter or your file format reader is broken.Teazel
Related problem: #9511999 OpenGL ES: #322611Implacental
F
1

Finally, I've got the answer. Error was there:

bool Program::InitTextures(string texturePath)
{
  textureID = LoadTextureBMP_custom(texturePath);
  // GLuint TBO_ID; _ERROR_
  glGenBuffers(1,&TBO_ID);
  glBindBuffer(GL_ARRAY_BUFFER,TBO_ID);
  glBufferData(GL_ARRAY_BUFFER,uv.size()*sizeof(vec2),&uv[0],GL_STATIC_DRAW);
}

There is the part of Program class declaration:

class Program
{
 private:
  Shader shader;
  GLuint textureID;

  GLuint VAO_ID;
  GLuint VBO_ID;
  GLuint TBO_ID; // Member covered with local variable declaration in InitTextures()
  ...
}

I've erroneously declared local TBO_ID that covered TBO_ID in class scope. UVs were generated with crummy precision and seams are horrible, but they weren't problem.

enter image description here

I have to admit that information I've supplied is too small to enable help. I should have put all the code of Program class. Thanks everybody who tried to.

Flunky answered 9/7, 2013 at 14:47 Comment(0)
W
1

I haven't done any professional GL programming, but I've been working with 3D software quite a lot.

  • your UVs are most likely bad
  • your texture is a bad fit to project on a sphere
  • considering UVs are bad, you might want to check your normals as well
  • consider an ISOSPHERE instead of a regular one to make more efficient use of polygons

You are currently using a flat texture with flat mapping, which may give you very ugly results, since you will have very low resolution in the "outer" perimeter and most likely a nasty seam artifact where the two projections meet if you like... rotate the planet or something.

Note that you don't have to have any particular UV map, it just needs to be correct with the geometry, which it doesn't look like it is right now. The spherical mapping will take care for the rest. You could probably get away with a cylindrical map as well, since most Earth textures are in a suitable projection.

Wingless answered 5/7, 2013 at 15:31 Comment(4)
I've testing my program with other models and it works in the same fashion, mapping one instance of texture to every triangle of model. Thanks for clear advices.Flunky
@Flunky - this looks like you have no UVs, e.g. all UVs take up the entire space and are overlapping on top of each other rather than aligned in a flat grid as they need to be. Are you supplying the UV coordinates yourself? Maybe you don't provide and what you see is the default behavior, e.g. every face fills the entire texture space.Wingless
I downloaded simple .obj models (with uvs) and try them out. Its the same, one texture for one face (triangle). Something must be wrong with code, perhaps...Flunky
So maybe the UVs are there but the shaders are not getting the right data somehow. Can't be of much help in that regard yet, I have GL planned further down the line. I guess for the sake of experiment you can try to pass the UV data and render it on a flat surface to see what it actually represents.. visually debug.Wingless
F
1

Finally, I've got the answer. Error was there:

bool Program::InitTextures(string texturePath)
{
  textureID = LoadTextureBMP_custom(texturePath);
  // GLuint TBO_ID; _ERROR_
  glGenBuffers(1,&TBO_ID);
  glBindBuffer(GL_ARRAY_BUFFER,TBO_ID);
  glBufferData(GL_ARRAY_BUFFER,uv.size()*sizeof(vec2),&uv[0],GL_STATIC_DRAW);
}

There is the part of Program class declaration:

class Program
{
 private:
  Shader shader;
  GLuint textureID;

  GLuint VAO_ID;
  GLuint VBO_ID;
  GLuint TBO_ID; // Member covered with local variable declaration in InitTextures()
  ...
}

I've erroneously declared local TBO_ID that covered TBO_ID in class scope. UVs were generated with crummy precision and seams are horrible, but they weren't problem.

enter image description here

I have to admit that information I've supplied is too small to enable help. I should have put all the code of Program class. Thanks everybody who tried to.

Flunky answered 9/7, 2013 at 14:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.