OpenGL学习(4)——纹理
拖了半个多月的博客,这次学习如何使用纹理(Texture)贴图来实现更多的细节。
生成纹理对象
和创建VAO、VBO方法类似,调用glGenTextures函数。
glGenTextures(1, &texture);
绑定并配置纹理对象
glBindTexture(GL_TEXTURE_2D, texture);
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);
调用四次glTexParameter函数:
- 2D纹理坐标的横坐标和纵坐标范围都在[0, 1],当设置的横坐标超出这个范围,则以GL_REPEAT的形式显示纹理;
- 同理,当设置的纵坐标范围超出[0, 1]范围,同样以GL_REPEAT的形式显示纹理。不同选项下纹理的显示效果如下:
- 当纹理被缩小时,一个屏幕像素对应多个纹理像素,采用GL_LINEAR即线性过滤进行采样;
- 当纹理被放大时,一个纹理像素对应多个屏幕像素,同样采用线性过滤进行采样。可选择的过滤方式还有GL_NEAREST,GL_NEAREST_MIPMAP_NEAREST,GL_LINEAR_MIPMAP_NEAREST,GL_NEAREST_MIPMAP_LINEAR和GL_LINEAR_MIPMAP_LINEAR,需要注意的是,后四种过滤方式只能使用在纹理被缩小的情况下。
加载纹理图像
首先添加库stb_image.h。
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
调用库函数stbi_load加载图片,五个参数分别设置图片的路径,输出图片的宽度,高度,默认颜色通道数和自定义颜色通道数。这里设置自定义颜色通道数为0,表示输出图片默认的颜色通道数。
unsigned char *imageData = stbi_load("/path1/xxx.jpg", &width, &height, &nrChannel, 0);
若图片加载成功,调用glTexImage2D函数使用被加载的图片参数生成2D纹理图像。注意第三和第七个参数虽然都设置成GL_RGB,但含义不同。第三个参数设置OpenGL存储纹理数据的格式,而第七个参数表示图片被加载时生成的数据格式。
之后调用glGenerateMipmap函数根据纹理图像生成Mipmap。
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
glGenerateMipmap(GL_TEXTURE_2D);
最后释放内存并解绑纹理对象。
stbi_image_free(imageData);
glBindTexture(GL_TEXTURE_2D, 0);
配置顶点属性指针
在顶点数据中添加纹理坐标,四个顶点的纹理坐标分别为(2.0f, 2.0f),(0.0f, 2.0f),(2.0f, 0.0f)和(0.0f, 0.0f):
float vertices[] = {0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 2.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f};
配置顶点属性指针的函数调用和参数说明参见《绘制三角形》:
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(6*sizeof(float))); //配置纹理坐标
glEnableVertexAttribArray(2); //使能属性位置2
编写Shader
首先是Vertex Shader,Tex的属性位置为2,表示纹理坐标:
#version 330 core
layout (location = 0) in vec3 Pos;
layout (location = 1) in vec3 Col;
layout (location = 2) in vec2 Tex;
out vec4 Color;
out vec2 texCoord;
void main()
{
gl_Position = vec4(Pos, 1.0f);
Color = vec4(Col, 1.0f);
texCoord = Tex;
}
添加sampler2D类型的变量,使Fragment Shader能够访问之前创建的纹理对象:
#version 330 core
out vec4 FragColor;
in vec4 Color;
in vec2 texCoord;
uniform sampler2D texSampler;
void main()
{
FragColor = texture(texSampler, texCoord) * Color;
}
渲染循环
ourShader.use();
glBindVertexArray(VAO);
glBindTexture(GL_TEXTURE_2D, texture);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
Texture Unit
那么该如何同时生成并使用两个纹理呢?
在编写Fragment Shader时,定义了uniform sampler2D类型的变量,可以调用glGetUniformLocation函数和glUniform函数对它进行赋值,来设置纹理的位置信息,即Texture Unit。通过glActiveTexture函数激活不同的Texture Unit,并绑定不同的纹理对象,在Fragment Shader中就可以同时对不同的纹理图像进行采样,混合并输出颜色。
首先绑定并配置另一个纹理对象:
glBindTexture(GL_TEXTURE_2D, texture[1]);
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_load加载另一张图片,根据图片参数生成纹理图像和Mipmap:
imageData = stbi_load("/path2/xxx.png", &width, &height, &nrChannel, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); //因为我这里加载的图片有alpha通道,所以第七个参数设置成GL_RGBA
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(imageData);
glBindTexture(GL_TEXTURE_2D, 0);
修改Fragment Shader,定义两个sampler2D变量,并调用mix函数进行颜色混合,即第一个纹理采样颜色的80%和第二个纹理采样颜色的20%:
#version 330 core
out vec4 FragColor;
in vec4 Color;
in vec2 texCoord;
uniform sampler2D texSampler1;
uniform sampler2D texSampler2;
void main()
{
FragColor = mix(texture(texSampler1, texCoord)*Color, texture(texSampler2, texCoord), 0.2);
}
在main函数中对Fragment Shader中的uniform进行赋值,分别设置位置信息即Texture Unit为0和1:
ourShader.use();
glUniform1i(glGetUniformLocation(ourShader.ID, "texSampler1"), 0);
glUniform1i(glGetUniformLocation(ourShader.ID, "texSampler2"), 1);
在渲染循环中激活Texture Unit,并绑定对应的纹理对象:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture[1]);
执行结果:
完整代码如下:
#include "shader.h"
#include <GLFW/glfw3.h>
#include <cmath>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
using namespace std;
/*
void frambuffer_size_callback(GLFWwindow *window, int width, int height)
{
glViewport(0, 0, width, height);
}
*/
void processInput(GLFWwindow* window)
{
//check if ESCAPE is pressed
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
int main()
{
//initialize GLFW
if(!glfwInit())
return -1;
//configure GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
//creat a window object
const unsigned int window_width = 800;
const unsigned int window_height = 600;
GLFWwindow *window = glfwCreateWindow(window_width, window_height, "OpenGL_Demo", NULL, NULL);
if (window == NULL){
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
//initialize GLAD to manage function pointers for OpenGL
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
cout << "Failed to initialize GLAD" << endl;
return -1;
}
//set width and height of Viewport
glViewport(0, 0, window_width, window_height);
Shader ourShader;
ourShader.shader("/home/yi/Test/GL_test/vShader.vs", "/home/yi/Test/GL_test/fShader.fs");
//glfwSetFramebufferSizeCallback(window, frambuffer_size_callback);
//coordnate (x,y,z) of vertices
float vertices[] = {0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 2.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f};
//indices of vertices
unsigned int indices[] = {0, 1, 2, 1, 2, 3};
//vertex buffer object(VBO)
unsigned int VBO;
//element buffuer object(EBO)
unsigned int EBO;
//vertex array object(VAO)
unsigned int VAO;
//texture object
unsigned int texture[2];
//generate VAO, VBO, EBO, texture
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glGenTextures(2, texture);
//bind and configure VAO, VBO and EBO
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//configure texture1
glBindTexture(GL_TEXTURE_2D, texture[0]);
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_set_flip_vertically_on_load(true);
int width, height, nrChannel;
unsigned char *imageData = stbi_load("/path1/xxx.jpg", &width, &height, &nrChannel, 0);
if(imageData){
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
glGenerateMipmap(GL_TEXTURE_2D);
cout << "Successful to load texture1" << endl;
}
else{
cout << "Failed to load texture1" << endl;
return -1;
}
stbi_image_free(imageData);
glBindTexture(GL_TEXTURE_2D, 0);
//configure texture2
glBindTexture(GL_TEXTURE_2D, texture[1]);
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);
imageData = stbi_load("/path2/xxx.png", &width, &height, &nrChannel, 0);
if(imageData){
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
glGenerateMipmap(GL_TEXTURE_2D);
cout << "Successful to load texture2" << endl;
}
else{
cout << "Failed to load texture2" << endl;
return -1;
}
stbi_image_free(imageData);
glBindTexture(GL_TEXTURE_2D, 0);
//set which texture unit each sampler in fragment shader belongs to
ourShader.use();
glUniform1i(glGetUniformLocation(ourShader.ID, "texSampler1"), 0);
glUniform1i(glGetUniformLocation(ourShader.ID, "texSampler2"), 1);
//link vertex attributes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(6*sizeof(float)));
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//render loop
while(!glfwWindowShouldClose(window)){
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ourShader.use();
glBindVertexArray(VAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture[1]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window);
glfwPollEvents();
}
//clear resource
glfwTerminate();
return 0;
}
OpenGL学习(4)——纹理的更多相关文章
- OpenGL学习笔记(四)纹理
目录 要完成的纹理效果 纹理环绕方式 纹理过滤 多级渐远纹理 加载与创建纹理 stb_image库的使用方法 生成纹理对象 应用纹理 纹理单元 参考资料:OpenGL中文翻译 要完成的纹理效果 纹理是 ...
- OpenGL学习进程(12)第九课:矩阵乘法实现3D变换
本节是OpenGL学习的第九个课时,下面将详细介绍OpenGL的多种3D变换和如何操作矩阵堆栈. (1)3D变换: OpenGL中绘制3D世界的空间变换包括:模型变换.视图变换.投影变换和视口 ...
- OpenGL学习之路(一)
1 引子 虽然是计算机科班出身,但从小对几何方面的东西就不太感冒,空间想象能力也较差,所以从本科到研究生,基本没接触过<计算机图形学>.为什么说基本没学过呢?因为好奇(尤其是惊叹于三维游戏 ...
- OpenGL学习之路(三)
1 引子 这些天公司一次次的软件发布节点忙的博主不可开交,另外还有其它的一些事也占用了很多时间.现在坐在电脑前,在很安静的环境下,与大家分享自己的OpenGL学习笔记和理解心得,感到格外舒服.这让我回 ...
- opengl学习笔记
准备: 1.准备资源:从GLEW1.13.0下载GLEW,并且解压出glew-1.13.0目录.从FreeGLUT官网下载3.0.0版本.直接从这里下的编译后的FreeGLUT,选for MSVC,下 ...
- OpenGL学习进程(7)第五课:点、边和图形(二)边
本节是OpenGL学习的第五个课时,下面介绍OpenGL边的相关知识: (1)边的概念: 数学上的直线没有宽度,但OpenGL的直线则是有宽度的.同时,OpenGL的直线必须是有限长度,而不是像数学概 ...
- OpenGL学习进程(5)第三课:视口与裁剪区域
本节是OpenGL学习的第三个课时,下面介绍如何运用显示窗体的视口和裁剪区域: (1)知识点引入: 1)问题现象: 当在窗体中绘制图形后,拉伸窗体图形形状会发生变化: #include ...
- OpenGL学习进程(4)第二课:绘制图形
本节是OpenGL学习的第二个课时,下面介绍如何用点和线来绘制图形: (1)用点的坐标来绘制矩形: #include <GL/glut.h> void display(void) ...
- OpenGL学习笔记3——缓冲区对象
在GL中特别提出了缓冲区对象这一概念,是针对提高绘图效率的一个手段.由于GL的架构是基于客户——服务器模型建立的,因此默认所有的绘图数据均是存储在本地客户端,通过GL内核渲染处理以后再将数据发往GPU ...
- OpenGL学习进程(11)第八课:颜色绘制的详解
本节是OpenGL学习的第八个课时,下面将详细介绍OpenGL的颜色模式,颜色混合以及抗锯齿. (1)颜色模式: OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式. R ...
随机推荐
- linux实操_shell读取控制台输入
基本语法: read [选项] [参数] 选项: -p 指定读取值时的提示符 -t 指定读取值时等待的时间(秒),如果没有在指定的时间内输入,就不再等待了 参数: 变量:指定读取值的变量名 实例1:读 ...
- vue1 计算属性
- java判断文件是否为图片
/** * 判断文件是否为图片<br> * <br> * @param pInput 文件名<br> * @param pImgeFlag 判断具体文件类型< ...
- 201812-4 数据中心(kruskal)
考场上的时候被题目完全蒙住了,当时状态也不好,前几次考试每次考试当天就头晕感冒流鼻涕 好的,以上都是借口,自己没有好好复习才是真的... 题目: 好的,以上题目简述就是:给你一个无向连通图,求它的最小 ...
- IntelliJ IDEA必装插件以及SpringBoot使用小技巧合集
IntelliJ IDEA必装插件 有不知道怎么安装的吗?File-->settings打开设置面板,找到plugins,输入想要安装的插件回车即可 1.背景图片 目前,IDEA支持设置背景图片 ...
- JQuery 实践--让页面动起来
获取和设置元素特性特性属性:是指DOM元素中能够和HTML元素中某个特性对应得上的属性.通常JS特性属性的名称与对应的特性一一匹配,但class <=>className操作特性还是操作属 ...
- C++指针图解
- sql server 复习笔记2
主键约束 可以通过定义primary key 约束来定义主键, 用于强制表的实体化完整性,一个表只能有一个主键约束, 并且primary key 约束中的列不能为空值,由于primary key 约束 ...
- ipv4保留地址
曾经以为保留地址下面三类.原来使用中还有很多的. A类 10.0.0.0--10.255.255.255 B类 172.16.0.0--172.31.255.255 C类 192.168.0.0--1 ...
- 2019.6.28 校内测试 T1 Jelly的难题1
这题面有点难理解,建议直接跳到题意解释那一部分(虽然我觉得解释的不大对,但按照解释来做确实能AC): 按照“题意解释”的思路来思考这个题,那么就十分的简单了: 1.首先要读入这个字符矩阵,可以用cin ...