zoukankan      html  css  js  c++  java
  • LearnOpenGL学习笔记(二)纹理

    开始学习OpenGL,参考的是著名的LearnOpenGL这个网站,在这里做一些总结性的记录,只是方便自己日后查找或者记录自己的一些拓展思考,关于OpenGL的具体内容请移步:
    https://learnopengl-cn.github.io/
    或英文原版:https://learnopengl.com/

    纹理坐标

    为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(译注:采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。

    所以先在顶点数据中加入纹理坐标并记得将其传入顶点着色器:

    	float vertices[] = {
    		//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
    			 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    // 左上
    	};
    

    读取图片

    用的stb_image.h这个头文件:

    //读取图片
    int width, height, nrChannels;//图片宽度,高度,颜色通道数量
    stbi_set_flip_vertically_on_load(true);//翻转y轴
    unsigned char* data = stbi_load("wall.jpg", &width, &height, &nrChannels, 0);
    

    生成纹理对象

    类似于之前生成VAO、VBO的流程:

    	//生成纹理
    	unsigned int texture1;
    	glGenTextures(1, &texture1);//第一个参数为生成纹理的数量
    	glBindTexture(GL_TEXTURE_2D, texture1);//绑定纹理对象
    

    设置纹理

    设置纹理环绕方式和纹理过滤选项,决定了纹理坐标超过1时如何采样以及多级渐远纹理级别之间的过滤方式,具体含义和效果见LearnOpenGL。

    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的第3个和第7个参数,决定了纹理的源格式和我们希望的处理格式,如果原图带有Alpha通道,这里要改成GL_RGBA,原文中没有说明导致我一直加载不出带透明通道的图,后来看源码才发现。

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//生成纹理
    glGenerateMipmap(GL_TEXTURE_2D);//生成多级渐远纹理
    

    结束之后记得释放图像的内存:

    	stbi_image_free(data);//释放图像内存
    

    片元着色器采样

    纹理坐标传入顶点着色器后直接传到片元着色器即可,至于纹理对象本身定义为sampler2D类型的uniform变量,通过GLSL的texture函数即可进行采样。

    #version 330 core
    out vec4 FragColor;
    
    in vec3 ourColor;
    in vec2 TexCoord;
    
    uniform sampler2D ourTexture;
    
    void main()
    {
        FragColor = texture(ourTexture, TexCoord);
    }
    

    结果:

    纹理单元

    可以看到上面片元着色器中定义了uniform的纹理变量,但我们并没有在程序中给这个uniform变量传值,这是因为OpenGL中有很多纹理单元,如果只有一个纹理对象,会默认分配至GL_TEXTURE0,sampler变量也会默认从0号单元中采样,所以如果需要渲染更多的纹理,需要分配不同的纹理单元。

    更改一下片元着色器,用mix函数让两张图片进行混合:

    #version 330 core
    out vec4 FragColor;
    
    in vec3 ourColor;
    in vec2 TexCoord;
    
    uniform sampler2D texture1;
    uniform sampler2D texture2;
    
    void main()
    {
         FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.3);
    }
    

    先按之前的方式读入第二张图(这里读了一张带透明通道的图所以记得glTexImage2D的参数改为GL_RGBA):

    	unsigned int texture2;
    	glGenTextures(1, &texture2);
    	glBindTexture(GL_TEXTURE_2D, texture2);
    	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);
    	data = stbi_load("face.png", &width, &height, &nrChannels, 0);
    	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    	glGenerateMipmap(GL_TEXTURE_2D);
    	stbi_image_free(data);
    

    然后在渲染循环之前给sampler指定对应的纹理单元(注意一定要先激活shader程序):

    	shader.use();
    	glUniform1i(glGetUniformLocation(shader.ID, "texture1"), 0);
    	glUniform1i(glGetUniformLocation(shader.ID, "texture2"), 1);
    

    然后激活对应的纹理单元并将纹理对象绑定上去:

    	glActiveTexture(GL_TEXTURE0);
    	glBindTexture(GL_TEXTURE_2D, texture1);
    	glActiveTexture(GL_TEXTURE1);
    	glBindTexture(GL_TEXTURE_2D, texture2);
    

    结果:

    关于像素对齐的一个小坑

    在LearnOpenGL这一章的评论区发现有人说,当读取的图片宽度为奇数时显示不正常,而我试了一下宽度为奇数的图片程序直接就崩溃了,评论区也给出了解决方法,加入一行代码之后就正常了:

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    

    这行代码的作用是设置OpenGL读取数据的对齐方式,OpenGL默认为4字节对齐,也就是说一行图像数据的长度必须是4的倍数,我又尝试了一下宽度为偶数但不是4的倍数的图片,果然显示不正确:

    所以上面的代码就是设置读取数据的方式为1个字节对齐,这样OpenGL会一个一个字节读取,不会导致越界,但这样读取效率肯定也会降低,所以最好的方法还是提供宽度为4的倍数的图片。

  • 相关阅读:
    std thread
    windows更新包发布地址
    How to set up logging level for Spark application in IntelliJ IDEA?
    spark 错误 How to set heap size in spark within the Eclipse environment?
    hadoop 常用命令
    windows 安装hadoop 3.2.1
    windows JAVA_HOME 路径有空格,执行软连接
    day01MyBatisPlus条件构造器(04)
    day01MyBatisPlus的CRUD 接口(03)
    day01MyBatisPlus入门(02)
  • 原文地址:https://www.cnblogs.com/LiveForGame/p/11827378.html
Copyright © 2011-2022 走看看