简介
纹理是用来丰富我们绘制物体细节的,它可以是一张2D图片(除了图像外,纹理也被用来存储大量数据,传递到着色器上),就像贴图一样贴在绘制的物体上.
纹理属性
纹理坐标
为了将纹理映射到绘制的物体上,我们需要指定 某个顶点对应着 纹理的那个位置. 通过纹理坐标,标明顶点从纹理图像那一部分采样,之后在图形的其它片段进行片段插值.
纹理坐标系和顶点坐标系有所不同,顶点坐标系 (0,0)点位于窗口中心. 纹理坐标系 (0,0)点位于 纹理左下角.
顶点坐标系
纹理坐标系
纹理环绕方式
当我们将顶点位置设置到纹理坐标之外时,则需要设置纹理环绕方式 来显示纹理图案
环绕属性 |
效果 |
GL_REPEAT |
重复纹理图案(默认) |
GL_MIRRORED_REPEAT |
镜像重复纹理图案 |
GL_CLAMP_TO_EDGE |
将纹理锁定在0~1之间,超出部分重复纹理边缘图案,产生拉伸效果 |
GL_CLAMP_TO_BORDER |
超出部分为用户指定边缘颜色 |
纹理环绕函数
glTexParameteri (GLenum target, GLenum pname, GLint param);
参数:
target: 指定纹理目标,若为2D纹理 则为 GL_TEXTURE_2D
pname: 对应的纹理坐标轴(这里 s,t,r 对应 x,y,z) GL_TEXTURE_WRAP_S ,GL_TEXTURE_WRAP_T
param: 环绕方式,填入上面的方式.
对2D纹理时,必须对 s,t坐标轴都进行设置. 若是设置 GL_CLAMP_TO_BORDER 形式,则还需要额外设置 环绕颜色
1 2
| float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
|
纹理过滤
纹理坐标是不依赖于 纹理大小和分辨率的, 1就表示纹理的边缘.但是物体和和纹理大小可能不一致,这就造成了对纹理的放大和拉伸.这时如何将纹理像素映射到纹理坐标上,就需要我们设置. 该属性就是 纹理过滤.
纹理过滤有很多种,下面是最重要的两种
邻近过滤
GL_NEAREST 是默认的纹理过滤方式,它会选择距离 纹理坐标最近的像素点作为样本颜色.当纹理被放大时,会有颗粒感
线性过滤
GL_LINEAR, 会基于当前纹理坐标附近的像素点计算一个插值,也就是附近纹理的混合色,离得越近的像素点,颜色贡献越大,当纹理被放大时,会比临近过滤更平滑.
过滤函数
我们需要对放大(Magnify)和缩小(Minify)的情况设置过滤效果
1 2
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
多级渐远纹理
在3D世界,根据物体的远近不同,物体也存在缩放的效果,要显示不同的分辨率,若都使用相同的分辨率,一是会使物体产生不真实的效果,二是会造成内存的浪费. 在研究3D纹理时再说.~~
纹理代码
首先是设置 上下文,着色器,程序对象…的方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| context1 = [[EAGLContext alloc] initWithAPI:(kEAGLRenderingAPIOpenGLES3)]; BOOL isSetCOntextRight = [EAGLContext setCurrentContext:context1]; if (!isSetCOntextRight) { printf("设置Context失败"); }
NSString* verStr = [[NSBundle mainBundle] pathForResource:@"Texture2D_Vert.glsl" ofType:nil]; NSString* fragStr = [[NSBundle mainBundle]pathForResource:@"Texture2D_Frag.glsl" ofType:nil];
program1 = createGLProgramFromFile(verStr.UTF8String, fragStr.UTF8String); glUseProgram(program1);
//创建,绑定渲染缓存 并分配空间 glGenRenderbuffers(1, &renderBuf1); glBindRenderbuffer(GL_RENDERBUFFER, renderBuf1); // 为 color renderbuffer 分配存储空间 [context1 renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
//创建,绑定帧缓存 并分配空间 glGenFramebuffers(1, &frameBuf1); // 设置为当前 framebuffer glBindFramebuffer(GL_FRAMEBUFFER, frameBuf1); // 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuf1);
glClearColor(1.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, self.frame.size.width, self.frame.size.height);
|
然后是 绘制物体的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| float verData[] = { // 位置 颜色 纹理坐标 0.5f,0.5f,0.0f, 1.0f,0.0f,0.0f, 1.0f,1.0f, 0.5f,-0.5f,0.0f, 0.0f,1.0f,0.0f, 1.0f,0.0f, -0.5f,-0.5f,0.0f, 0.0f,0.0f,1.0f, 0.0f,0.0f, -0.5f,0.5f,0.0f, 1.0f,1.0f,0.0f, 0.0f,1.0f, }; unsigned int indices[] = { 0,1,3, 1,2,3 };
glGenVertexArrays(1, &VAO1); glGenBuffers(1, &VBO1); glGenBuffers(1, &EBO1);
glBindVertexArray(VAO1); glBindBuffer(GL_ARRAY_BUFFER, VBO1); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);
glBufferData(GL_ARRAY_BUFFER, sizeof(verData), verData, GL_STATIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(3*sizeof(float))); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(6*sizeof(float)));
glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2);
|
纹理设置~~
stbi_image 一个非常流行的单头文件图像加载库 stbi_image
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| //因为使用 stbi 函数导入的图片会颠倒,所以需要将其摆正 stbi_set_flip_vertically_on_load(true);
NSString* imPath = [[NSBundle mainBundle] pathForResource:@"wall.jpg" ofType:nil]; int width,height,nrChannels;
//加载图片 unsigned char * imdata = stbi_load(imPath.UTF8String, &width, &height, &nrChannels, 0);
//创建 纹理 unsigned int texture; glGenTextures(1, &texture);
//激活纹理单元0 glActiveTexture(GL_TEXTURE0); //绑定纹理 glBindTexture(GL_TEXTURE_2D, texture); //将图像传入纹理 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imdata);
//glGenerateMipmap(GL_TEXTURE_2D);
//设置纹理环绕和纹理过滤 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//将不用的图像释放 stbi_image_free(imdata);
//将纹理作为统一变量传入显存 glUniform1i(glGetUniformLocation(program1, "outTexture"), 0);
//这里PNG格式 通过stbi_load 拉入时 会导致产生 BGRA格式(我也不大清楚原因,懂的朋友 告告我 先O(∩_∩)O谢谢了) ,这时 图片作为纹理显示时色彩会出错.所以将其转换一蛤~ NSString* imPath1 = [[NSBundle mainBundle] pathForResource:@"face.png" ofType:nil]; int width1,height1,nrChannels1; unsigned char * imdata1 = stbi_load(imPath1.UTF8String, &width1, &height1, &nrChannels1, STBI_rgb_alpha); for (int i = 0; i<width1*height1; i++ ) { char tR = imdata1[i*4+2]; imdata1[i*4+2] = imdata1[i*4]; imdata1[i*4] = tR; }
unsigned int texture1;
glGenTextures(1, &texture1); glActiveTexture(GL_TEXTURE1); //必须先写这个再绑定 glBindTexture(GL_TEXTURE_2D, texture1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width1, height1, 0, GL_RGBA, GL_UNSIGNED_BYTE, imdata1); glGenerateMipmap(GL_TEXTURE_2D); stbi_image_free(imdata1);
glUniform1i(glGetUniformLocation(program1, "outTexture1"), 1);
//绘制显示 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); [context1 presentRenderbuffer:GL_RENDERBUFFER];
|
片段着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #version 300 es
precision mediump float;
in vec2 outTexCoord;
uniform sampler2D outTexture; uniform sampler2D outTexture1;
in vec3 outColor;
out vec4 FragColor;
void main() { FragColor = mix(texture(outTexture,outTexCoord),texture(outTexture1,outTexCoord),0.2); }
|
GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。
函数补充说明
glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
target 纹理目标 设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理
level 为纹理指定多级渐远纹理的级别,0表示基本级别
internalformat 希望将纹理存储为何等格式
width height 图像宽高
border 设置为0 说是历史遗留问题
format 源图格式 type 数据格式
pixels 图像数据
纹理单元
在代码中 使用 glUniform1i
方法进行纹理传递 是因为 纹理单元 这个概念.
使用glUniform1i
可以为纹理采样器分配一个位置值,通过把纹理单元赋值给采样器,就可以一次绑定多个纹理.
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。
需要注意的是 在绑定纹理时 先要激活纹理单元才可以
显示结果