纹理是实时渲染中的非常基础且非常重要的一个主题,它一直作为增强渲染效果的一个强有力手段。在固定渲染管线的opengl中,纹理的使用灵活度非常有限,有了shader之后,我们可以在shader中操作纹理,这时就可以用一些额外的渲染参数来渲染纹理,比如位移图(displacement maps),法向量(normal vectors)等等。
实际上在OpenGL4.0中,纹理不仅仅是图像信息,更准确的,它应该是内存中一个块区。
这是最简单的一种贴图了。从文件读取图片文件,然后绑定贴图,绘制到顶点。
一般的3D模型制作软件都可以制作UV贴图,推荐使用Blender + GIMP,制作的方法可以参考这里。
接着按照前面说过的方法导出box.obj文件,待会用来加载。
在Blender中导出obj文件中,没有设置和设置材质导出的最终格式并不一样,不止是有无纹理坐标的问题,面定义的格式也改变了,这里需要重写一个加载obj文件的函数。
bool Util::loadObjWithTex(const char * path,std::vector<glm::vec3> & out_vertices,std::vector<glm::vec2> & out_uvs,std::vector<glm::vec3> & out_normals) { printf("Loading OBJ file with tex %s...\n", path); std::vector<unsigned int> vertexIndices, uvIndices, normalIndices; std::vector<glm::vec3> temp_vertices; std::vector<glm::vec2> temp_uvs; std::vector<glm::vec3> temp_normals; FILE * file = fopen(path, "r"); if( file == NULL ){ printf("Impossible to open the file ! Are you in the right path ? \n"); return false; } while( 1 ){ char lineHeader[128]; // read the first word of the line int res = fscanf(file, "%s", lineHeader); if (res == EOF) break; // EOF = End Of File. Quit the loop. // else : parse lineHeader if ( strcmp( lineHeader, "v" ) == 0 ){ //cout<<"Get v"<<endl; glm::vec3 vertex; fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z ); temp_vertices.push_back(vertex); }else if ( strcmp( lineHeader, "vt" ) == 0 ){ //cout<<"Get vt"<<endl; glm::vec2 uv; fscanf(file, "%f %f\n", &uv.x, &uv.y ); //cout<<"uv.x"<<uv.x<<" uv.y"<<uv.y; uv.y = -uv.y; // Invert V coordinate since we will only use DDS texture, which are inverted. Remove if you want to use TGA or BMP loaders. temp_uvs.push_back(uv); }else if ( strcmp( lineHeader, "vn" ) == 0 ){ // cout<<"Get vn"<<endl; glm::vec3 normal; fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z ); temp_normals.push_back(normal); }else if ( strcmp( lineHeader, "f" ) == 0 ){ //cout<<"Get f"<<endl; std::string vertex1, vertex2, vertex3; unsigned int vertexIndex[3], uvIndex[3], normalIndex[3]; int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] ); if (matches != 9){ printf("File can't be read by our simple parser :-( Try exporting with other options\n"); return false; } vertexIndices.push_back(vertexIndex[0]); vertexIndices.push_back(vertexIndex[1]); vertexIndices.push_back(vertexIndex[2]); uvIndices.push_back(uvIndex[0]); uvIndices.push_back(uvIndex[1]); uvIndices.push_back(uvIndex[2]); normalIndices.push_back(normalIndex[0]); normalIndices.push_back(normalIndex[1]); normalIndices.push_back(normalIndex[2]); }else{ // Probably a comment, eat up the rest of the line char stupidBuffer[1000]; fgets(stupidBuffer, 1000, file); } } // For each vertex of each triangle for( unsigned int i=0; i<vertexIndices.size(); i++ ){ // Get the indices of its attributes unsigned int vertexIndex = vertexIndices[i]; unsigned int uvIndex = uvIndices[i]; unsigned int normalIndex = normalIndices[i]; // Get the attributes thanks to the index glm::vec3 vertex = temp_vertices[ vertexIndex-1 ]; glm::vec2 uv = temp_uvs[ uvIndex-1 ]; glm::vec3 normal = temp_normals[ normalIndex-1 ]; // Put the attributes in buffers out_vertices.push_back(vertex); out_uvs .push_back(uv); out_normals .push_back(normal); } return true; }
在initGL中需要启动2D纹理,添加一句:
glEnable(GL_TEXTURE_2D);
同时还要添加的是纹理的加载函数,用到了SDL的image库。
首先是在codeblocks工程中把库添加进来:
然后在CGL类中添加方法:
bool CGL::loadTexture(const char *name) { int mode; GLuint texture; SDL_Surface* surface = IMG_Load(name); SDL_SetAlpha(surface, SDL_SRCALPHA, 0); if (surface==NULL) { //If it failed, say why and don't continue loading the texture cout<<"Error:" <<SDL_GetError()<<endl; return 0; } // work out what format to tell glTexImage2D to use... if (surface->format->BytesPerPixel == 3) { // RGB 24bit mode = GL_RGB; } else if (surface->format->BytesPerPixel == 4) { // RGBA 32bit mode = GL_RGBA; } else { SDL_FreeSurface(surface); return 0; } glActiveTexture(GL_TEXTURE0); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, mode, surface->w,surface->h, 0, mode,GL_UNSIGNED_BYTE,surface->pixels); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT ); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT ); if(surface) SDL_FreeSurface(surface); prog.setUniform("Tex1", 0); return true; }
#version 400 layout (location = 0) in vec3 VertexPosition; layout (location = 1) in vec3 VertexNormal; layout (location = 2) in vec2 VertexTexCoord; out vec4 Position; out vec3 Normal; out vec2 TexCoord; uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP; void getEyeSpace(out vec3 norm, out vec4 position) { norm = normalize(NormalMatrix * VertexNormal); position = ModelViewMatrix * vec4(VertexPosition, 1.0); } void main() { getEyeSpace(Normal, Position); TexCoord = VertexTexCoord; gl_Position = MVP * vec4( VertexPosition, 1.0); }
这里的layout用于一个具体变量前,用于显式标明该变量的一些布局属性,location的值和前面glBindAttribLocation的位置是一一对应的。
在顶点shader中获取VertexTexCoord之后,不经处理直接扔给Fregment shader,接下来在片段 shader中:
#version 400 in vec4 Position; in vec3 Normal; in vec2 TexCoord; struct LightInfo{ vec4 position; vec3 intensity; }; struct MaterialInfo{ vec3 Ka; vec3 Kd; vec3 Ks; float Shininess; }; uniform sampler2D Tex1; uniform LightInfo Light; uniform MaterialInfo Material; void phongModel(vec4 position, vec3 norm, out vec3 amb, out vec3 diff, out vec3 spec) { vec3 tmp = vec3(0.9f, 0.5f, 0.3f); vec3 s = normalize(vec3(Light.position - position)); vec3 v = normalize(-position.xyz); vec3 r = reflect(-s, norm); amb = Light.intensity * Material.Ka; float sDotN = max(dot(s, norm),0.0); diff = Light.intensity * Material.Kd * sDotN; spec = vec3(0.0); if(sDotN > 0.0) spec = Light.intensity *Material.Ks * pow( max( dot(r,v), 0.0 ),Material.Shininess ); } void main(void) { vec3 amb,diff,spec; vec4 texColor = texture(Tex1, TexCoord); phongModel(Position, Normal, amb, diff, spec); //Render without light effect. gl_FragColor = (vec4( amb + diff, 1.0) * texColor) + vec4(spec, 1.0); }
在main函数中主要是通过GLSL内置的纹理函数 - texture 来将与纹理坐标对应的纹理值从内存中取出来,接下来和光照的颜色一起混合,得到最后的颜色。
编译运行一下:
多纹理的实现比较简单,就是将多个纹理加载到内存,然后混合得到最终纹理的颜色。
纹理的加载函数需要修改一下:
bool CGL::loadTexture() { int mode; GLuint texIDs[2]; glGenTextures(2, texIDs); // Load brick brake file const char * texName = "assets/textures/crate.bmp"; SDL_Surface* surface = IMG_Load(texName); SDL_SetAlpha(surface, SDL_SRCALPHA, 0); if (surface==NULL) { //If it failed, say why and don't continue loading the texture cout<<"Error:" <<SDL_GetError()<<endl; return 0; } // work out what format to tell glTexImage2D to use... if (surface->format->BytesPerPixel == 3) { // RGB 24bit mode = GL_RGB; } else if (surface->format->BytesPerPixel == 4) { // RGBA 32bit mode = GL_RGBA; } else { SDL_FreeSurface(surface); return 0; } // Copy file to OpenGL glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texIDs[0]); glTexImage2D(GL_TEXTURE_2D, 0, mode, surface->w,surface->h, 0, mode,GL_UNSIGNED_BYTE,surface->pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Load moss texture file texName = "assets/textures/smile.png"; surface = IMG_Load(texName); SDL_SetAlpha(surface, SDL_SRCALPHA, 0); if (surface==NULL) { //If it failed, say why and don't continue loading the texture cout<<"Error:" <<SDL_GetError()<<endl; return 0; } // work out what format to tell glTexImage2D to use... if (surface->format->BytesPerPixel == 3) { // RGB 24bit mode = GL_RGB; } else if (surface->format->BytesPerPixel == 4) { // RGBA 32bit mode = GL_RGBA; } else { SDL_FreeSurface(surface); return 0; } // Copy file to OpenGL glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texIDs[1]); glTexImage2D(GL_TEXTURE_2D, 0, mode, surface->w,surface->h, 0, mode,GL_UNSIGNED_BYTE,surface->pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); if(surface) SDL_FreeSurface(surface); prog.setUniform("Tex1", 0); prog.setUniform("Tex2", 1); return true; }
然后修改fregment shader:
#version 400 in vec4 Position; in vec3 Normal; in vec2 TexCoord; struct LightInfo{ vec4 position; vec3 intensity; }; struct MaterialInfo{ vec3 Ka; vec3 Kd; vec3 Ks; float Shininess; }; uniform sampler2D Tex1; uniform sampler2D Tex2; uniform LightInfo Light; uniform MaterialInfo Material; void phongModel(vec4 position, vec3 norm, out vec3 amb, out vec3 diff, out vec3 spec) { vec3 tmp = vec3(0.9f, 0.5f, 0.3f); vec3 s = normalize(vec3(Light.position - position)); vec3 v = normalize(-position.xyz); vec3 r = reflect(-s, norm); amb = Light.intensity * Material.Ka; float sDotN = max(dot(s, norm),0.0); diff = Light.intensity * Material.Kd * sDotN; spec = vec3(0.0); if(sDotN > 0.0) spec = Light.intensity *Material.Ks * pow( max( dot(r,v), 0.0 ),Material.Shininess ); } void main(void) { vec3 amb,diff,spec; vec4 crateColor = texture(Tex1, TexCoord); vec4 mosColor = texture(Tex2, TexCoord); phongModel(Position, Normal, amb, diff, spec); vec4 texColor = mix(crateColor,mosColor,mosColor.a); //Render without light effect. gl_FragColor = (vec4( amb + diff, 1.0) * texColor) + vec4(spec, 1.0); }
源码下载
OpenGL 4.0 Shading Langage Cookbook