OpenGL进阶(十六) :GLSL纹理(Texture)

发表于2017-09-11
评论0 3.1k浏览

提要

        纹理是实时渲染中的非常基础且非常重要的一个主题,它一直作为增强渲染效果的一个强有力手段。在固定渲染管线的opengl中,纹理的使用灵活度非常有限,有了shader之后,我们可以在shader中操作纹理,这时就可以用一些额外的渲染参数来渲染纹理,比如位移图(displacement maps),法向量(normal vectors)等等。

       实际上在OpenGL4.0中,纹理不仅仅是图像信息,更准确的,它应该是内存中一个块区。


UV贴图

       这是最简单的一种贴图了。从文件读取图片文件,然后绑定贴图,绘制到顶点。

       一般的3D模型制作软件都可以制作UV贴图,推荐使用Blender GIMP。

接着按照前面说过的方法导出box.obj文件,待会用来加载。

      在Blender中导出obj文件中,没有设置和设置材质导出的最终格式并不一样,不止是有无纹理坐标的问题,面定义的格式也改变了,这里需要重写一个加载obj文件的函数。

  1. bool Util::loadObjWithTex(const char * path,std::vector & out_vertices,std::vector & out_uvs,std::vector & out_normals)  
  2. {  
  3.     printf("Loading OBJ file with tex %s...\n", path);  
  4.     std::vector<unsigned int</unsigned > vertexIndices, uvIndices, normalIndices;  
  5.     std::vector temp_vertices;  
  6.     std::vector temp_uvs;  
  7.     std::vector temp_normals;  
  8.   
  9.     FILE * file = fopen(path, "r");  
  10.     if( file == NULL ){  
  11.         printf("Impossible to open the file ! Are you in the right path ? \n");  
  12.         return false;  
  13.     }  
  14.     while( 1 ){  
  15.         char lineHeader[128];  
  16.         // read the first word of the line  
  17.         int res = fscanf(file, "%s", lineHeader);  
  18.         if (res == EOF) break// EOF = End Of File. Quit the loop.  
  19.         // else : parse lineHeader  
  20.         if ( strcmp( lineHeader, "v" ) == 0 ){  
  21.             //cout<<"Get v"<<endl;< span="">  </endl;<>
  22.             glm::vec3 vertex;  
  23.             fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z );  
  24.             temp_vertices.push_back(vertex);  
  25.         }else if ( strcmp( lineHeader, "vt" ) == 0 ){  
  26.             //cout<<"Get vt"<<endl;< span="">  </endl;<>
  27.             glm::vec2 uv;  
  28.             fscanf(file, "%f %f\n", &uv.x, &uv.y );  
  29.             //cout<<"uv.x"<<uv.x<<" uv.y"<<uv.y;< span="">  </uv.x<<" uv.y"<<uv.y;<>
  30.             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.  
  31.             temp_uvs.push_back(uv);  
  32.         }else if ( strcmp( lineHeader, "vn" ) == 0 ){  
  33.            // cout<<"Get vn"<<endl;< span="">  </endl;<>
  34.             glm::vec3 normal;  
  35.             fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z );  
  36.             temp_normals.push_back(normal);  
  37.         }else if ( strcmp( lineHeader, "f" ) == 0 ){  
  38.             //cout<<"Get f"<<endl;< span="">  </endl;<>
  39.             std::string vertex1, vertex2, vertex3;  
  40.             unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];  
  41.             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] );  
  42.             if (matches != 9){  
  43.                 printf("File can't be read by our simple parser :-( Try exporting with other options\n");  
  44.                 return false;  
  45.             }  
  46.             vertexIndices.push_back(vertexIndex[0]);  
  47.             vertexIndices.push_back(vertexIndex[1]);  
  48.             vertexIndices.push_back(vertexIndex[2]);  
  49.             uvIndices.push_back(uvIndex[0]);  
  50.             uvIndices.push_back(uvIndex[1]);  
  51.             uvIndices.push_back(uvIndex[2]);  
  52.             normalIndices.push_back(normalIndex[0]);  
  53.             normalIndices.push_back(normalIndex[1]);  
  54.             normalIndices.push_back(normalIndex[2]);  
  55.         }else{  
  56.             // Probably a comment, eat up the rest of the line  
  57.             char stupidBuffer[1000];  
  58.             fgets(stupidBuffer, 1000, file);  
  59.         }  
  60.     }  
  61.   
  62. // For each vertex of each triangle  
  63.     for( unsigned int i=0; i
  64.         // Get the indices of its attributes  
  65.         unsigned int vertexIndex = vertexIndices[i];  
  66.         unsigned int uvIndex = uvIndices[i];  
  67.         unsigned int normalIndex = normalIndices[i];  
  68.         // Get the attributes thanks to the index  
  69.         glm::vec3 vertex = temp_vertices[ vertexIndex-1 ];  
  70.         glm::vec2 uv = temp_uvs[ uvIndex-1 ];  
  71.         glm::vec3 normal = temp_normals[ normalIndex-1 ];  
  72.         // Put the attributes in buffers  
  73.         out_vertices.push_back(vertex);  
  74.         out_uvs     .push_back(uv);  
  75.         out_normals .push_back(normal);  
  76.         }  
  77.     return true;  
  78. }  

注意在加载uv坐标的时候y轴需要转换一下。

在initGL中需要启动2D纹理,添加一句: copy

  1. glEnable(GL_TEXTURE_2D);  

同时还要添加的是纹理的加载函数,用到了SDL的image库。

首先是在codeblocks工程中把库添加进来:



然后在CGL类中添加方法: copy

  1. bool CGL::loadTexture(const char *name)  
  2. {  
  3.     int mode;  
  4.     GLuint texture;  
  5.     SDL_Surface* surface = IMG_Load(name);  
  6.     SDL_SetAlpha(surface, SDL_SRCALPHA, 0);  
  7.     if (surface==NULL) { //If it failed, say why and don't continue loading the texture  
  8.         cout<<"Error:" <<sdl_geterror()<<endl;  < span=""></sdl_geterror()<<endl;  <>
  9.         return 0;  
  10.     }  
  11.   
  12.     // work out what format to tell glTexImage2D to use...  
  13.     if (surface->format->BytesPerPixel == 3) { // RGB 24bit  
  14.             mode = GL_RGB;  
  15.         } else if (surface->format->BytesPerPixel == 4) { // RGBA 32bit  
  16.             mode = GL_RGBA;  
  17.         } else {  
  18.             SDL_FreeSurface(surface);  
  19.             return 0;  
  20.         }  
  21.     glActiveTexture(GL_TEXTURE0);  
  22.     glGenTextures(1, &texture);  
  23.     glBindTexture(GL_TEXTURE_2D, texture);  
  24.     glTexImage2D(GL_TEXTURE_2D, 0, mode, surface->w,surface->h, 0, mode,GL_UNSIGNED_BYTE,surface->pixels);  
  25.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);  
  26.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);  
  27.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT );  
  28.     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT );  
  29.   
  30.     if(surface) SDL_FreeSurface(surface);  
  31.   
  32.     prog.setUniform("Tex1", 0);  
  33.     return true;  
  34. }  

接下来是最重要的shader了,首先看顶点shader。 copy
  1. #version 400  
  2. layout (location = 0) in vec3 VertexPosition;    
  3. layout (location = 1) in vec3 VertexNormal;    
  4. layout (location = 2) in vec2 VertexTexCoord;  
  5.   
  6. out vec4 Position;  
  7. out vec3 Normal;  
  8. out vec2 TexCoord;  
  9.   
  10. uniform mat4 ModelViewMatrix;  
  11. uniform mat3 NormalMatrix;  
  12. uniform mat4 ProjectionMatrix;  
  13. uniform mat4 MVP;  
  14.   
  15.   
  16. void getEyeSpace(out vec3 norm, out vec4 position)  
  17. {  
  18.     norm =  normalize(NormalMatrix * VertexNormal);  
  19.     position = ModelViewMatrix * vec4(VertexPosition, 1.0);  
  20. }  
  21.   
  22.   
  23. void main()  
  24. {  
  25.     getEyeSpace(Normal, Position);  
  26.     TexCoord = VertexTexCoord;  
  27.     gl_Position = MVP * vec4( VertexPosition, 1.0);  
  28. }  

VertexPosition,VertexNormal.VertexTexCoord 都是作为输入的属性传进来的。

这里的layout用于一个具体变量前,用于显式标明该变量的一些布局属性,location的值和前面glBindAttribLocation的位置是一一对应的。

在顶点shader中获取VertexTexCoord之后,不经处理直接扔给Fregment shader,接下来在片段 shader中: copy

  1. #version 400  
  2.   
  3. in vec4 Position;  
  4. in vec3 Normal;  
  5. in vec2 TexCoord;  
  6.   
  7. struct LightInfo{  
  8.     vec4 position;  
  9.     vec3 intensity;  
  10. };  
  11.   
  12. struct MaterialInfo{  
  13.     vec3 Ka;  
  14.     vec3 Kd;  
  15.     vec3 Ks;  
  16.     float Shininess;  
  17. };  
  18.   
  19. uniform sampler2D Tex1;   
  20. uniform LightInfo Light;  
  21. uniform MaterialInfo Material;  
  22.   
  23. void phongModel(vec4 position, vec3 norm, out vec3 amb, out vec3 diff, out vec3 spec)  
  24. {  
  25.     vec3 tmp = vec3(0.9f, 0.5f, 0.3f);  
  26.     vec3 s = normalize(vec3(Light.position - position));  
  27.     vec3 v = normalize(-position.xyz);  
  28.     vec3 r = reflect(-s, norm);  
  29.   
  30.     amb = Light.intensity * Material.Ka;  
  31.     float sDotN = max(dot(s, norm),0.0);  
  32.     diff = Light.intensity * Material.Kd * sDotN;  
  33.     spec = vec3(0.0);  
  34.     if(sDotN > 0.0)  
  35.         spec = Light.intensity *Material.Ks * pow( max( dot(r,v), 0.0 ),Material.Shininess );    
  36. }  
  37.   
  38. void main(void)  
  39. {  
  40.     vec3 amb,diff,spec;  
  41.     vec4 texColor = texture(Tex1, TexCoord);  
  42.     phongModel(Position, Normal, amb, diff, spec);  
  43.       
  44.     //Render without light effect.  
  45.     gl_FragColor = (vec4( amb   diff, 1.0) * texColor)   vec4(spec, 1.0);  
  46.   
  47. }  


在main函数中主要是通过GLSL内置的纹理函数 - texture 来将与纹理坐标对应的纹理值从内存中取出来,接下来和光照的颜色一起混合,得到最后的颜色。

编译运行一下:




多纹理

多纹理的实现比较简单,就是将多个纹理加载到内存,然后混合得到最终纹理的颜色。

纹理的加载函数需要修改一下:

  1. bool CGL::loadTexture()  
  2. {  
  3.     int mode;  
  4.     GLuint texIDs[2];  
  5.     glGenTextures(2, texIDs);  
  6.     // Load brick brake file  
  7.     const char * texName = "assets/textures/crate.bmp";  
  8.     SDL_Surface* surface = IMG_Load(texName);  
  9.     SDL_SetAlpha(surface, SDL_SRCALPHA, 0);  
  10.     if (surface==NULL) { //If it failed, say why and don't continue loading the texture  
  11.         cout<<"Error:" <<sdl_geterror()<<endl;  < span=""></sdl_geterror()<<endl;  <>
  12.         return 0;  
  13.     }  
  14.   
  15.     // work out what format to tell glTexImage2D to use...  
  16.     if (surface->format->BytesPerPixel == 3) { // RGB 24bit  
  17.         mode = GL_RGB;  
  18.     } else if (surface->format->BytesPerPixel == 4) { // RGBA 32bit  
  19.         mode = GL_RGBA;  
  20.     } else {  
  21.         SDL_FreeSurface(surface);  
  22.         return 0;  
  23.     }  
  24.   
  25.     // Copy file to OpenGL  
  26.     glActiveTexture(GL_TEXTURE0);  
  27.     glBindTexture(GL_TEXTURE_2D, texIDs[0]);  
  28.     glTexImage2D(GL_TEXTURE_2D, 0, mode, surface->w,surface->h, 0, mode,GL_UNSIGNED_BYTE,surface->pixels);  
  29.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
  30.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
  31.   
  32.     // Load moss texture file  
  33.     texName = "assets/textures/smile.png";  
  34.     surface = IMG_Load(texName);  
  35.     SDL_SetAlpha(surface, SDL_SRCALPHA, 0);  
  36.     if (surface==NULL) { //If it failed, say why and don't continue loading the texture  
  37.         cout<<"Error:" <<sdl_geterror()<<endl;  < span=""></sdl_geterror()<<endl;  <>
  38.         return 0;  
  39.     }  
  40.   
  41.     // work out what format to tell glTexImage2D to use...  
  42.     if (surface->format->BytesPerPixel == 3) { // RGB 24bit  
  43.         mode = GL_RGB;  
  44.     } else if (surface->format->BytesPerPixel == 4) { // RGBA 32bit  
  45.         mode = GL_RGBA;  
  46.     } else {  
  47.         SDL_FreeSurface(surface);  
  48.         return 0;  
  49.     }  
  50.   
  51.     // Copy file to OpenGL  
  52.     glActiveTexture(GL_TEXTURE1);  
  53.     glBindTexture(GL_TEXTURE_2D, texIDs[1]);  
  54.     glTexImage2D(GL_TEXTURE_2D, 0, mode, surface->w,surface->h, 0, mode,GL_UNSIGNED_BYTE,surface->pixels);  
  55.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
  56.     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
  57.   
  58.     if(surface) SDL_FreeSurface(surface);  
  59.   
  60.     prog.setUniform("Tex1", 0);  
  61.     prog.setUniform("Tex2", 1);  
  62.     return true;  
  63. }  

如果是加载更多的纹理的话,可以使用纹理Buffer,那样可以获得更高的效率,这里就偷懒了 - -。

然后修改fregment shader:

  1. #version 400  
  2.   
  3. in vec4 Position;  
  4. in vec3 Normal;  
  5. in vec2 TexCoord;  
  6.   
  7. struct LightInfo{  
  8.     vec4 position;  
  9.     vec3 intensity;  
  10. };  
  11.   
  12. struct MaterialInfo{  
  13.     vec3 Ka;  
  14.     vec3 Kd;  
  15.     vec3 Ks;  
  16.     float Shininess;  
  17. };  
  18.   
  19. uniform sampler2D Tex1;   
  20. uniform sampler2D Tex2;   
  21.   
  22. uniform LightInfo Light;  
  23. uniform MaterialInfo Material;  
  24.   
  25. void phongModel(vec4 position, vec3 norm, out vec3 amb, out vec3 diff, out vec3 spec)  
  26. {  
  27.     vec3 tmp = vec3(0.9f, 0.5f, 0.3f);  
  28.     vec3 s = normalize(vec3(Light.position - position));  
  29.     vec3 v = normalize(-position.xyz);  
  30.     vec3 r = reflect(-s, norm);  
  31.   
  32.     amb = Light.intensity * Material.Ka;  
  33.     float sDotN = max(dot(s, norm),0.0);  
  34.     diff = Light.intensity * Material.Kd * sDotN;  
  35.     spec = vec3(0.0);  
  36.     if(sDotN > 0.0)  
  37.         spec = Light.intensity *Material.Ks * pow( max( dot(r,v), 0.0 ),Material.Shininess );    
  38. }  
  39.   
  40. void main(void)  
  41. {  
  42.     vec3 amb,diff,spec;  
  43.     vec4 crateColor = texture(Tex1, TexCoord);  
  44.     vec4 mosColor = texture(Tex2, TexCoord);  
  45.     phongModel(Position, Normal, amb, diff, spec);  
  46.       
  47.     vec4 texColor = mix(crateColor,mosColor,mosColor.a);  
  48.     //Render without light effect.  
  49.     gl_FragColor = (vec4( amb   diff, 1.0) * texColor)   vec4(spec, 1.0);  
  50.   
  51. }  

最终的纹理颜色是用内建的mix函数混合得到的。运行效果:


如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引