Reputation: 607
I have problem getting cube maps to work in OpenGL. I followed some tutorials and created my own class wrapping the OpenGL cube map texture. For start I'm trying to load six images for the cube map sides and render them onto a pot model. I want to create a mirror-like effect ultimately but for now I'm using just the normal as the cube map texture coordinate. The problem is the sampler in fragment shader seems to be returning only zeroes so the whole model is just black.
my findings so far:
glTexImage2D
) and retrieving the data back (glGetTexImage2D
).glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
or
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
for the cubemap textures, I get invalid operation error inside the rendering loop when I try to render the mirror (the pot) - strange.Here is how I initialize the cube map:
texture_cube = new TextureCubeMap(512,TEXEL_TYPE_COLOR);
cout << "cubemap loading: " << texture_cube->load_ppms("rock.ppm","rock.ppm","rock.ppm","rock.ppm","rock.ppm","rock.ppm") << endl;
texture_cube->update_gpu();
...
glUniform1i(sampler_location,0); // 'tex' unit
glUniform1i(sampler_cube_location,0); // 'tex_cube' unit
Here's my rendering loop:
void render()
{
glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(view_matrix_location,1,GL_TRUE,glm::value_ptr(CameraHandler::camera_transformation.get_matrix()));
texture_cup->bind(0);
glUniformMatrix4fv(model_matrix_location,1,GL_TRUE,glm::value_ptr(transformation_cup.get_matrix()));
geometry_cup->draw_as_triangles();
texture_rock->bind(0);
glUniformMatrix4fv(model_matrix_location,1,GL_TRUE,glm::value_ptr(transformation_rock.get_matrix()));
geometry_rock->draw_as_triangles();
texture_cow->bind(0);
glUniformMatrix4fv(model_matrix_location,1,GL_TRUE,glm::value_ptr(transformation_cow.get_matrix()));
geometry_cow->draw_as_triangles();
texture_room->bind(0);
glUniformMatrix4fv(model_matrix_location,1,GL_TRUE,glm::value_ptr(glm::mat4(1.0)));
geometry_room->draw_as_triangles();
glUniformMatrix4fv(model_matrix_location,1,GL_TRUE, glm::value_ptr(transformation_mirror.get_matrix()));
// draw the mirror:
texture_cube->bind(0);
glUniform1ui(mirror_location,1);
geometry_mirror->draw_as_triangles();
glUniform1ui(mirror_location,0);
ErrorWriter::checkGlErrors("rendering loop");
glutSwapBuffers();
}
Here's my fragment shader:
#version 330
in vec3 transformed_normal;
in vec2 uv_coords;
uniform vec3 light_direction;
uniform sampler2D tex;
uniform samplerCube tex_cube;
uniform bool mirror;
out vec4 FragColor;
float diffuse_intensity;
float lighting_intensity;
void main()
{
diffuse_intensity = clamp(dot(normalize(transformed_normal),-1 * light_direction),0.0,1.0);
lighting_intensity = clamp(0.4 + diffuse_intensity,0.0,1.0);
if (mirror)
{
FragColor = texture(tex_cube, transformed_normal);
}
else
{
FragColor = 0.3 * vec4(lighting_intensity, lighting_intensity, lighting_intensity, 1.0);
FragColor += texture(tex, uv_coords);
}
}
And here is my whole cube map class (Image2D is my own class that should be working OK, I have it tested with 2D textures):
class TextureCubeMap: public Texture
{
protected:
unsigned int size;
public:
Image2D *image_front;
Image2D *image_back;
Image2D *image_left;
Image2D *image_right;
Image2D *image_top;
Image2D *image_bottom;
/**
* Initialises a new cube map object.
*
* @size width and height resolution in pixels (cube map must
* have square size)
*/
TextureCubeMap(unsigned int size, unsigned int texel_type = TEXEL_TYPE_COLOR)
{
this->size = size;
glGenTextures(1,&(this->to));
glBindTexture(GL_TEXTURE_CUBE_MAP,this->to);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_CUBE_MAP,0);
this->image_front = new Image2D(size,size,texel_type);
this->image_back = new Image2D(size,size,texel_type);
this->image_left = new Image2D(size,size,texel_type);
this->image_right = new Image2D(size,size,texel_type);
this->image_top = new Image2D(size,size,texel_type);
this->image_bottom = new Image2D(size,size,texel_type);
}
virtual ~TextureCubeMap()
{
delete this->image_front;
delete this->image_back;
delete this->image_left;
delete this->image_right;
delete this->image_top;
delete this->image_bottom;
}
virtual void update_gpu()
{
int i;
Image2D *images[] =
{this->image_front,
this->image_back,
this->image_left,
this->image_right,
this->image_bottom,
this->image_top};
GLuint targets[] =
{GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y};
glBindTexture(GL_TEXTURE_CUBE_MAP,this->to);
for (i = 0; i < 6; i++)
{
glTexImage2D(targets[i],0,GL_RGBA,this->size,this->size,0,GL_RGBA,GL_FLOAT,images[i]->get_data_pointer());
// glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}
glBindTexture(GL_TEXTURE_CUBE_MAP,0);
}
virtual void load_from_gpu()
{
int i;
glBindTexture(GL_TEXTURE_CUBE_MAP,this->to);
Image2D *images[] =
{this->image_front,
this->image_back,
this->image_left,
this->image_right,
this->image_bottom,
this->image_top};
GLuint targets[] =
{GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y};
for (i = 0; i < 6; i++)
{
images[i]->set_size(this->size,this->size);
glGetTexImage(targets[i],0,GL_RGBA,GL_FLOAT,images[i]->get_data_pointer());
}
}
bool load_ppms(string front, string back, string left, string right, string bottom, string top)
{
bool result = true;
result = result && this->image_front->load_ppm(front);
result = result && this->image_back->load_ppm(back);
result = result && this->image_left->load_ppm(left);
result = result && this->image_right->load_ppm(right);
result = result && this->image_bottom->load_ppm(bottom);
result = result && this->image_top->load_ppm(top);
auto lambda_size_ok = [](Image2D *image, int size)
{
return (image->get_width() == size) && (image->get_height() == size);
};
if (!lambda_size_ok(this->image_front,this->size) ||
!lambda_size_ok(this->image_back,this->size) ||
!lambda_size_ok(this->image_left,this->size) ||
!lambda_size_ok(this->image_right,this->size) ||
!lambda_size_ok(this->image_top,this->size) ||
!lambda_size_ok(this->image_bottom,this->size))
ErrorWriter::write_error("Loaded cubemap images don't have the same size.");
return result;
}
virtual void print()
{
cout << "front:" << endl;
this->image_front->print();
cout << "back:" << endl;
this->image_back->print();
cout << "left:" << endl;
this->image_left->print();
cout << "right:" << endl;
this->image_right->print();
cout << "bottom:" << endl;
this->image_bottom->print();
cout << "top:" << endl;
this->image_top->print();
}
virtual void bind(unsigned int unit)
{
switch (unit)
{
case 0: glActiveTexture(GL_TEXTURE0); break;
case 1: glActiveTexture(GL_TEXTURE1); break;
case 2: glActiveTexture(GL_TEXTURE2); break;
case 3: glActiveTexture(GL_TEXTURE3); break;
case 4: glActiveTexture(GL_TEXTURE4); break;
case 5: glActiveTexture(GL_TEXTURE5); break;
case 6: glActiveTexture(GL_TEXTURE6); break;
case 7: glActiveTexture(GL_TEXTURE7); break;
case 8: glActiveTexture(GL_TEXTURE8); break;
case 9: glActiveTexture(GL_TEXTURE9); break;
default:
break;
}
glBindTexture(GL_TEXTURE_CUBE_MAP,this->to);
}
};
And the images of the error (first one what I get, the second one is with normals rendered):
Upvotes: 0
Views: 3392
Reputation: 54572
You figured out the solution yourself in the comments, but requested some more background on why it behaves the way it does.
Yes, if you want to use two different textures in the same fragment shader, you will have to bind the two textures to different texture units, and set the values of the sampler uniform variables to the matching texture unit indices.
For the case where the two textures use the same target, it's fairly obvious that this must be true. Otherwise your texture binding code would look something like this:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex1);
glBindTexture(GL_TEXTURE_2D, tex2);
and the second glBindTexture()
call would of course override the first one. You will therefore use different texture units:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex2);
Things are much less obvious in the case you are looking at, where you use two different texture targets. After this call sequence:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex1);
glBindTexture(GL_TEXTURE_CUBE_MAP, tex2);
You indeed have both textures bound to texture unit 0. In the OpenGL state, each texture unit contains a binding for each target.
Now, if you want to use a 2D and a cube map texture in the same shader, it seems like a perfectly reasonable expectation that you could bind the two textures as shown above, and set both sampler uniforms to value 0. But in fact, this is not supported, because... it's defined that way.
This is on page 74 of the OpenGL 3.3 spec, in section "2.11.5 Samplers":
It is not allowed to have variables of different sampler types pointing to the same texture image unit within a program object. This situation can only be detected at the next rendering command issued, and an INVALID_OPERATION error will then be generated.
While this makes sense based on how GPUs normally access samplers from shader code, it's unfortunate that you can set up state for texture bindings in ways that are not usable from shader code. Part of this is probably based on the long history of OpenGL.
To make this work, you will have to use two different texture units, just like you did for the case with two textures using the same target:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_CUBE_MAP, tex2);
Upvotes: 3