【2】openGL shader着色器分析三角形填色

news2024/12/23 18:22:42

源代码在下面。文档查询 > docs.gl

结果展示:使用自己的shader和打印错误描述
在这里插入图片描述

该篇主要在上一部分代码的基础上添加了自己写的shader,即着色器。最常用的两个着色器 vertex shader 和 fragment shader,即顶点着色器和片段着色器。

大概了解一下:

  • shader只是一段程序
  • 假如你要画一个三角形,有三个顶点,那么vertex shader就被调用了三次
  • 你要对三角形填色,也就是光栅化,那么fragment shader就被调用了很多次,成千上万次,一个像素点调用一次
  • openGL是一个状态机,就跟有很多开关一样,不是说你一改变代码就立马执行并影响到后面的代码了

添加的shader主要通过函数 CreateShader 来实现,本次主要通过查文档来提高一下分析能力。

/*方便起见,写成一个函数*/
static unsigned int CompileShader(unsigned int type, const std::string& source) {
    unsigned int id = glCreateShader(type);
    const char* src = source.c_str(); /*或者写 &source[0]*/
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        // char message[length]; /*这里会发现因为长度不定,无法栈分配,但你仍要这么做*/
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to compile " << 
            (type == GL_VERTEX_SHADER ? "vertex":"fragment" )<< "shader!请定位到此行" << std::endl;
        std::cout << message << std::endl;
        glDeleteShader(id);
        return 0;
    }

    return id;
}

/*使用static是因为不想它泄露到其他翻译单元?
使用string不是最好的选择,但是相对安全, int类型-该着色器唯一标识符,一个ID*/
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) {
    /*使用unsigned是因为它接受的参数就是这样,
    或者可以使用 GLuint,但是作者不喜欢这样,因为它要使用多个图像api*/
    unsigned int program = glCreateProgram();
    unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glValidateProgram(program);

    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

开始分析部分代码:

1.先看 CreateShader 和 CompileShader,都是 static

这里对static关键字的使用做以下解释:

  • C++函数默认是非static的,也就是可以从外部函数/文件调用。

  • 如果定义为static,则这个函数只在所在的编译单元(源文件内)可见并用,外部无法访问。

  • 在OpenGL中,通常一个文件会包含多个shader程序,我们需要一个函数返回一个特定程序的ID。

  • 如果不加static,这个函数就会被所有程序公用,可能会产生冲突。

  • 加static后,这个函数就只对本文件内的对象可见,不会对其他文件产生影响。

  • 换句话说,不加static可能会导致函数定义在其他翻译单元(文件)中重复,产生命名冲突。

  • 加static可以避免该函数暴露给其他文件,仅在本文件内起到程序创建的目的,不与其他程序产生影响。

所以总的来说,static用于将函数封装在本文件内,避免跨文件调用产生潜在 bugs 的意思。

您提出了一个好问题。我刚才解释中说:

“在OpenGL中,通常一个文件会包含多个shader程序,我们需要一个函数返回一个特定程序的ID。”

这里需要进一步解释下为什么:

  • 在OpenGL中,一个着色器程序可以包含多个shader(顶点着色器+片元着色器等)。

  • 如果一个文件定义了多个这样的程序,就需要区分返回每个程序的唯一ID。

  • 如果函数定义为非static,当多个程序同时调用此函数时:

    • 由于函数名重复定义,编译会报错。

    • 或者后定义的程序会覆盖前面程序保存的ID,导致ID错乱。

  • 使用static后,每个程序调用的函数实际上是不同函数的副本。

  • 这样每个程序得到的ID就是对应着色器程序本身的唯一标识符。

所以非static定义在这里可能会导致:

  • 编译错误
  • ID管理错误
  • 程序无法正确识别各自的着色器

而static可以避免此问题,保证函数本地性质的定义。

2.根据文档分析函数,如 glShaderSource

查看文档:https://docs.gl/gl4/glShaderSource

在这里插入图片描述
四个参数,分别是
在这里插入图片描述
函数描述:
glShaderSource sets the source code in shader to the source code in the array of strings specified by string. Any source code previously stored in the shader object is completely replaced. The number of strings in the array is specified by count. If length is NULL, each string is assumed to be null terminated. If length is a value other than NULL, it points to an array containing a string length for each of the corresponding elements of string. Each element in the length array may contain the length of the corresponding string (the null character is not counted as part of the string length) or a value less than 0 to indicate that the string is null terminated. The source code strings are not scanned or parsed at this time; they are simply copied into the specified shader object.

这个看完就比较清楚了。

第一个参数,GLuint类型,确定是哪个shader,
您提出了一个重要的问题。

OpenGL中的shader id(如glCreateShader返回的id),如果不加以管理,确实可能不是唯一的。

例如:

  • 程序运行期间创建多个shader对象,IDs可能重叠冲突。

  • 不同程序间shader ID也可能相同。

所以为了保证shader id的唯一性,需要做一些附加处理:

  1. 使用静态计数器维护shader id分配,每个id加1分配。

  2. 将id与shader对象绑定,使用对象指针作为唯一标识。

  3. 提取id生成代码到单独函数,控制唯一性。

  4. 为程序设计名称空间,每个程序独立定义id。

  5. 使用面向对象的shader类,id作为对象属性管理。

  6. 等等其它方法。

即使OpenGL ids本身不唯一,我们也可以通过程序设计的方式来管理ids,保证在程序运行过程中是唯一的。

这就需要考虑额外的id管理机制,而不是简单依赖OpenGL原生id。

第二个参数,算个数,shader的个数,shader是以string的形式存在的,vertexShader的内容就是vertexShader的源代码。

第三个参数传进来的指针就是

 std::string vertexShader =
        "#version 330 core\n"
        "\n"
        "layout(location = 0) in vec4 position;"
        "\n"
        "void main()"
        "{\n"
        "   gl_Position  = position;\n"
        "}\n";

    std::string fragmentShader =
        "#version 330 core\n"
        "\n"
        "layout(location = 0) out vec4 color;"
        "\n"
        "void main()"
        "{\n"
        "   color = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

第四个参数,给一个数组计算每个shader的长度

3.错误打印 直接看代码即可

glGetShaderInfoLog(id, length, &length, message);

openGL提供了接口。


源代码:

#inclu
de <iostream>
#include <string> 

#include <GL/glew.h> 
#include <GLFW/glfw3.h>


 

/*方便起见,写成一个函数*/
static unsigned int CompileShader(unsigned int type, const std::string& source) {
    unsigned int id = glCreateShader(type);
    const char* src = source.c_str(); /*或者写 &source[0]*/
    glShaderSource(id, 1, &src, nullptr);
    glCompileShader(id);

    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if (result == GL_FALSE) {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        // char message[length]; /*这里会发现因为长度不定,无法栈分配,但你仍要这么做*/
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id, length, &length, message);
        std::cout << "Failed to compile " << 
            (type == GL_VERTEX_SHADER ? "vertex":"fragment" )<< "shader!请定位到此行" << std::endl;
        std::cout << message << std::endl;
        glDeleteShader(id);
        return 0;
    }

    return id;
}

/*使用static是因为不想它泄露到其他翻译单元?
使用string不是最好的选择,但是相对安全, int类型-该着色器唯一标识符,一个ID*/
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) {
    /*使用unsigned是因为它接受的参数就是这样,
    或者可以使用 GLuint,但是作者不喜欢这样,因为它要使用多个图像api*/
    unsigned int program = glCreateProgram();
    unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    glValidateProgram(program);

    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

int main(void)
{
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    //if (glewInit() != GLEW_OK)/*glew文档,这里会报错,因为需要上下文,而上下文在后面*/
    //    std::cout << "ERROR!-1" << std::endl;

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    if (glewInit() != GLEW_OK)/*这里就不会报错了*/
        std::cout << "ERROR!-2" << std::endl;

    std::cout << glGetString(GL_VERSION) << std::endl;

    float positions[6] = {
        -0.5f, 0.5f,
        0.0f, 0.0f,
        0.5f, 0.5f
    };

    /*
    这段代码是创建和初始化顶点缓冲对象(Vertex Buffer Object,简称VBO)。

VBO是OpenGL中一个很重要的概念,用于高效渲染顶点数据。

它这段代码的作用是:

glGenBuffers生成一个新的VBO,ID保存到buffer变量中。

glBindBuffer将这个VBO绑定到GL_ARRAY_BUFFER目标上。

glBufferData向被绑定的这个VBO中填充实际的顶点数据。

通过这三步:

我们得到了一个可以存储顶点数据的VBO对象

后续绘制调用只需要指定这个VBO就可以加载顶点数据

教程强调VBO是因为:

相对直接送入顶点更高效

绘制调用不再需要每帧重复发送相同顶点

提高渲染性能

所以总结下VBO可以高效绘制复杂顶点数据至显卡,是OpenGL重要概念



glGenBuffers(1, &buffer);
glGenBuffers作用是生成VBO对象的ID编号。

第一个参数1表示要生成的VBO数量,这里只生成1个。

第二个参数&buffer是用于返回生成的VBO ID编号。

glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBindBuffer用于将VBO对象绑定到指定的目标上。

第一个参数GL_ARRAY_BUFFER表示要绑定的目标是顶点属性数组缓冲。

GL_ARRAY_BUFFER指定将要保存顶点属性数据如位置、颜色等。

第二个参数buffer就是前面glGenBuffers生成的VBO ID。

所以总结下:

glGenBuffers生成1个VBO对象并获取ID编号

glBindBuffer将这个VBO绑定到属性缓冲目标上,作为后续顶点数据的存储对象。




glBufferData的作用是向之前绑定的VBO对象中填充实际的顶点数据。

参数说明:

GL_ARRAY_BUFFER:指定操作目标为顶点属性缓冲(与glBindBuffer一致)

6 * sizeof(float):数据大小,这里 positions 数组有6个float数

positions:数组指针,提供实际的数据源

GL_STATIC_DRAW:数据使用模式

GL_STATIC_DRAW:数据不会或很少改变
GL_DYNAMIC_DRAW:数据可能会被修改
GL_STREAM_DRAW:数据每次绘制都会改变
它的功能是:

分配指定大小内存给当前绑定的VBO对象

将positions数组内容拷贝到VBO对象内存中

以GL_STATIC_DRAW模式,显卡知道如何优化分配内存

这样一来,positions数组中的顶点数据就上传到GPU中VBO对象里了。

OpenGL随后通过该VBO对象来读取顶点数据进行绘制。

*/
    unsigned int buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), positions, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    /*index-只有一个属性,填0
    size-两个数表示一个点,填2
    stripe-顶点之间的字节数
    pointer-偏移量




    好的,我们来用一个例子来解释glVertexAttribPointer的参数含义:

假设我们有一个VBO,里面存放3个三维顶点数据,每个顶点由(x,y,z)组成,每个元素类型为float。

那么数据在VBO中排列如下:

VBO地址 | 数据
0     |  x1
4     |  y1\
8     |  z1
12     |  x2
16     |  y2
20     |  z2
24     |  x3
28     |  y3
32     |  z3

现在我们要告诉OpenGL如何解析这些数据:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 12, 0);

- 0:属性为位置数据
- 3:每个位置由3个float组成,(x,y,z)
- GL_FLOAT:数据类型是float
- 12:当前属性到下一个属性的间隔,即一个顶点需要12个字节
- 0:这个属性起始位置就是VBO的开头

这样OpenGL就知道:

- 从VBO开始地址读取3个float作为第一个顶点的位置
- 下一个顶点偏移12字节再读取3个float

最后一个参数0就是告诉OpenGL属性的起始读取偏移是多少。





    
    好的,用一个例子来具体说明一下这种情况:

假设我们有一个VBO来存储顶点数据,每个顶点包含位置和颜色两个属性。

数据在VBO内部的排列方式为:

位置x | 位置y | 位置z | 颜色r | 颜色g | 颜色b

那么对于第一个顶点来说,它在VBO内的布局是:

VBO地址 | 数据
0     |  位置x\
4     |  位置y
8     |  位置z
12    |  颜色r
16    |  颜色g
20    |  颜色b

此时,我们设置位置属性和颜色属性的指针:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, 12);

可以看到:

- 位置属性从0字节处开始读取
- 颜色属性从12字节处开始读取(让出位置数据占用的空间)

这就是为什么位置属性的偏移不能写0,需要指定非0偏移量让出给颜色属性存储空间。

这样才能正确解析这两个分开但共处一个VBO的数据。*/
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);/* (const void)*8/

    /*这里开始使用着色器*/
    std::string vertexShader =
        "#version 330 core\n"
        "\n"
        "layout(location = 0) in vec4 position;"
        "\n"
        "void main()"
        "{\n"
        "   gl_Position  = position;\n"
        "}\n";

    std::string fragmentShader =
        "#version 330 core\n"
        "\n"
        "layout(location = 0) out vec4 color;"
        "\n"
        "void main()"
        "{\n"
        "   color = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

    unsigned int shader = CreateShader(vertexShader, fragmentShader);
    glUseProgram(shader);

    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        glDrawArrays(GL_TRIANGLES, 0, 3);
        // glDrawElements(GL_TRIANGLES, )

    /*    glBegin(GL_TRIANGLES);
        glVertex2f(-0.5f, 0.5f);
        glVertex2f(0.0f, 0.0f);
        glVertex2f(0.5f, 0.5f);
        glEnd();*/

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glDeleteProgram(shader);

    glfwTerminate();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/957805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式行业——选择比努力重要

嵌入式开发可以说是一个较大的类别&#xff0c;也可以看作是应用技术的一种广义称谓。它在不同的工业和行业场景中应用不同的业务模式和领域。 举个例子&#xff0c;嵌入式技术结合基站通信技术&#xff0c;就构成了华为基站&#xff1b;嵌入式技术结合视频处理/图像处理技术&a…

如何使用CSS实现一个响应式图片幻灯片(Responsive Image Slider)效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 响应式图片幻灯片⭐ HTML结构⭐ CSS样式⭐ JavaScript交互⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个…

快速排序(Quit Sort)

C自学精简教程 目录(必读) 快速排序 每次都把数据分成左右两部分&#xff0c;左边的小于某个数&#xff0c;右边的大于某个数。 递归操作左侧和右侧&#xff0c;最终完成所有数据的排序。 输入数据 72 6 57 88 60 42 83 73 48 85 执行过程 快速排序&#xff0c;选取位于…

【100天精通python】Day50:python web编程_web框架,Flask的使用

目录 1 Web 框架 2 python 中常用的web框架 3 Flask 框架的使用 3.1 Flask框架安装 3.2 第一个Flask程序 3.3 路由 3.3.1 基本路由 3.3.2 动态路由 3.3.3 HTTP 方法 3.3.4 多个路由绑定到一个视图函数 3.3.5 访问URL 参数的路由 3.3.6 带默认值的动态路由 3.3.7 带…

成都智慧企业发展研究院总经理郑小华:践行双轮驱动,为能源电力数智化注入新活力丨数据猿专访...

大数据产业创新服务媒体 ——聚焦数据 改变商业 随着全球经济走向数字化&#xff0c;中国正处于这一浪潮的前沿&#xff0c;进行前所未有的技术与产业深度融合。政府在2023年2月印发的《数字中国建设整体布局规划》等政策下&#xff0c;明确展示了对数字经济的支持与鼓励&…

虚拟机Ubuntu20.04 网络连接器图标开机不显示怎么办

执行以下指令&#xff1a; sudo service network-manager stop sudo rm /var/lib/NetworkManager/NetworkManager.state sudo service network-manager start

SPSS统计作图教程:频率多边形

SPSS统计作图教程&#xff1a;频率多边形 1、问题与数据 某研究者想了解某数据集中最大携氧能力&#xff08;VO2max&#xff09;是否服从正态分布&#xff0c;部分数据如图1。研究者应如何绘图查看呢&#xff1f; 图1 部分数据 2、对问题的分析 研究者想绘图展示最大携氧能…

Collections.singletonList、Arrays.asList与ImmutableList.of

文章目录 ListArrayListLinkedListArrayList与LinkedList的区别快速构建list集合Collections.singletonListArrays.asListImmutableList.of Java集合类型有三种&#xff1a;set(集)、list(列表)和map(映射)&#xff0c;而List集合是很常用的一种集合类型&#xff0c; List 我…

【算法训练-模拟 一】模拟设计LRU缓存结构

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是LRU缓存结构设计&#xff0c;这类题目出现频率还是很高的&#xff0c;几乎所有大厂都常考。 当然面对这道题&#xff0c;首先要讲清楚LRU是干什么…

使用环境中的视觉地标和扩展卡尔曼滤波器定位移动机器人研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

go学习part19(3)协程求素数

1.代码 intChan存放原始数据 primeChan存放检验之后的素数 exitChan存放几个协程的状态 package mainimport ("fmt""time" )/* 求素数 */ //放入1-8000个数 func putNum(intChan chan int) {for i : 2; i < 8000; i {intChan <- i}//关闭intChanc…

Java 设计模式实战系列—单例模式

本文首发公众号&#xff1a;小码A梦 单例模式是设计模式中最简单一个设计模式&#xff0c;该模式属于创建型模式&#xff0c;它提供了一种创建实例的最佳方式。 单例模式的定义也比较简单&#xff1a;一个类只能允许创建一个对象或者实例&#xff0c;那么这个类就是单例类&…

Unity 之ToolTip的用法

文章目录 在Unity中&#xff0c;ToolTip是一个在编辑器中使用的UI元素&#xff0c;它提供了鼠标悬停在某个对象或控件上时显示的文本信息。ToolTip通常用于向开发人员提供有关对象、字段、控件或菜单项的附加信息&#xff0c;从而帮助他们更好地理解和使用这些元素。 ToolTip通…

细节揭示:XXE漏洞复现步骤及安全防护建议

环境准备 这篇文章旨在用于网络安全学习&#xff0c;请勿进行任何非法行为&#xff0c;否则后果自负。 攻击相关介绍 介绍&#xff1a; XXE漏洞发生在那些使用XML解析器处理用户提供的XML输入的应用程序中。攻击者通过在用户输入的XML文档中插入恶意的实体引用&#xff0c;…

gin框架

【狂神说】Gin框架一小时上手 | 快速转型GoWeb开发 | Go语言零基础教程_哔哩哔哩_bilibili 1.介绍 2.简单程序 1&#xff09;gin.GET/POST/PUT/DELETE函数 Go Gin 简明教程 | 快速入门 | 极客兔兔 (geektutu.com) 我的理解是&#xff1a;这类函数就像是在监听接口一样&…

深入理解搜索引擎优化(SEO)

深入理解搜索引擎优化 深入理解搜索引擎优化(SEO)1、SEO基础入门SEO概述搜索引擎营销策略SEO查询工具与站长平台收录与权重 2、SEO站内优化关键词域名、主机、程序与SEO设计技巧及优化标签优化技巧(TDK)页面关键词的布局和密度网站内部优化与代码优化301重定向&#xff0c;404优…

文件读取漏洞复现(Metinfo 6.0.0)

安装环境 安装phpstudy&#xff0c;下载MetInfo 6.0.0版本软件&#xff0c;复制到phpstudy目录下的www目录中。 打开phpstudy&#xff0c;访问浏览器127.0.0.1/MetInfo6.0.0/install/index.php&#xff0c;打开Meinfo 6.0.0主页&#xff1a; 点击下一步、下一步&#xff0c…

stencilJs学习之构建 Drawer 组件

前言 在之前的学习中&#xff0c;我们已经掌握了 stencilJs 中的一些核心概念和基础知识&#xff0c;如装饰器 Prop、State、Event、Listen、Method、Component 以及生命周期方法。这些知识是构建复杂组件和应用的基础&#xff0c;而抽屉组件是一个很好的示例&#xff0c;能够…

温室气体数据记录软件

温室气体数据记录软件用于记录温室气体分析仪、冷阱系统、阀箱以及采样单元数据的获取及记录。其软件界面如下&#xff1a; 在软件操作几面上部是工具栏&#xff0c;可以实现软件的各种操作&#xff0c;工具栏的排布如下所示&#xff1a; 最左侧为“连接”工具&#xff0c;用…

Android 蓝牙开发( 四 )

前言 上一篇文章给大家分享了Kotlin版的Android蓝牙的基础知识和基础用法&#xff0c;不过上一篇都是一些零散碎片化的程序&#xff0c;&#xff0c;这一篇给大家分享Android蓝牙开发实战项目KotlinCompose的初步使用 效果演示 : Android Compose 蓝牙开发 Android蓝牙实战开发…