知者何南 发表于 前天 00:34

OpenGL Chan视频学习-9 Index Buffers inOpenGL

bilibili视频链接:
【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p=5&vd_source=44b77bde056381262ee55e448b9b1973

函数网站:
docs.gl

阐明:
1.之后就不再单独整理网站具体函数了,网站直接翻译会更直观也会有更多注意点。直接通过csdn索引查找反而会慢。
2.代码地区会单独注释功能参数返回值和相干注意事项。
3.课程学习从4-本节,如果有些函数没有注释可以看专栏内里的前面发表的文章,一样平常有表明。
4.如果觉得代码注释白色字体不太直观可以直接copy到相应软件看。
5.有两种版本的可供检察:注释全面的和注释简洁版的,可以在索引内里找到相干代码检察。
6.希望能帮到你。
7.有错误请跟我阐明一下,大概整理的时候没有检查好。


一、知识点整理

1.1 画方形

1.1.1方法一

1.1.1.1代码

//准备数据
float position[] = {
    0.5f, 0.5f,
    -0.5f, -0.5f,
    0.5f, -0.5f,

    -0.5f,0.5f,
    -0.5f,-0.5f,
    0.5f,-0.5f

};

//定义缓冲区对象
unsigned int buffer;
//功能:生成缓冲区对象,并将数据写入缓冲区
glGenBuffers(1, &buffer);
//功能:将缓冲区对象绑定到目标
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//功能:将数据写入缓冲区
glBufferData(GL_ARRAY_BUFFER, 2 * 6 * sizeof(float), position, GL_STATIC_DRAW);

//功能:配置顶点属性指针
glEnableVertexAttribArray(0);
//功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0); 1.1.1.2运行效果

https://i-blog.csdnimg.cn/direct/3213dbb5b5544ebfa436b779258eb44a.png
1.1.1.3表明

在float数组内里直接添加组成绘制方形的两个三角形所需的六个顶点。
1.1.1.4缺点

顶点冗余
1.1.2方法二——索引缓冲

1.1.1.1代码

//准备数据
float position[] = {
    -0.5f, -0.5f,
    0.5f, -0.5f,
    0.5f,0.5f,
    -0.5f,0.5f,
};

//定义顶点索引缓存,用于标定顶点顺序
unsigned int indices[] = {
    0,1,2,
    2,3,0
};


//定义缓冲区对象
unsigned int buffer;
//功能:生成缓冲区对象,并将数据写入缓冲区
glGenBuffers(1, &buffer);
//功能:将缓冲区对象绑定到目标
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//功能:将数据写入缓冲区
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);

//功能:配置顶点属性指针
glEnableVertexAttribArray(0);
//功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);



//索引缓冲区对象
unsigned int ibo;
//功能:生成缓冲区对象,并将数据写入缓冲区
glGenBuffers(1, &ibo);
//功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区
//参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象
//2.ibo:索引缓冲区对象ID
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
//功能:将数据写入缓冲区
//参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象
//2.6*sizeof(unsigned int):索引数据大小
//3.indices:索引数据指针
//4.GL_STATIC_DRAW:指定数据不会改变
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW); while绘制时改动
//glDrawArrays(GL_TRIANGLES, 0, 6);
//功能:绘制三角形
//参数:1.GL_TRIANGLES:绘制三角形
//2.6:顶点数量
//3.GL_UNSIGNED_INT:索引数据类型
//4.nullptr:索引数据指针
//因为索引缓冲区已经绑定到GL_ELEMENT_ARRAY_BUFFER目标,所以这里不需要再传入索引数据指针
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
1.1.1.2运行效果

https://i-blog.csdnimg.cn/direct/11b99b7a429c44fe8eed1f8f5fe8eec2.png
如果不消unsigned,如:
glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr); 会绘制错误
https://i-blog.csdnimg.cn/direct/4a897ed99679454f8f3e52b9e7dbfeac.png
1.1.1.3表明

使用索引缓冲。避免方法一的重复撰写,减少内存占用(在实际应用中顶点还包罗了颜色,法线等信息)。顶点数从6->4。
https://i-blog.csdnimg.cn/direct/52169f73d0404dc1bd33fa8ceedfd11f.png
   QS:position的坐标点怎么和indices联系起来的
1. 顶点数据 (position 数组)
float position[] = {
    -0.5f, -0.5f,// 顶点 0
    0.5f, -0.5f,   // 顶点 1
    0.5f, 0.5f,    // 顶点 2
    -0.5f, 0.5f    // 顶点 3
};


[*]position数组存储了四个顶点的坐标。
[*]每个顶点由两个浮点数表示,分别是x和y坐标。
[*]顶点的顺序是:左下、右下、右上、左上。
2. 索引数据 (indices 数组)
unsigned int indices[] = {
    0, 1, 2,// 三角形 1: 左下 -> 右下 -> 右上
    2, 3, 0   // 三角形 2: 右上 -> 左上 -> 左下
};


[*]indices数组存储了绘制两个三角形所需的顶点索引。
[*]索引值对应position数组中顶点的顺序。
[*]比方:

[*]第一个三角形的顶点索引是 0, 1, 2,这意味着使用 position 数组中的第0个、第1个和第2个顶点来绘制一个三角形。
[*]第二个三角形的顶点索引是 2, 3, 0,这意味着使用 position 数组中的第2个、第3个和第0个顶点来绘制另一个三角形。

3. 顶点缓冲区对象 (buffer)
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);


[*]天生一个顶点缓冲区对象(VBO),用于存储顶点数据。
[*]将顶点数据绑定到GL_ARRAY_BUFFER,这是一个用于存储顶点属性(如位置、颜色等)的缓冲区目标。
[*]使用glBufferData将顶点数据传输到GPU的缓冲区中。
4. 索引缓冲区对象 (ibo)
unsigned int ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);


[*]天生一个索引缓冲区对象(IBO),用于存储索引数据。
[*]将索引数据绑定到GL_ELEMENT_ARRAY_BUFFER,这是一个用于存储顶点索引的缓冲区目标。
[*]使用glBufferData将索引数据传输到GPU的缓冲区中。
如何联系起来


[*]顶点缓冲区对象 (buffer) 存储了顶点的位置数据。
[*]索引缓冲区对象 (ibo) 存储了顶点的索引数据,这些索引数据告诉OpenGL如何使用顶点缓冲区中的顶点来绘制多少图形。
在main函数中,使用glDrawElements函数来绘制多少图形:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);


[*]GL_TRIANGLES:指定绘制的图形范例为三角形。
[*]6:指定索引的数量(即indices数组的长度)。
[*]GL_UNSIGNED_INT:指定索引数据的范例为无符号整数。
[*]nullptr:指定索引数据在缓冲区中的偏移量为0。因为索引缓冲区已经绑定到GL_ELEMENT_ARRAY_BUFFER,OpenGL会主动从该缓冲区中读取索引数据。
详细联系过程

[*] 顶点数据绑定:

[*]position数组中存储了四个顶点的坐标。
[*]使用glVertexAttribPointer配置顶点属性指针: glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);

[*]这里,0表示顶点属性的索引,2表示每个顶点有两个分量(x和y坐标),GL_FLOAT表示数据范例为浮点数。

[*] 索引数据绑定:

[*]indices数组中存储了绘制两个三角形所需的顶点索引。
[*]使用glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)将索引数据绑定到GL_ELEMENT_ARRAY_BUFFER。

[*] 绘制过程:

[*]当调用glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr)时,OpenGL会按照indices数组中的索引顺序从position数组中获取顶点数据。
[*]比方:

[*]第一个三角形的索引是 0, 1, 2,OpenGL会获取position数组中的第0个、第1个和第2个顶点来绘制第一个三角形。
[*]第二个三角形的索引是 2, 3, 0,OpenGL会获取position数组中的第2个、第3个和第0个顶点来绘制第二个三角形。


总结


[*]顶点缓冲区对象 (buffer) 和 索引缓冲区对象 (ibo) 分别存储顶点数据和索引数据。
[*]glDrawElements 函数使用索引数据来决定如何从顶点缓冲区中提取顶点数据来绘制多少图形。
[*]这种方法可以减少顶点数据的冗余存储,进步内存效率。
二、完整代码

2.1完全注释代码

#include <GL/glew.h>#include <GLFW/glfw3.h>#include<iostream>#include<fstream>#include<string>#include<sstream>//功能:定义ShaderProgramSource布局体,用于存储着色器代码struct ShaderProgramSource{    std::string VertexSource;    std::string FragmentSource;};//功能:分析着色器代码文件。static ShaderProgramSource ParseShader(const std::string& filepath){    //功能:打开文件流    std::ifstream stream(filepath);    //定义着色器范例    enumclass ShaderType    {      NONE=-1,VERTEX=0,FRAGMENT=1    };    //该变量用于存储着色器代码    std::string line;    //该变量用于存储着色器范例    std::stringstream ss;    //该变量是当前着色器范例    ShaderType type = ShaderType::NONE;    //功能:读取文件中的每一行内容,直到文件竣事    while (getline(stream, line))    {      //如果当前行包罗#shader,则阐明接下来是着色器代码      if (line.find("#shader") != std::string::npos)      {            //如果当前行包罗vertex,则阐明接下来是顶点着色器代码            if (line.find("vertex") != std::string::npos)            {                type = ShaderType::VERTEX;            }            else if (line.find("fragment") != std::string::npos)            {                type = ShaderType::FRAGMENT;            }      }      else      {            //否则,将当前行添加到对应着色器代码的stringstream中            ss[(int)type] << line << '\n';      }    }    //返回ShaderProgramSource布局体    return { ss.str(), ss.str() };}//功能:编译着色器代码static unsigned int CompilesShader(unsigned int type, const std::string& source){    //功能:创建着色器对象    unsigned int id = glCreateShader(type);    //功能:设置着色器源代码.    const char* src = source.c_str();    //功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串    glShaderSource(id, 1, &src, nullptr);    //功能:编译着色器对象的源代码    glCompileShader(id);    //设置返回着色器的对象ID    int result;    //功能:从着色器对象返回一个参数,表示编译是否成功。    glGetShaderiv(id, GL_COMPILE_STATUS, &result);    //如果编译失败,则输出错误信息    if (result == GL_FALSE)    {      int length;      //功能:获取编译错误信息的长度      glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);      //分配内存,用于存储编译错误信息      char* message = (char*)alloca(length*sizeof(char));      //功能:获取编译错误信息      glGetShaderInfoLog(id, length, &length, message);      std::cout << "Failed to compile shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;      std::cout << message << std::endl;      //删除着色器对象      glDeleteShader(id);      return 0;    }    //TODO:错误处理ing    return id;}//功能:创建着色器程序static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader){    //创建程序对象    unsigned int program = glCreateProgram();    //编译顶点着色器对象    unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);    //编译片段着色器对象    unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);    //功能:将编译好的着色器对象附加到程序对象中    glAttachShader(program, vs);    glAttachShader(program, fs);    //功能:链接程序对象    glLinkProgram(program);    //功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。    glValidateProgram(program);    //删除着色器对象,因为它们已经被链接到程序对象中    glDeleteShader(vs);    glDeleteShader(fs);    //返回着色器程序    return program;}int main(void){    GLFWwindow* window;    //初始化glfw    if (!glfwInit())      return -1;    //创建一个窗口模式的窗口并设置OpenGL上下文    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);    if (!window)//如果窗口创建失败,则终止程序    {      glfwTerminate();//释放glfw资源      return -1;    }    //设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行    glfwMakeContextCurrent(window);    //初始化GLEW    if (glewInit() != GLEW_OK)      std::cout << "Error!" << std::endl;    //打印OpenGL版本信息    std::cout << glGetString(GL_VERSION) << std::endl;    //准备数据    float position[] = {      -0.5f, -0.5f,      0.5f, -0.5f,      0.5f,0.5f,      -0.5f,0.5f,    };    //定义顶点索引缓存,用于标定顶点顺序    unsigned int indices[] = {      0,1,2,      2,3,0    };      //定义缓冲区对象    unsigned int buffer;    //功能:天生缓冲区对象,并将数据写入缓冲区    glGenBuffers(1, &buffer);    //功能:将缓冲区对象绑定到目标    glBindBuffer(GL_ARRAY_BUFFER, buffer);    //功能:将数据写入缓冲区    glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);    //功能:配置顶点属性指针    glEnableVertexAttribArray(0);    //功能:指定顶点属性数组的索引、巨细、数据范例、是否归一化、偏移量、数据指针    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);            //索引缓冲区对象    unsigned int ibo;    //功能:天生缓冲区对象,并将数据写入缓冲区    glGenBuffers(1, &ibo);    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区    //参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象    //2.ibo:索引缓冲区对象ID    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);    //功能:将数据写入缓冲区    //参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象    //2.6*sizeof(unsigned int):索引数据巨细    //3.indices:索引数据指针    //4.GL_STATIC_DRAW:指定数据不会改变    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);    //分析着色器代码文件    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");    std::string vertexShader = source.VertexSource;    std::string fragmentShader = source.FragmentSource;    //创建着色器程序    unsigned int shader = CreateShader(vertexShader, fragmentShader);    //使用着色器程序    glUseProgram(shader);    //渲染循环,直到窗口被关闭    while (!glfwWindowShouldClose(window))    {      //清除颜色缓冲区      glClear(GL_COLOR_BUFFER_BIT);                //glDrawArrays(GL_TRIANGLES, 0, 6);      //功能:绘制三角形      //参数:1.GL_TRIANGLES:绘制三角形      //2.6:顶点数量      //3.GL_UNSIGNED_INT:索引数据范例      //4.nullptr:索引数据指针      //因为索引缓冲区已经绑定到GL_ELEMENT_ARRAY_BUFFER目标,以是这里不必要再传入索引数据指针      glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
      //革新缓冲区并交换窗口      glfwSwapBuffers(window);      //处理窗口变乱,如键盘输入、鼠标移动等      glfwPollEvents();    }    //删除着色器程序    //glDeleteProgram(shader);    //释放 GLFW 库占用的所有资源。    glfwTerminate();    return 0;} 2.2简洁注释代码

#include <GL/glew.h>#include <GLFW/glfw3.h>#include<iostream>#include<fstream>#include<string>#include<sstream>//功能:定义ShaderProgramSource布局体,用于存储着色器代码struct ShaderProgramSource{    std::string VertexSource;    std::string FragmentSource;};//功能:分析着色器代码文件。static ShaderProgramSource ParseShader(const std::string& filepath){    //功能:打开文件流    std::ifstream stream(filepath);    //定义着色器范例    enumclass ShaderType    {      NONE=-1,VERTEX=0,FRAGMENT=1    };    //该变量用于存储着色器代码    std::string line;    //该变量用于存储着色器范例    std::stringstream ss;    //该变量是当前着色器范例    ShaderType type = ShaderType::NONE;    //功能:读取文件中的每一行内容,直到文件竣事    while (getline(stream, line))    {      //如果当前行包罗#shader,则阐明接下来是着色器代码      if (line.find("#shader") != std::string::npos)      {            //如果当前行包罗vertex,则阐明接下来是顶点着色器代码            if (line.find("vertex") != std::string::npos)            {                type = ShaderType::VERTEX;            }            else if (line.find("fragment") != std::string::npos)            {                type = ShaderType::FRAGMENT;            }      }      else      {            //否则,将当前行添加到对应着色器代码的stringstream中            ss[(int)type] << line << '\n';      }    }    //返回ShaderProgramSource布局体    return { ss.str(), ss.str() };}//功能:编译着色器代码static unsigned int CompilesShader(unsigned int type, const std::string& source){    //功能:创建着色器对象    unsigned int id = glCreateShader(type);    //功能:设置着色器源代码.    const char* src = source.c_str();    //功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串    glShaderSource(id, 1, &src, nullptr);    //功能:编译着色器对象的源代码    glCompileShader(id);    //设置返回着色器的对象ID    int result;    //功能:从着色器对象返回一个参数,表示编译是否成功。    glGetShaderiv(id, GL_COMPILE_STATUS, &result);    //如果编译失败,则输出错误信息    if (result == GL_FALSE)    {      int length;      //功能:获取编译错误信息的长度      glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);      //分配内存,用于存储编译错误信息      char* message = (char*)alloca(length*sizeof(char));      //功能:获取编译错误信息      glGetShaderInfoLog(id, length, &length, message);      std::cout << "Failed to compile shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;      std::cout << message << std::endl;      //删除着色器对象      glDeleteShader(id);      return 0;    }    //TODO:错误处理ing    return id;}//功能:创建着色器程序static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader){    //创建程序对象    unsigned int program = glCreateProgram();    //编译顶点着色器对象    unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);    //编译片段着色器对象    unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);    //功能:将编译好的着色器对象附加到程序对象中    glAttachShader(program, vs);    glAttachShader(program, fs);    //功能:链接程序对象    glLinkProgram(program);    //功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。    glValidateProgram(program);    //删除着色器对象,因为它们已经被链接到程序对象中    glDeleteShader(vs);    glDeleteShader(fs);    //返回着色器程序    return program;}int main(void){    GLFWwindow* window;    //初始化glfw    if (!glfwInit())      return -1;    //创建一个窗口模式的窗口并设置OpenGL上下文    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);    if (!window)//如果窗口创建失败,则终止程序    {      glfwTerminate();//释放glfw资源      return -1;    }    //设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行    glfwMakeContextCurrent(window);    //初始化GLEW    if (glewInit() != GLEW_OK)      std::cout << "Error!" << std::endl;    //打印OpenGL版本信息    std::cout << glGetString(GL_VERSION) << std::endl;    //准备数据    float position[] = {      -0.5f, -0.5f,      0.5f, -0.5f,      0.5f,0.5f,      -0.5f,0.5f,    };    //定义顶点索引缓存,用于标定顶点顺序    unsigned int indices[] = {      0,1,2,      2,3,0    };      //定义缓冲区对象    unsigned int buffer;    //功能:天生缓冲区对象,并将数据写入缓冲区    glGenBuffers(1, &buffer);    //功能:将缓冲区对象绑定到目标    glBindBuffer(GL_ARRAY_BUFFER, buffer);    //功能:将数据写入缓冲区    glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW);    //功能:配置顶点属性指针    glEnableVertexAttribArray(0);    //功能:指定顶点属性数组的索引、巨细、数据范例、是否归一化、偏移量、数据指针    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);            //索引缓冲区对象    unsigned int ibo;    //功能:天生缓冲区对象,并将数据写入缓冲区    glGenBuffers(1, &ibo);    //功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区    //参数:1.GL_ELEMENT_ARRAY_BUFFER:指定目标为索引缓冲区对象    //2.ibo:索引缓冲区对象ID    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);    //功能:将数据写入缓冲区    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW);    //分析着色器代码文件    ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");    std::string vertexShader = source.VertexSource;    std::string fragmentShader = source.FragmentSource;    //创建着色器程序    unsigned int shader = CreateShader(vertexShader, fragmentShader);    //使用着色器程序    glUseProgram(shader);    //渲染循环,直到窗口被关闭    while (!glfwWindowShouldClose(window))    {      //清除颜色缓冲区      glClear(GL_COLOR_BUFFER_BIT);                //glDrawArrays(GL_TRIANGLES, 0, 6);      //功能:绘制三角形      //参数:1.GL_TRIANGLES:绘制三角形      glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
      //革新缓冲区并交换窗口      glfwSwapBuffers(window);      //处理窗口变乱,如键盘输入、鼠标移动等      glfwPollEvents();    }    //删除着色器程序    //glDeleteProgram(shader);    //释放 GLFW 库占用的所有资源。    glfwTerminate();    return 0;} 2.3运行效果

https://i-blog.csdnimg.cn/direct/11b99b7a429c44fe8eed1f8f5fe8eec2.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: OpenGL Chan视频学习-9 Index Buffers inOpenGL