• 顶点数组对象:Vertex Array Object,VAO,用于存储顶点状态配置信息,每当界面刷新时,则通过VAO进行绘制.
  • 顶点缓冲对象:Vertex Buffer Object,VBO,通过VBO将大量顶点存储在GPU内存(通常被称为显存)中
 
1.渲染步骤
下面,你会看到一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。
  • 注意:片段着色器也称为片元着色器
 
 
顶点着色器(Vertex Shader)
顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
 
图元装配(Primitive Assembly)
将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。
 
几何着色器和光栅化阶段
几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
 
片元着色器
主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
 
测试和混合(Blending)阶段
这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点/片段着色器)。出于这个原因,刚开始学习现代OpenGL的时候可能会非常困难,因为在你能够渲染自己的第一个三角形之前已经需要了解一大堆知识了。在本节结束你最终渲染出你的三角形的时候,你也会了解到非常多的图形编程知识。
而几何着色器是可选的,通常使用它默认的着色器就行了。
 
2.通过代码实现每步骤
2.1 顶点数据(Vertex Data)
我们希望渲染一个三角形,所以创建三个顶点,我们将它顶点的z坐标设置为0.0。从而使它看上去像是2D的。
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
然后我们通过glViewport函数进行视口变换(Viewport Transform),变换后的坐标x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。
glViewport函数声明如下所示:
glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
//x y:定义视口的左下角开始位置
//width,height :定义这个视口矩形的宽度和高度
如果设置全屏,一般都是 glViewport(0, 0, width(), height()),比如设置为glViewport(100, 50, 100,100)时,那么对应300x200窗口,是这样的:
2.2 通过VBO将顶点存储到GPU内存中
接下来我们还要通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,通过它将大量顶点存储在GPU内存(通常被称为显存)中。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
顶点缓冲对象是我们在OpenGL教程中第一个出现的OpenGL对象。就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:
unsigned int VBO;
glGenBuffers(1, &VBO);
OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
glBindBuffer(GL_ARRAY_BUFFER, VBO);
从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //把用户定义的数据复制到当前绑定缓冲对象上
//参数1:目标缓冲的类型
//参数2:传输数据的大小(以字节为单位)
//参数3:数据指针
//参数4:指定我们希望显卡如何管理给定的数据
它有三种形式:
  • GL_STATIC_DRAW :数据不会或几乎不会改变(一次修改,多次使用)
  • GL_DYNAMIC_DRAW:数据会频繁修改(多次修改,多次使用)
  • GL_STREAM_DRAW :数据每次绘制时都会改变(每帧都不同,一次修改,一次使用)
 
现在我们已经把顶点数据储存在显卡的内存中,用VBO这个顶点缓冲对象管理。下面我们会创建一个顶点和片段着色器来真正处理这些数据。
 
2.3 顶点着色器源码
做的第一件事是用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器源码,下面你会看到一个非常基础的GLSL顶点着色器的源代码:
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
  • #version 330 core : 定义版本号,需要OpenGL 3.3或者更高版本
  • layout (location = 0) in vec3 aPos : 使用in关键字来声明顶点属性输入,这里创建一个输入变量aPos(3分量),通过layout (location = 0)设定了输入变量的顶点属性的位置值(Location)为0,后面将会通过glVertexAttribPointer()函数来设置它.
  • gl_Position : 设置顶点着色器的输出,这里gl_Position之所以为vec4类型,是因为3d图形演算要用到 4x4的矩阵(4行4列),而矩阵乘法要求n行m列 和 m行p列才能相乘,所以是vec4而不是vec3,由于position 是位置所以应该是 (x,y,z,1.0f),如果是方向向量,则就是 (x,y,z,0.0f).
在真实的程序里输入数据通常都不是标准化设备坐标,所以我们首先必须先把它们转换至OpenGL的可视区域内。
 
2.4 编译顶点着色器
我们已经写了一个顶点着色器源码,但为了能够让OpenGL使用它,我们必须在运行时动态编译它的源码。
我们首先要做的是创建一个顶点着色器对象,注意还是用ID来引用的。所以我们储存这个顶点着色器为unsigned int,然后用glCreateShader创建这个着色器:
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
//创建一个着色器,参数GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER,由于我们创建的是顶点shader,所以填入GL_VERTEX_SHADER ,否则就是片元shader

下一步我们把这个着色器源码附加到着色器对象上,然后编译它:

glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //设置顶点源码
glCompileShader(vertexShader);//编译源码 int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);//检测着色器编译是否成功 if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);//返回着色器对象的信息日志
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
glGetShaderiv函数不仅仅用于检测着色器编译成功,还可以判断类型,状态,源码等,函数定义如下所示:
void glGetShaderiv(GLuint shader,GLenum pname,GLint *params);
//shader:要查询的着色器对象
//pname : 查询类型,可以设置的有GL_SHADER_TYPE、GL_DELETE_STATUS、GL_COMPILE_STATUS、GL_INFO_LOG_LENGTH.
//GL_SHADER_TYPE: 用来判断并返回着色器类型,若是顶点着色器则success=GL_VERTEX_SHADER,若是片元着色器success=GL_FRAGMENT_SHADER
//GL_DELETE_STATUS: 判断着色器是否被删除,success=GL_TRUE,否则success=GL_FALSE,
//GL_COMPILE_STATUS: 用于检测编译是否成功,success=GL_TRUE,否则success=GL_FALSE,
//GL_INFO_LOG_LENGTH: 获取着色器的信息日志的长度(information log length), 如果着色器没有信息日志,则success=0。
//GL_SHADER_SOURCE_LENGTH: 获取着色器源码长度,不存在则success=0;
//params:查询的内容
 
2.5 片元着色器(也称为片段着色器)
我们渲染三角形最后一步就是片元着色器,用来计算出每个像素的最终颜色。这里我们设置为输出橘黄色。
在OpenGL或GLSL中,颜色每个分量的强度设置在0.0到1.0之间。比如说我们设置红为1.0f,绿为1.0f,我们会得到两个颜色的混合色,即黄色。
所以片元着色器源码如下所示:
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);\n"
"}\n\0";
  • FragColor : 定义类型为out vec4 类型,表明是个要输出的变量,该变量值为 vec4(1.0f, 1.0f, 0.0f, 1.0f),表示的是RGBA为(1,1,0,1),所以为黄色,而alpha值为1.0,表示完全不透明
 
2.6 编译顶点着色器
编译片段着色器的过程与顶点着色器类似,代码如下所示:
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); //创建一个片元着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//设置片元源码
glCompileShader(fragmentShader); //编译

2.7 着色器Program对象

两个着色器现在都编译了,接下来就是把两个着色器对象链接到一个用来渲染(调用顶点shader和片元shader数据)的着色器Program对象中。

创建一个Program对象,并链接shader
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader); //将附加vertexShader到的shaderProgram对象中
glAttachShader(shaderProgram, fragmentShader);//将附加fragmentShader到的shaderProgram对象中
glLinkProgram(shaderProgram); //将附加的shader链接到program对象中 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); ////检测链接是否成功
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
} glDeleteShader(vertexShader); //链接后不再需要它们,需要删除shader
glDeleteShader(fragmentShader);
glGetProgramiv函数不仅仅用于检测链接成功,还可以获取着色器对象数量、激活的属性变量数等,比如:
glGetProgramiv(shaderProgram, GL_ATTACHED_SHADERS, &cnt); //获取着色器对象的数量
现在,我们已经把输入顶点数据发送给了GPU,但是OpenGL还不知道它该如何解释内存中的顶点数据(组件数量,数据类型,顶点个数),比如xyz坐标数据类型是GL_BYTE型,还是GL_SHORT型,还是GL_FLOAT型等
所以我们需要通过glVertexAttribPointer()设置顶点属性,然后使能属性,并激活shaderProgram
 
2.8 链接顶点属性
我们的顶点缓冲数据会被解析为下面这样子:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//设置顶点属性
glEnableVertexAttribArray(0);//使能顶点属性(默认是禁止的)
glUseProgram(shaderProgram); //激活Program对象
someOpenGLFunctionThatDrawsOurTriangle();// 绘制物体
其中glVertexAttribPointer()函数声明如下所示:
void glVertexAttribPointer(GLuint index , GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr);
//指定渲染时索引值为 index 的顶点属性数组的数据格式和位置。
//index:指定要修改的顶点位置的索引值,之前使用layout(location = 0)设置了顶点位置为0,
//size:指定每个顶点属性的组件数量。必须为1、2、3、4之一。(如我们这里顶点是由3个(x,y,z)组成,而颜色是4个(r,g,b,a))
//type:指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT
//normalized:是否希望数据被标准化。设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSEM,不需要。
//stride:步长,指定顶点之间的偏移量。我们这里是每3个float为一个顶点,所以偏移量为3 * sizeof(float)
//ptr:可以指向需要绑定的VBO,如果已经绑定VBO,并且位置数据在缓冲中起始位置为0,那么此项为0,否则填入开头的偏移量

当我们绘制好物体后,每当最大化,尺寸变化界面后,openGL就会进入刷新状态,所以我们需要把所有这些状态配置储存在一个顶点数组对象(Vertex Array Object, VAO)中,每次刷新时,就可以通过VAO来恢复状态.

2.9 顶点数组对象VAO实现
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置清除颜色(背景色)为rgba(0.2f, 0.3f, 0.3f, 1.0f)

//  初始化代码,初始化顶点shader、片元shader、program、vbo
// ... ..
// ... ... //1.初始化vao
unsigned int VAO;
glGenVertexArrays(1, &VAO); // 注册VAO
glBindVertexArray(VAO); // 绑定VAO //2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把用户定义的顶点数据复制到vao上

//3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);//使能顶点属性(默认是禁止的)
//4. 解绑VAO
glBindVertexArray(0);
然后每次绘制物体时,只需要:
glClear(GL_COLOR_BUFFER_BIT); //开始清除,设置背景色
glUseProgram(shaderProgram); ////激活Program对象
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3); //绘制三角形
someOpenGLFunctionThatDrawsOurTriangle();// 绘制物体
glBindVertexArray(0); //绘制完成,便解绑,用来绑定下一个要绘制的物体
这里,我们一直再调用glBindVertexArray()绑定和解绑,是为了方便绘制有多个VAO的时候,避免出错(如果只绘制一个VAO,也可以无需多次解绑).
 
其中glDrawArrays函数声明如下所示:
glDrawArrays(GLenum mode, GLint first, GLsizei count);
//函数根据顶点数组中的坐标数据和指定的模式,进行绘制。
//mode,绘制方式,如下图所示,提供以下参数:
//GL_POINTS(画点)、GL_LINES(每两个顶点为一条直线)、GL_LINE_LOOP(是个环状)、
//GL_LINE_STRIP(第一个顶点和最后一个顶点不相连)、GL_TRIANGLES(每三个顶点组成一个三角形)、
//GL_TRIANGLE_STRIP(共用多个顶点的一个三角形)、GL_TRIANGLE_FAN(共用一个原点为中心的一个三角形)。 //first,从数组缓存中的哪一位开始绘制,一般为0。
//count,数组中顶点的数量。

如下图所示:

 
2.10 最终代码如下所示:
//hello_triangle.cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h> #include <iostream> void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window); // settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600; const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0"; int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif // glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
} // build and compile our shader program
// ------------------------------------
// vertex shader
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// fragment shader
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile errors
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// link shaders
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader); // set up vertex data (and buffer(s)) and configure vertex attributes
// ------------------------------------------------------------------
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
}; unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0); // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
glBindVertexArray(0); // uncomment this call to draw in wireframe polygons.
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// input
// -----
processInput(window); // render
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); // draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0); // no need to unbind it every time // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
} // optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO); // glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
return 0;
} // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
} // glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height);
}

未完待续 ,下章学习:

2.通过QOpenGLWidget绘制三角形

1.opengl绘制三角形的更多相关文章

  1. opengl绘制三角形

    顶点数组对象:Vertex Array Object,VAO 顶点缓冲对象:Vertex Buffer Object,VBO 索引缓冲对象:Element Buffer Object,EBO或Inde ...

  2. 2.通过QOpenGLWidget绘制三角形

    参考:1.opengl绘制三角形 1.QOpenGLWidget的早先版本 QGLWidget是遗留Qt OpenGL模块的一部分,和其他QGL类一样,应该在新的应用程序中避免使用.相反,从Qt 5. ...

  3. [Modern OpenGL系列(三)]用OpenGL绘制一个三角形

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...

  4. Android OpenGL ES(十)绘制三角形Triangle .

    三角形为OpenGL ES支持的面,同样创建一个DrawTriangle Activity,定义6个顶点使用三种不同模式来绘制三角形: float vertexArray[] = { -0.8f, - ...

  5. Android OpenGL 入门示例----绘制三角形和正方形

    Android上对OpenGl的支持是无缝的,所以才有众多3D效果如此逼真的游戏,在Camera的一些流程中也有用到GLSurfaceView的情况.本文记录OpenGL在Android上的入门级示例 ...

  6. Linux OpenGL 实践篇-3 绘制三角形

    本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...

  7. OpenGL学习(2)——绘制三角形

    在创建窗口的基础上,添加代码实现三角形的绘制. 声明和定义变量 在屏幕上绘制一个三角形需要的变量有: 三角形的三个顶点坐标: Vertex Buffer Object 将顶点数据存储在GPU的内存中: ...

  8. 用OpenGL绘制平滑着色的三角形与相交区域的混合着色

    一.三角形的绘制 在OpenGL中,面是由多边形构成的.三角形可能是最简单的多边形,它有三条边.可以使用GL_TRIANGLES模式通过把三个顶点连接到一起而绘出三角形. 使用GL_TRIANGLE_ ...

  9. OpenGL绘制一个三角形

    应该建立一个vertex shader文件和一个pixel shader文件,分别命名为shader.vsh和shader.fsh. shader.vsh: attribute vec3 positi ...

随机推荐

  1. java identityHashCode 和 hashCode

    当类并没有重写Object#hashCode()时, 对于 System.identityHashCode(Object) 和 Object#hashCode() 的结果是一致的; 但对于类似Stri ...

  2. Redis5设计与源码分析读后感(一)认识Redis

    一.初识redis 定义 Redis是一个开源的Key-Value数据库,通常被称为数据结构服务器,其值可以是多种常见的数据格式,且读写性能极高,且所有操作都是原子性的. 高性能的主要原因 1.基于内 ...

  3. CUMTCTF'2020 已做wp

    三天的比赛终于结束了,不知道有没有睡10个小时,感觉像中了魔一样,但也很享受这种感觉,除了没有能和我一起琢磨题目朋友.. 就最终结果而言还是有一些可惜,明明号称擅长web和misc反而是得分比例最小的 ...

  4. ORA-00060: Deadlock detected 模拟死锁产生与解决方案

    死锁:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程 ...

  5. 如何写出优美的 JavaScript 代码?

    一.变量相关 (1)变量数量的定义 NO:滥用变量 let kpi = 4; // 定义好了之后再也没用过 function example() { var a = 1; var b = 2; var ...

  6. Centos-实时监控系统处理器状态-top

    top 实时监控处理器状态的实时监控,能够显示系统中各个进程的资源占用状况 相关选项 -d 指定每两次屏幕信息刷新之间间隔秒数 -i  不显示闲置或者僵死进程信息 -c 显示进程整个命令路径 -s 安 ...

  7. 深入理解Callable接口

    Callable接口: Callable,新启线程的一种方式,返回结果并且可能抛出异常的任务,在前面的新启线程的文章中用过,但是没有具体讲解 优点: 可以获取线程的执行结果,也称为返回值 通过与Fut ...

  8. 我把这个贼好用的Excel导出工具开源了!!

    写在前面 不管是传统软件企业还是互联网企业,不管是管理软件还是面向C端的互联网应用.都不可避免的会涉及到报表操作,而对于报表业务来说,一个很重要的功能就是将数据导出到Excel.如果我们在业务代码中, ...

  9. 093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 03 static关键字(下)

    093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  10. 061 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 08 一维数组总结

    061 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 08 一维数组总结 本文知识点:一维数组总结 总结 注意点