提要

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

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

UV贴图

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

       一般的3D模型制作软件都可以制作UV贴图,推荐使用Blender + GIMP,制作的方法可以参考这里。

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

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

bool Util::loadObjWithTex(const char * path,std::vector
& out_vertices,std::vector
& out_uvs,std::vector
& out_normals) { printf("Loading OBJ file with tex %s...\n", path); std::vector
vertexIndices, uvIndices, normalIndices; std::vector
temp_vertices; std::vector
temp_uvs; std::vector
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"<
注意在加载uv坐标的时候y轴需要转换一下。

在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:" <
<
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; }
接下来是最重要的shader了,首先看顶点shader。

#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); }
VertexPosition,VertexNormal.VertexTexCoord 都是作为输入的属性传进来的。

这里的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:" <
<
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:" <
<
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; }
如果是加载更多的纹理的话,可以使用纹理Buffer,那样可以获得更高的效率,这里就偷懒了 - -。

然后修改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);  }
最终的纹理颜色是用内建的mix函数混合得到的。运行效果:

参考

OpenGL 4.0 Shading Langage Cookbook