为您推荐:
精华内容
最热下载
问答
  • 5星
    21.17MB qc_id_01 2021-07-29 12:05:14
  • 【我的OpenGL学习进阶之旅】顶点属性、顶点数组一、 指定顶点属性数据1.1 常量顶点属性1.2 顶点数组1.3 分配和存储顶点属性数据有两种常用的方法1.3.1 例1: 结构数组1.3.2 例2: 数组结构1.4 如何存储顶点的不同属性...

    在这里插入图片描述

    顶点数据也称作顶点属性,指定每个顶点的数据。这种逐顶点数据可以为每个顶点指定,也可以用于所有顶点的常量。
    例如,如果你想要绘制固定颜色的三角形(如下图,假定颜色为黑色),可以指定一个常量值,用于三角形的全部3个顶点。但是,组成三角形的3个顶点的位置不同,所以我们必须指定一个顶点数组来存储3个位置值。

    在这里插入图片描述

    一、 指定顶点属性数据

    顶点属性数据可以用一个顶点数组对每个顶点指定,也可以将一个常量值用于一个图元的所有顶点。

    所有OpenGL ES 3.0实现必须支持最少16个顶点属性。应用程序可以查询特定实现支持的顶点属性的准确数量。

    下面代码说明应用程序如何查询OpenGL ES 3.0 实现 真正支持的顶点属性数量、

    	GLint maxVertexAttribs;// n will be >= 16
    	glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
    

    在这里插入图片描述

    1.1 常量顶点属性

    常量顶点属性对于一个图元的所有顶点都相同,所以对一个图元的所有顶点只需指定一个值。
    可以用如下任何一个函数指定:

    	void glVertexAttrib1f (GLuint index, GLfloat x);
    	void glVertexAttrib1fv (GLuint index, const GLfloat *v);
    	
    	void glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y);
    	void glVertexAttrib2fv (GLuint index, const GLfloat *v);
    	
    	void glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z);
    	void glVertexAttrib3fv (GLuint index, const GLfloat *v);
    	
    	void glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);
    	void glVertexAttrib4fv (GLuint index, const GLfloat *v);
    

    在这里插入图片描述
    glVertexAttrib* 命令用于加载index指定的通用属性。

    • glVertexAttrib1fglVertexAttrib1fv 函数在通用顶点属性中加载 (x, 0.0, 0.0, 1.0)
    • glVertexAttrib2fglVertexAttrib2fv 函数在通用顶点属性中加载 (x, y, 0.0, 1.0)
    • glVertexAttrib3fglVertexAttrib3fv 函数在通用顶点属性中加载 (x, y, z, 1.0)
    • glVertexAttrib4fglVertexAttrib4fv 函数在通用顶点属性中加载 (x, y, z, w)

    在实践中,常量顶点属性提供与使用标量/向量统一变量等价的功能,两者都是可以接受的选择。

    1.2 顶点数组

    顶点数组指定每个顶点的属性,是保存在应用程序地址空间(OpenGL ES 称为客户空间) 的缓冲区。
    它们作为顶点缓冲区对象的基础,提供指定顶点属性数据的一种高效、灵活的手段。

    顶点数组用glVertexAttribPointerglVertexAttribIPointer函数指定。

    void glVertexAttribPointer (GLuint index, GLint size,
    								 GLenum type, 
    								 GLboolean normalized, 
    								 GLsizei stride, 
    								 const void *pointer);
    
    
    void glVertexAttribIPointer (GLuint index, GLint size, 
    								GLenum type, 
    								GLsizei stride, 
    								const void *pointer);
    
    

    在这里插入图片描述

    在这里插入图片描述
    参数说明:

    • index
      指定通用顶点属性索引。这个值的范围从0到支持的最大顶点属性数-1。

    • size
      顶点数组中为索引引用的顶点属性所指定的分量数量。有效值为1~4

    • type
      数据格式。两个函数都包括的有效值是:

      #define GL_BYTE                           0x1400
      #define GL_UNSIGNED_BYTE                  0x1401
      #define GL_SHORT                          0x1402
      #define GL_UNSIGNED_SHORT                 0x1403
      #define GL_INT                            0x1404
      #define GL_UNSIGNED_INT                   0x1405
      

      在这里插入图片描述

      glVertexAttribPointer的有效值还包括:

      #define GL_HALF_FLOAT                     0x140B
      #define GL_FLOAT                          0x1406
      #define GL_FIXED                          0x140C
      #define GL_INT_2_10_10_10_REV             0x8D9F
      #define GL_UNSIGNED_INT_2_10_10_10_REV    0x8368
      
    • normalized
      仅限glVertexAttribPointer函数,用于标识非浮点数据格式在转换为浮点值时是否应该规范化。对于glVertexAttribIPointer ,这些值被当做整数对待

    • stride
      每个顶点由size指定的顶点属性分量顺序存储。stride指定顶点索引I和(I+1)表示的顶点数据之间的位移。如果stride为0,则每个顶点的属性数据顺序存储。如果stride大于0,则使用该值作为获取下一个索引表示的顶点数据的跨距。

    • pointer
      如果使用客户端顶点数组,则是保存顶点属性数据的缓冲区指针。如果使用顶点缓冲区对象,则表示该缓冲区内的偏移量。

    接下来,我们介绍几个示例,说明如何用glVertexAttribPointer函数指定顶点属性。

    1.3 分配和存储顶点属性数据有两种常用的方法

    分配和存储顶点属性数据有两种常用的方法:

    • 结构数组
      结构数组:在一个缓冲区中存储顶点属性。结构表示顶点的所有属性,每个顶点由一个属性的数组。
    • 数组结构
      数组结构:在单独的缓冲区中保存每个顶点属性。

    假定每个顶点有4个顶点属性:位置、法线和两个纹理坐标。 这些属性一起保存在为所有顶点分配的一个缓冲区中。

    • 顶点位置属性: 以3个浮点数的向量(x,y,z) 的形式指定。
    • 顶点法线: 也以3个浮点数的向量(x,y,z) 的形式指定。
    • 每个纹理坐标: 以2个浮点数组成的向量(s,t)的形式指定。

    下图给出了这个缓冲区的内存布局。

    在这里插入图片描述
    在这个例子中,缓冲区的跨距为组成顶点的所有属性的总大小。(一个顶点等于10个浮点数或者40个字节:12个字节用于位置、12个字节用于法线,8个字节用于Tex0,8个字节用于Tex1)

    下面的例1描述了如何用glVertexAttribPointer指定这4个顶点属性。

    注意:这里介绍如何使用客户端顶点数组,以便解释逐顶点数据指定的概念。
    但是我们建议应用程序使用顶点缓冲区对象 ,避免使用客户端顶点数组,以实现最佳性能。

    • 在OpenGL ES 3.0 中支持 客户端顶点数组只是为了与 OpenGL ES 2.0兼容。
    • 在OpenGL ES 3.0 中,总是建议使用顶点缓冲区对象。

    1.3.1 例1: 结构数组

    //
    // Created by OuyangPeng on 2021/11/17 0017.
    //
    #define VERTEX_POS_SIZE             3     // x, y and z
    #define VERTEX_NORMAL_SIZE          3     // x, y and z
    #define VERTEX_TEXCOORD0_SIZE       2     // s and t
    #define VERTEX_TEXCOORD1_SIZE       2     // s and t
    
    #define VERTEX_POS_INDX             0
    #define VERTEX_NORMAL_INDX          1
    #define VERTEX_TEXCOORD0_INDX       2
    #define VERTEX_TEXCOORD1_INDX       3
    
    // the following 4 defines are used to determine the locations
    // of various attributes if vertex data are stored as an array
    // of structures
    #define VERTEX_POS_OFFSET             0
    #define VERTEX_NORMAL_OFFSET          3
    #define VERTEX_TEXCOORD0_OFFSET       6
    #define VERTEX_TEXCOORD1_OFFSET       8
    
    #define VERTEX_ATTRIB_SIZE  (VERTEX_POS_SIZE + \
                                    VERTEX_NORMAL_SIZE + \
                                    VERTEX_TEXCOORD0_SIZE + \
                                    VERTEX_TEXCOORD1_SIZE)
    
    float *p = (float*) malloc(numVertices * VERTEX_ATTRIB_SIZE * sizeof(float));
    
    // position is vertex attribute 0
    glVertexAttribPointer(VERTEX_POS_INDX,VERTEX_POS_SIZE,
                          GL_FLOAT,GL_FALSE,
                          VERTEX_ATTRIB_SIZE * sizeof(float),
                          p);
    
    // normal is vertex attribute 1
    glVertexAttribPointer(VERTEX_NORMAL_INDX,VERTEX_NORMAL_SIZE,
                            GL_FLOAT,GL_FALSE,
                            VERTEX_ATTRIB_SIZE * sizeof(float),
                            p+VERTEX_NORMAL_OFFSET);
    
    // texture coordinate 0  is vertex attribute 2
    glVertexAttribPointer(VERTEX_TEXCOORD0_INDX,VERTEX_TEXCOORD0_SIZE,
                        GL_FLOAT,GL_FALSE,
                        VERTEX_ATTRIB_SIZE * sizeof(float),
                        p+VERTEX_TEXCOORD0_OFFSET);
    
    // texture coordinate 1 is vertex attribute 3
    glVertexAttribPointer(VERTEX_TEXCOORD1_INDX,VERTEX_TEXCOORD1_SIZE,
                        GL_FLOAT,GL_FALSE,
                        VERTEX_ATTRIB_SIZE * sizeof(float),
                        p+VERTEX_TEXCOORD1_OFFSET);
    

    在这里插入图片描述
    在这里插入图片描述

    1.3.2 例2: 数组结构

    //
    // Created by OuyangPeng on 2021/11/17 0017.
    //
    #define VERTEX_POS_SIZE             3     // x, y and z
    #define VERTEX_NORMAL_SIZE          3     // x, y and z
    #define VERTEX_TEXCOORD0_SIZE       2     // s and t
    #define VERTEX_TEXCOORD1_SIZE       2     // s and t
    
    #define VERTEX_POS_INDX             0
    #define VERTEX_NORMAL_INDX          1
    #define VERTEX_TEXCOORD0_INDX       2
    #define VERTEX_TEXCOORD1_INDX       3
    
    float *position = (float*) malloc(numVertices * VERTEX_POS_SIZE * sizeof(float));
    float *normal = (float*) malloc(numVertices * VERTEX_NORMAL_SIZE * sizeof(float));
    float *texcoord0 = (float*) malloc(numVertices * VERTEX_TEXCOORD0_SIZE * sizeof(float));
    float *texcoord1 = (float*) malloc(numVertices * VERTEX_TEXCOORD1_SIZE * sizeof(float));
    
    // position is vertex attribute 0
    glVertexAttribPointer(VERTEX_POS_INDX,VERTEX_POS_SIZE,
                          GL_FLOAT,GL_FALSE,
                          VERTEX_POS_SIZE * sizeof(float),
                          position);
    
    // normal is vertex attribute 1
    glVertexAttribPointer(VERTEX_NORMAL_INDX,VERTEX_NORMAL_SIZE,
                            GL_FLOAT,GL_FALSE,
                            VERTEX_NORMAL_SIZE * sizeof(float),
                            normal);
    
    // texture coordinate 0  is vertex attribute 2
    glVertexAttribPointer(VERTEX_TEXCOORD0_INDX,VERTEX_TEXCOORD0_SIZE,
                        GL_FLOAT,GL_FALSE,
                        VERTEX_TEXCOORD0_SIZE * sizeof(float),
                        texcoord0);
    
    // texture coordinate 1 is vertex attribute 3
    glVertexAttribPointer(VERTEX_TEXCOORD1_INDX,VERTEX_TEXCOORD1_SIZE,
                        GL_FLOAT,GL_FALSE,
                        VERTEX_TEXCOORD1_SIZE * sizeof(float),
                        texcoord1);
    

    在这里插入图片描述
    在这里插入图片描述

    1.4 如何存储顶点的不同属性

    1.4.1 结构数组和数组结构,哪种分配方法更高效?

    我们已经描述了两种常用的顶点属性存储方法: 结构数组和数组结构。
    问题是,对于OpenGL ES 3.0硬件实现,哪种分配方法更高效? 在大部分情况下,答案是结构数组。
    原因是,每个顶点的属性数据可以顺序方式读取,这最有可能造成高效的内存访问模式。

    1.4.2 使用结构数组的缺点

    使用结构数组的缺点在应用程序需要修改特定属性时变得很明显。 如果顶点属性数据的一个子集需要修改(例如,纹理坐标),这将造成顶点缓冲区的跨距更新。当顶点缓冲区以缓冲区对象的形式提供时,需要重新加载整个顶点属性缓冲区。

    • 如何解决?
      可以通过将动态的顶点属性保存在单独的缓冲区来避免这种效率低下的情况。

    1.5 顶点属性使用哪种数据格式

    glVertexAttribPointer 中用type参数指定的顶点属性数据格式不仅影响顶点属性数据的图形内存存储要求,而且影响整体性能,这是渲染帧所需内存带宽的一个函数。

    数据空间占用越小,需要的内存带宽越小。

    OpenGL ES 3.0 支持名为 GL_HALF_FLOAT的16位浮点顶点格式。建议在应用程序中尽可能使用 GL_HALF_FLOAT

    • 纹理坐标、法线、副法线、切向量等都是使用 GL_HALF_FLOAT存储每个分量的候选。
    • 颜色可以存储为 GL_UNSIGNED_BYTE,每个顶点颜色具有4个分量。
    • 建议使用GL_HALF_FLOAT存储顶点位置,但是发现这种选择在不少情况下都不可行。对于这些情况,顶点位置可以存储为GL_FLOAT

    1.6 glVertexAttribPointer 中的规范化标志如何工作

    在用于顶点着色器之前,顶点属性在内部保存为单精度浮点数。如果数据类型表示顶点属性不少浮点数,顶点属性将在用于顶点着色器之前转换为单精度浮点数。

    规范化标志控制非浮点顶点属性数据到单精度浮点值的转换。

    • 如果规范化标志位假,则顶点数据被直接转换为浮点值。
      这类似于将非浮点类型的变量转换为浮点变量。下面代码提供了一个例子:
    GLfloat f;
    GLbyte b;
    f = (GLfloat) b; // f represents values in the range [ -128.0 , 127.0 ]
    
    • 如果规范化标志位真,且如果数据类型为GL_BYTE、GL_SHORT或者GL_FIXED,则顶点数据被映射到[ -1.0, 1.0 ]范围内。如果数据类型为GL_UNSIGNED_BYTE 或者 GL_UNSIGNED_SHORT,则被映射到[ 0.0, 1.0 ]范围内。

    下表说明了设置规范化标志时非浮点数据类型的转换,表中第2列的c值指的是第1列中的指定格式的一个值。

    在这里插入图片描述
    在这里插入图片描述

    在顶点着色器中,也有可能按照整数的形式访问整数型顶点属性数据,而不将其转换为浮点数。
    在这种情况下,将使用glVertexAttribIPointer 函数,顶点属性应该在顶点着色器中声明为一种整数类型。

    1.7 在常量顶点属性和顶点数组之间选择

    应用程序可以让OpenGL ES使用常量顶点属性或者来自顶点数组的数据。
    下图描述了这在 OpenGL ES 3.0 中的实现方式。

    在这里插入图片描述
    glEnableVertexAttribArrayglDisableVertexAttribArray命令分别用于启用禁用通用顶点属性数组。
    如果某个通用属性索引的顶点属性数组被禁用,则将使用为该索引指定的常量顶点属性数组。

    void glDisableVertexAttribArray (GLuint index);
    
    void glEnableVertexAttribArray (GLuint index);
    
    • index
      指定通用顶点属性索引。这个值的范围从0到支持的最大顶点属性数量减1。
      在这里插入图片描述

    1.7.1 例3 使用常量和顶点数组属性

    例3 说明如何绘制一个三角形,该三角形的一个顶点属性是常量,其他属性用顶点数组指定。

    下面的代码可以在 https://github.com/ouyangpeng/OpenGLESDemo/ 中去查看源代码
    在这里插入图片描述

    • NativeTriangle2.h
    #pragma once
    #include "../../utils/GLUtils.h"
    namespace NAMESPACE_NativeTriangle2 {
    	class NativeTriangle {
    
    	public:
    		NativeTriangle();
    
    		~NativeTriangle();
    
    		void create();
    
    		void change(int width, int height);
    
    		void draw();
    
    	private:
    		GLuint mProgram;
    		int mWidth;
    		int mHeight;
    	};
    }
    
    • NativeTriangle2.cpp
    #include "NativeTriangle2.h"
    
    // 可以参考这篇讲解: https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
    namespace NAMESPACE_NativeTriangle2 {
    	// 顶点着色器
    	const char* VERTEX_SHADER_TRIANGLE =
    		"#version 300 es                          \n"
    		"layout(location = 0) in vec4 a_position; \n"
    		"layout(location = 1) in vec4 a_color;    \n"
    		"out vec4 v_color;                        \n"
    		"void main()                              \n"
    		"{                                        \n"
    		"   v_color = a_color;                    \n"
    		"   gl_Position = a_position;             \n"
    		"}                                        \n";
    
    	// 片段着色器
    	const char* FRAGMENT_SHADER_TRIANGLE =
    		"#version 300 es                              \n"
    		"precision mediump float;                     \n"
    		"in vec4 v_color;                             \n"
    		"out vec4 o_fragColor;                        \n"
    		"void main()                                  \n"
    		"{                                            \n"
    		"   o_fragColor = v_color;                    \n"
    		"}                                            \n";
    
    	// 我们在OpenGL中指定的所有坐标都是3D坐标(x、y和z)
    	// 由于我们希望渲染一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。
    	// 我们会将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float数组。
    	// https://learnopengl-cn.github.io/img/01/04/ndc.png
    
    	// https://developer.android.com/guide/topics/graphics/opengl#kotlin
    	// 在 OpenGL 中,形状的面是由三维空间中的三个或更多点定义的表面。
    	// 一个包含三个或更多三维点(在 OpenGL 中被称为顶点)的集合具有一个正面和一个背面。
    	// 如何知道哪一面为正面,哪一面为背面呢?这个问题问得好!答案与环绕(即您定义形状的点的方向)有关。
    	// 查看图片 : https://developer.android.com/images/opengl/ccw-winding.png
    	// 或者查看本地图片:Android_Java/Chapter_2/Hello_Triangle/ccw-winding.png
    	// 在此示例中,三角形的点按照使它们沿逆时针方向绘制的顺序定义。
    	// 这些坐标的绘制顺序定义了该形状的环绕方向。默认情况下,在 OpenGL 中,沿逆时针方向绘制的面为正面。
    	// 因此您看到的是该形状的正面(根据 OpenGL 解释),而另一面是背面。
    	//
    	// 知道形状的哪一面为正面为何如此重要呢?
    	// 答案与 OpenGL 的“面剔除”这一常用功能有关。
    	// 面剔除是 OpenGL 环境的一个选项,它允许渲染管道忽略(不计算或不绘制)形状的背面,从而节省时间和内存并缩短处理周期:
    	GLfloat vVertices[] = {
    		// 逆时针 三个顶点
    		0.0f, 0.5f, 0.0f,            // 上角
    		-0.5f, -0.5f, 0.0f,          // 左下角
    		0.5f, -0.5f, 0.0f            // 右下角
    	};
    
    	// 设置顶点的颜色值  这里设置成蓝色
    	GLfloat color[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
    
    
    	NativeTriangle::NativeTriangle() {
    
    	}
    
    	NativeTriangle::~NativeTriangle() {
    
    	}
    
    
    	void NativeTriangle::create() {
    		GLUtils::printGLInfo();
    
    		mProgram = GLUtils::createProgram(&VERTEX_SHADER_TRIANGLE, &FRAGMENT_SHADER_TRIANGLE);
    		if (!mProgram) {
    			LOGD("Could not create program");
    			return;
    		}
    		// 设置清除颜色
    		glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    	}
    
    
    	void NativeTriangle::draw() {
    		// Clear the color buffer
    		// 清除屏幕
    		// 在OpenGL ES中,绘图中涉及多种缓冲区类型:颜色、深度、模板。
    		// 这个例子,绘制三角形,只向颜色缓冲区中绘制图形。在每个帧的开始,我们用glClear函数清除颜色缓冲区
    		// 缓冲区将用glClearColor指定的颜色清除。
    		// 这个例子,我们调用了GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f); 因此屏幕清为白色。
    		// 清除颜色应该由应用程序在调用颜色缓冲区的glClear之前设置。
    		glClear(GL_COLOR_BUFFER_BIT);
    
    		// Use the program object
    		// 在glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用这个程序对象(也就是之前写的着色器)了。
    		// 当我们渲染一个物体时要使用着色器程序 , 将其设置为活动程序。这样就可以开始渲染了
    		glUseProgram(mProgram);
    
    		// Load the vertex data
    		//  顶点着色器允许我们指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时,
    		//  它还的确意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。
    		//  所以,我们必须在渲染前指定OpenGL该如何解释顶点数据。
    
    		//  我们的顶点缓冲数据会被解析为下面这样子:https://learnopengl-cn.github.io/img/01/04/vertex_attribute_pointer.png
    		//   . 位置数据被储存为32位(4字节)浮点值。
    		//   . 每个位置包含3个这样的值。
    		//   . 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
    		//   . 数据中第一个值在缓冲开始的位置。
    
    		// 有了这些信息我们就可以使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)了:
    		// Load the vertex data
    
    		//  指定通用顶点属性数组
    		// 第一个参数指定我们要配置的顶点属性。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
    		// 第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
    		// 第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
    		// 第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
    		// 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。我们设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。
    		//      一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,
    		//      (译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
    		// 最后一个参数的类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。
    		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
    
    		// 现在我们已经定义了OpenGL该如何解释顶点数据,
    		// 我们现在应该使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。
    		glEnableVertexAttribArray(0);
    
    		// Set the vertex color to red
    		// 设置顶点的颜色值
    		// 加载index指定的通用顶点属性,加载(x,y,z,w)
    		// opengl各个坐标系理解与转换公式 https://blog.csdn.net/grace_yi/article/details/109341926
    		// x,y,z,w:指的不是四维,其中w指的是缩放因子
    		// X轴为水平方向,Y轴为垂直方向,X和Y相互垂直
    		// Z轴同时垂直于X和Y轴。Z轴的实际意义代表着三维物体的深度
    		glVertexAttrib4fv(1, color);
    		// 相对于下面这句   这里设置成蓝色
    //    glVertexAttrib4f(1,0.0,0.0,1.0f,1.0f);
    
    		// glDrawArrays函数第一个参数是我们打算绘制的OpenGL图元的类型。我们希望绘制的是一个三角形,这里传递GL_TRIANGLES给它。
    		// 第二个参数指定了顶点数组的起始索引,我们这里填0。
    		// 最后一个参数指定我们打算绘制多少个顶点,这里是3(我们只从我们的数据中渲染一个三角形,它只有3个顶点长)。
    		//        public static final int GL_POINTS                                  = 0x0000;
    		//        public static final int GL_LINES                                   = 0x0001;
    		//        public static final int GL_LINE_LOOP                               = 0x0002;
    		//        public static final int GL_LINE_STRIP                              = 0x0003;
    		//        public static final int GL_TRIANGLES                               = 0x0004;
    		//        public static final int GL_TRIANGLE_STRIP                          = 0x0005;
    		//        public static final int GL_TRIANGLE_FAN                            = 0x0006;
    		glDrawArrays(GL_TRIANGLES, 0, 3);
    
    		// 禁用 通用顶点属性数组
    		glDisableVertexAttribArray(0);
    	}
    
    	void NativeTriangle::change(int width, int height) {
    		mWidth = width;
    		mHeight = height;
    		LOGD("change() width = %d , height = %d\n", width, height);
    
    		// Set the viewport
    		// 通知OpenGL ES 用于绘制的2D渲染表面的原点、宽度和高度。
    		// 在OpenGL ES 中,视口(Viewport) 定义所有OpenGL ES 渲染操作最终显示的2D矩形
    		// 视口(Viewport) 由原点坐标(x,y)和宽度(width) 、高度(height)定义。
    		glViewport(0, 0, mWidth, mHeight);
    	}
    
    }
    
    // ====================================================================
    
    NAMESPACE_NativeTriangle2::NativeTriangle* nativeTriangle2;
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_oyp_openglesdemo_triangle_HelloTriangle2NativeRenderer_nativeSurfaceCreate(
    	JNIEnv * env, jobject thiz) {
    
        if (nativeTriangle2) {
    		delete nativeTriangle2;
    		nativeTriangle2 = nullptr;
    	}
    	nativeTriangle2 = new NAMESPACE_NativeTriangle2::NativeTriangle();
    	nativeTriangle2->create();
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_oyp_openglesdemo_triangle_HelloTriangle2NativeRenderer_nativeSurfaceChange(
    	JNIEnv * env, jobject thiz, jint width, jint height) {
    	if (nativeTriangle2 != nullptr) {
    		nativeTriangle2->change(width, height);
    	}
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_oyp_openglesdemo_triangle_HelloTriangle2NativeRenderer_nativeDrawFrame(
    	JNIEnv * env, jobject thiz) {
    	if (nativeTriangle2 != nullptr) {
    		nativeTriangle2->draw();
    	}
    }
    

    代码示例中,

    • 使用的顶点属性 color 是一个常量,用glVertexAttrib4fv(1, color); 指定,不启用顶点属性数组1
    • vVertices属性用 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices); 以一个顶点数组指定,并用glEnableVertexAttribArray(0);启用数组0.
    • color值对于所绘制的三角形的所有顶点均相同,而vVertices属性对于三角形的各个顶点可以不同。

    二 、在顶点着色器中声明顶点属性变量

    我们已经知道顶点属性的定义,并且考虑了如何在OpenGL ES中指定顶点属性,现在讨论如何在顶点着色器中声明顶点属性。

    在顶点着色器中,变量通过使用in限定符声明为顶点属性。
    属性变量也可以选择包含一个布局限定符,提供属性索引。

    下面是几个顶点属性声明的示例:

    layout(location = 0) in vec4 a_position; 
    layout(location = 1) in vec4 a_color;    
    layout(location = 2) in vec2 a_texcoord;    
    layout(location = 3) in vec3 a_normal;    
    

    in限定符 只能用于以下数据类型:

    • float、vec2、vec3、vec4、
    • int、ivec2、ivec3、ivec4、
    • uint、uvec2、uvec3、uvec4
    • mat2 、mat2x3 、mat2x4 、mat3x2 、mat3、mat3x4 、mat4x2 、mat4x3 、mat4

    属性变量不能声明为数组或者结构。

    下面是无效顶点属性声明的例子,应该产生一个编译错误:

    in foo_t a_A;   //foo_t is a structure
    int vec4 a_B[10];
    

    在顶点着色器中声明为顶点属性的变量是只读变量,不能修改。
    下面代码将导致编译错误:

    in      vec4  a_pos;
    uniform vec4  u_v;
    
    void main()
    {
       a_pos = u_v;  // cannot assign to a_pos as it is read-only 
    }
    

    2.1 使用glGetProgramiv命令查询命令活动顶点属性数量

    属性可以在顶点着色器内部声明,但是如果没有使用,就不会被认为是活动属性,从而不会被计入限制。

    如果在顶点着色器中使用的属性数量大于GL_MAX_VERTEX_ATTRIBS,这个顶点着色器将无法链接。

    一旦程序链接成功,那么我们就需要找出连接到该程序的顶点着色器使用的活动顶点属性数量。
    注意,这一步骤只在你对属性不使用输入布局限定符时才有必要。在OpenGL ES 3.0中,建议使用布局限定符 ,这样你就没有必要事后查询这一信息。

    下面代码展示了如何获得活动顶点属性数量:

    在博客【我的OpenGL学习进阶之旅】程序对象 介绍过

    void glGetProgramiv (GLuint program, GLenum pname, GLint *params);
    

    2.2 使用glGetActiveAttrib命令查询活动顶点属性列表和它们的数据类型

    程序使用的活动顶点属性列表和它们的数据类型可以用glGetActiveAttrib命令查询。

    void glGetActiveAttrib (GLuint program, GLuint index, 
    						GLsizei bufSize, GLsizei *length,
    					 	GLint *size, GLenum *type, 
    					 	GLchar *name);
    
    

    参数说明:

    • program
      前面成功链接的程序对象名称

    • index
      指定需要查询的顶点属性,其值为0到GL_ACTIVIE_ATTRIBUTES -1GL_ACTIVIE_ATTRIBUTES 的值用glGetProgramiv确定。

    • bufSize
      指定可以写入name的最大字符数,包括Null终止符

    • length
      返回写入name的字符数,如果length不为NULL,则不含Null终止符

    • type
      返回属性类型,有效值为:

      • GL_FLOAT、GL_FLOAT_VEC2、GL_FLOAT_VEC3、GL_FLOAT_VEC4、
      • GL_FLOAT_MAT2、GL_FLOAT_MAT3、GL_FLOAT_MAT4、
      • GL_FLOAT_MAT2x3、GL_FLOAT_MAT2x4、
      • GL_FLOAT_MAT3x2、GL_FLOAT_MAT3x4、
      • GL_FLOAT_MAT4x2、GL_FLOAT_MAT4x3
      • GL_INT、GL_INT_VEC2、GL_INT_VEC3、GL_INT_VEC4、
      • GL_UNSIGNED_INT、GL_UNSIGNED_INT_VEC2、
      • GL_UNSIGNED_INT_VEC3、GL_UNSIGNED_INT_VEC4
    • size
      返回属性大小。这以type返回的类型单元格数量指定。如果数量不是一个数组,则size总数为1。如果变量是一个数组,则size返回数组的大小。

    • name
      顶点着色器中声明的属性变量名称

    三、将顶点属性绑定到顶点着色器中的属性变量

    前面我们讨论了顶点着色器中,顶点属性变量由in限定符指定,活动属性数量可以用glGetProgramiv 查询,程序中的活动属性列表可以用glGetActiveAttrib 查询。

    我们还讨论了如何使用 glVertexAttrib*glVertexAtrtribPointer 命令指定常量或者逐顶点(顶点数组)值。

    现在,我们考虑如何将这个通用属性索引映射到顶点着色器中声明的对于属性变量。这种映射使对应的顶点数据可以读入顶点着色器中正确的顶点属性变量。

    下图描述了 指定通用顶点属性和绑定到顶点着色器中的属性名称的方法。
    在这里插入图片描述

    3.1 将通用顶点属性索引映射到顶点着色器中的一个属性变量名称的3种方法

    在OpenGL ES 3.0中,可以使用3种方法将通用顶点属性索引映射到顶点着色器中的一个属性变量名称。
    这些方法可以分为如下几类:

    • 索引可以在顶点着色器源码中用 layout( location = N ) 限定符指定(推荐)。
    • OpenGL ES 3.0将通用顶点属性索引绑定到属性名称。
    • 应用程序可以将顶点属性索引绑定到属性名称。

    将属性绑定到一个位置最简单的方法是简单地使用 layout( location = N ) 限定符,这种方法需要的代码最少。
    但是,在某些情况下,其他两个选项可能更合适。

    3.2 glBindAttribLocation命令

    glBindAttribLocation命令可以用于将通用顶点属性索引绑定到顶点着色器中的一个属性变量。这种绑定在下一次程序链接时失效,不会改变当前链接的程序中使用的绑定。

    void glBindAttribLocation (GLuint program, GLuint index, const GLchar *name);
    
    

    在这里插入图片描述
    参数说明:

    • program
      程序对象名称
    • index
      通用顶点属性索引
    • name
      属性变量名称

    如果之前绑定了name,则它所指定的绑定被索引代替。glBindAttribLocation命令甚至可以在顶点着色器连接到程序对象之前调用,因此,这个调用可以用于绑定任何属性名称。不存在的属性名称或者连接到程序对象的顶点着色器中不活动的属性将被忽略。

    另一个选项是让OpenGL ES 3.0 将属性变量名称绑定到一个通用顶点属性索引。这种绑定在程序链接时进行。
    在链接阶段,OpenGL ES 3.0 实现为每个属性变量执行如下操作:

    对于每个属性变量,检查是否已经通过glBindAttribLocation命令指定了绑定。如果指定了一个绑定,则使用指定的对于属性索引。否则,OpenGL ES实现将分配一个通用顶点属性索引。

    这种分配特定于实现。也就是说,在一个OpenGL ES 3.0 实现中和在另一个实现中可能不同。

    3.2 glGetAttribLocation命令

    应用程序可以使用glGetAttribLocation命令查询分配的绑定

    GLint glGetAttribLocation (GLuint program, const GLchar *name);
    

    参数说明:

    • program
      程序对象名称

    • name
      属性变量名称

    glGetAttribLocation命令返回program定义的程序对象最后一次链接时绑定到属性变量name的通用属性索引。
    如果name不是一个活动属性变量,或者program不是一个有效的程序对象,或者没有链接成功,则返回-1,表示无效的属性索引。

    展开全文
    qq446282412 2021-11-17 21:45:43
  • 一、顶点数组对象VAO 通过前面的博客 【我的OpenGL学习进阶之旅】顶点属性、顶点数组 https://ouyangpeng.blog.csdn.net/article/details/121388737 【我的OpenGL学习进阶之旅】介绍顶点缓冲区对象VBO和元素数组...

    在这里插入图片描述

    一、顶点数组对象VAO

    通过前面的博客

    我们已经介绍了加载顶点属性的两种方式:

    • 使用客户顶点数组
    • 使用顶点缓冲区对象VBO
      顶点缓冲区对象VBO优于顶点数组,因为它们能够减少CPUGPU之间复制的数据量,从而获得更好的性能。

    OpenGL ES 3.0中 引入了一个新特性,使顶点数组的操作更加高效:顶点数组对象(VAO)
    正如前面的例子,我们可以发现: 使用顶点缓冲区对象VBO设置绘图操作可能需要多次调用glBindBufferglVertexAttribPointerglEnableVertexAttribArray

    为了更快地在顶点数组配置之间切换,OpenGL ES 3.0推出了顶点数组对象。VAO提供包含在顶点数组/顶点缓冲区对象配置之间切换所需要的所有状态的单一对象。

    在这里插入图片描述
    在这里插入图片描述

    二、VAO的相关API介绍

    2.1 glGenVertexArrays函数

    实际上,OpenGL ES 3.0中总是有一个活动的顶点数组对象。要创建新的顶点数组对象,可以使用glGenVertexArrays函数。

    void glGenVertexArrays (GLsizei n, GLuint *arrays);
    

    参数说明:

    • n
      要返回的顶点数组对象名称的数量
    • arrays
      指向一个n个缘故的数组的指针,该数组是分配的顶点数组对象返回的位置

    2.2 glBindVertexArray函数

    一旦创建,就可以使用glBindVertexArray函数绑定顶点数组对象供以后使用。

    void glBindVertexArray (GLuint array);
    

    参数说明:

    • array
      被指定为当前顶点数组对象的对象

    每个VAO都包含一个完整的状态向量,描述所有顶点缓冲区绑定和启用的顶点客户状态。
    绑定VAO时,它的状态向量提供缓冲区状态的当前设置。

    glBindVertexArray函数绑定顶点数组对象后,更改顶点数组状态的后续调用(glBindBufferglVertexAttribPointerglEnableVertexAttribArrayglDisableVertexAttribArray)将影响新的VAO。

    这样,应用程序可以通过绑定一个已经设置状态的顶点数组对象快速地在顶点数组配置之间切换。
    所有变化可以在一个函数调用中完成,没有必要多次调用以更改顶点数组状态。

    2.3 glDeleteVertexArrays函数

    当应用程序结束一个或者多个顶点数组对象的使用时,可以用glDeleteVertexArrays删除它们。

    void glDeleteVertexArrays (GLsizei n, const GLuint *arrays);
    

    参数说明:

    • n
      要删除的顶点数组对象名称的数量
    • arrays
      包含需要删除的顶点数组对象的有n个元素的数组

    三、实战一下

    下面的例子演示了顶点数组对象VAO在初始化时用于设置顶点数组状态。然后,在绘图的时候,使用 glBindVertexArray在一次函数调用中设置顶点数组状态。

    在这里插入图片描述

    • NativeTriangleVertextArrayObject.h
    #pragma once
    
    #include <BaseGLSample.h>
    
    #define VERTEX_POS_SIZE       3 // x, y and z
    #define VERTEX_COLOR_SIZE     4 // r, g, b, and a
    
    #define VERTEX_POS_INDX       0
    #define VERTEX_COLOR_INDX     1
    
    #define VERTEX_STRIDE         ( sizeof(GLfloat) * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE ) )
    
    class NativeTriangleVAO : public BaseGLSample {
    public:
        NativeTriangleVAO() = default;
    
        virtual ~NativeTriangleVAO() = default;
    
        virtual void create();
    
        virtual void draw();
    
        virtual void shutdown();
    
    private:
        // VertexBufferObject Ids
        GLuint vboIds[2];
    
        // VertexArrayObject Id
        /**
         * 顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。
         * 这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
         * 这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
         *
         * OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
         * 一个顶点数组对象会储存以下这些内容:
                glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
                通过glVertexAttribPointer设置的顶点属性配置。
                通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
         */
        GLuint vaoId;
    
    };
    
    
    
    • NativeTriangleVertextArrayObject.cpp
    #include <cstring>
    #include "NativeTriangleMapBuffers.h"
    #include "NativeTriangleVertextArrayObject.h"
    
    
    // 可以参考这篇讲解: https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
    // NDK OpenGL ES 3.0 开发(四):VBO、EBO 和 VAO https://blog.csdn.net/Kennethdroid/article/details/98088890
    
    //VBO 和 EBO
    //VBO(Vertex Buffer Object)是指顶点缓冲区对象,
    //而 EBO(Element Buffer Object)是指图元索引缓冲区对象,VAO 和 EBO 实际上是对同一类 Buffer 按照用途的不同称呼。
    
    // OpenGLES2.0 编程中,用于绘制的顶点数组数据首先保存在 CPU 内存,
    // 在调用 glDrawArrays 或者 glDrawElements 等进行绘制时,需要将顶点数组数据从 CPU 内存拷贝到显存。
    // 但是很多时候我们没必要每次绘制的时候都去进行内存拷贝,如果可以在显存中缓存这些数据,就可以在很大程度上降低内存拷贝带来的开销。
    
    // OpenGLES3.0 VBO 和 EBO 的出现就是为了解决这个问题。
    // VBO 和 EBO 的作用是在显存中提前开辟好一块内存,用于缓存顶点数据或者图元索引数据,
    // 从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以提升渲染性能,降低内存带宽和功耗。
    // OpenGLES3.0 支持两类缓冲区对象:顶点数组缓冲区对象、图元索引缓冲区对象。
    //    GL_ARRAY_BUFFER 标志指定的缓冲区对象用于保存顶点数组,
    //    GL_ELEMENT_ARRAY_BUFFER 标志指定的缓存区对象用于保存图元索引。
    // VBO 可以参考图片:docs/vertex_attribute_pointer_interleaved
    
    
    // VAO(Vertex Array Object)是指顶点数组对象,VAO 的主要作用是用于管理 VBO 或 EBO ,
    // 减少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 这些调用操作,高效地实现在顶点数组配置之间切换。
    // VAO 与 VBO、EBO之间的关系  可以参考图片: docs/vertex_array_objects.png 和 docs/vertex_array_objects_ebo.png
    
    
    // 3 vertices, with (x,y,z) ,(r, g, b, a)  per-vertex
    static GLfloat vertices[3 * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE)] =
            {
                    // 逆时针 三个顶点
                    0.0f, 0.5f, 0.0f,            // v0 上角
                    1.0f, 0.0f, 0.0f, 1.0f,      // c0
    
                    -0.5f, -0.5f, 0.0f,          // v1 左下角
                    0.0f, 1.0f, 0.0f, 1.0f,      // c1
    
                    0.5f, -0.5f, 0.0f,           // v2 右下角
                    0.0f, 0.0f, 1.0f, 1.0f       // c2
            };
    
    // Index buffer data
    static GLushort indices[3] = {0, 1, 2};
    
    void NativeTriangleVAO::create() {
        GLUtils::printGLInfo();
    
        // 顶点着色器
        VERTEX_SHADER = GLUtils::openTextFile(
                "vertex/vertex_shader_hello_triangle2.glsl");
        // 片段着色器
        FRAGMENT_SHADER = GLUtils::openTextFile(
                "fragment/fragment_shader_hello_triangle2.glsl");
        mProgram = GLUtils::createProgram(&VERTEX_SHADER, &FRAGMENT_SHADER);
    
        if (!mProgram) {
            LOGD("Could not create program")
            return;
        }
    
        //  Generate VBO Ids and load the VBOs with data
        // 创建 2 个 VBO(EBO 实际上跟 VBO 一样,只是按照用途的另一种称呼)
        glGenBuffers(2, vboIds);
    
        // 绑定第一个 VBO,拷贝顶点数组到显存
        // GL_STATIC_DRAW 标志标识缓冲区对象数据被修改一次,使用多次,用于绘制。
        glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
        // 绑定第二个 VBO(EBO),拷贝图元索引数据到显存
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
        // Generate VAO Id
        // 创建一个VAO
        glGenVertexArrays(1, &vaoId);
    
        // Bind the VAO and then setup the vertex attributes
        // 绑定VAO之后,操作 VBO ,当前 VAO 会记录 VBO 的操作
        // 要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。
        // 从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。
        // 当我们打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。
        glBindVertexArray(vaoId);
    
        // 把顶点数组复制到缓冲中供OpenGL使用
        glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
    
        glEnableVertexAttribArray(VERTEX_POS_INDX);
        glEnableVertexAttribArray(VERTEX_COLOR_INDX);
    
        // 设置顶点属性指针
        glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                              GL_FLOAT, GL_FALSE, VERTEX_STRIDE, nullptr);
    
        glVertexAttribPointer(VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE,
                              GL_FLOAT, GL_FALSE, VERTEX_STRIDE,
                              (const void *) (VERTEX_POS_SIZE * sizeof(GLfloat)));
    
        // Reset to the default VAO
        glBindVertexArray(0);
    
        // 设置清除颜色
        glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    }
    
    void NativeTriangleVAO::draw() {
        // Clear the color buffer
        // 清除屏幕
        // 在OpenGL ES中,绘图中涉及多种缓冲区类型:颜色、深度、模板。
        // 这个例子,绘制三角形,只向颜色缓冲区中绘制图形。在每个帧的开始,我们用glClear函数清除颜色缓冲区
        // 缓冲区将用glClearColor指定的颜色清除。
        // 这个例子,我们调用了GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f); 因此屏幕清为白色。
        // 清除颜色应该由应用程序在调用颜色缓冲区的glClear之前设置。
        glClear(GL_COLOR_BUFFER_BIT);
    
        // Use the program object
        // 在glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用这个程序对象(也就是之前写的着色器)了。
        // 当我们渲染一个物体时要使用着色器程序 , 将其设置为活动程序。这样就可以开始渲染了
        glUseProgram(mProgram);
    
        // Bind the VAO
        glBindVertexArray(vaoId);
    
        // Draw with the VAO settings
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr);
    
        // Return to the default VAO
        glBindVertexArray(0);
    }
    
    void NativeTriangleVAO::shutdown() {
        // Delete program object
        GLUtils::DeleteProgram(mProgram);
    
        glDeleteBuffers(2, &vboIds[0]);
        glDeleteVertexArrays(1, &vaoId);
    }
    
    • vertex/vertex_shader_hello_triangle2.glsl
    #version 300 es
    // 位置变量的属性位置值为 0
    layout(location = 0) in vec4 a_position;
    // 颜色变量的属性位置值为 1
    layout(location = 1) in vec4 a_color;
    // 向片段着色器输出一个颜色
    out vec4 v_color;
    
    void main()
    {
        v_color = a_color;
        gl_Position = a_position;
    }
    
    • fragment/fragment_shader_hello_triangle2.glsl
    #version 300 es
    // 表示OpenGL ES着色器语言V3.00
    
    // 声明着色器中浮点变量的默认精度
    precision mediump float;
    
    // 声明由上一步顶点着色器传入进来的颜色值
    in vec4 v_color;
    
    // 声明一个输出变量fragColor,这是一个4分量的向量,
    // 写入这个变量的值将被输出到颜色缓冲器
    out vec4 o_fragColor;
    
    void main()
    {
    	o_fragColor = v_color;
    }
    

    四、程序源码

    本例子可以在Github找到源代码 https://github.com/ouyangpeng/OpenGLESDemo

    展开全文
    qq446282412 2021-12-05 14:38:32
  • 使用顶点数据初始化 顶点缓冲对象,然后 顶点数组对象通过 顶点缓冲对象 获取 顶点数据(通过位置layout (location = 0)),把数据存放在 顶点数组对象的好处是 当配置好了顶点属性指针的时候,你只需要使用VAO即可 ...

    重要知识点:

    1,顶点数组对象:VAO

    2,顶点缓冲对象

    总结:
    使用顶点数据初始化 顶点缓冲对象,然后 顶点数组对象通过 顶点缓冲对象  获取 顶点数据(通过位置layout (location = 0)),把数据存放在 顶点数组对象的好处是  当配置好了顶点属性指针的时候,你只需要使用VAO即可 不需要再次去配置 顶点属性指针

    /**
    1,顶点数组对象:  VAO
    2,顶点缓冲对象:CBO
    3,索引缓冲对象:EBO
     
     图形渲染管线:就是把数据
     流程:
     1,顶点着色器:接受一组3D数据,转换成另外一个3D 数据
     2,图元着色器:接受顶点着色器的输出,然后装配成指定的图元形状
     3,几何着色器:接受图元着色器的输出,在这可以产生新的图元
     4,光栅化着色器:可以叫栅格化或像素化,就是把图元映射到屏幕上,变成像素,供片段着色器使用的片段。
     在运行片段着色器之前,会把屏幕之外的数据删除,以提高运行效率。这里说的片段是指 openGL 渲染一个像素 所需要的所有数据
     5,片段着色器:接受关栅化阶段的数据,计算出最终在屏幕像素上显示的颜色,包括关照、光影、光的颜色
     6,Alpha测试和混合(Blending)阶段:测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend
     
     顶点输入:
     第一:坐标
     1,标准化设备坐标:所有的坐标都是 3D,范围在  (-1,1)超过这个范围都会被舍弃
     2,屏幕空间坐标:标准化设备坐标是通过 glViewport 函数 进行视口变换,转换成屏幕空间坐标。屏幕空间坐标又会变换成片段  输入到片段着色器中
     第二:顶点输入步骤:
     1,创建用于存储的顶点数据
     2,配置OpenGL如何解释这些内存
     3,指定如何发送给显卡
     第三:顶点缓冲对象
     优势:可以缓冲大量数据,因cpu吧数据发送给显卡相对较慢,所以尽可能一次性发送尽可能多的数据,发送到显卡内存之后,顶点着色器可非常快的访问顶点数据
     */
    
    
    #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;
    
    //
    /**
    1,着色器的版本号  必须与 openGL 的版本号一至
    2, gl_Position : 预定义变量
     */
    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);
    #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: 加载所有的函数指针
        // ---------------------------------------
        if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
        {
            std::cout << "Failed to initialize GLAD" << std::endl;
            return -1;
        }
    
    
        // build and compile our shader program
        // ------------------------------------
        // 创建一个顶点着色器 对象
        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;
        //使用一个缓冲ID生成一个顶点数组对象
        glGenVertexArrays(1, &VAO);
        //使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:
        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);
        /**
         使用顶点数据初始化 顶点缓冲对象
         GL_STATIC_DRAW :数据不会或几乎不会改变。
         GL_DYNAMIC_DRAW:数据会被改变很多。
         GL_STREAM_DRAW :数据每次绘制时都会改变。
         */
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        /**
            设置顶点属性指针  =》 从顶点缓冲对象中 获取 顶点数据
            1,第一个参数,与顶点着色器 layout  location = 0  相对应  所以为 0
            2,第二参数:顶点数组每个数据都是一个vec3  总共三个  所以  是 3
            3,第三个参数:每个数据都是 float 类型    所以是  GL_FLOAT
            4,第四个参数: 数据是否需要标准化,标准化的时候 是:(-1,1)这里不需要标准化 (0,1) 所以是:GL_FALSE
            5,第五个参数:步长(Stride),它告诉我们在连续的顶点属性组之间的间隔,因为下组位置是在 3个float 之后  所以是  3 * sizeof(float)
            6,第六个参数:类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
            获取数据规则是:每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。由于在调用glVertexAttribPointer之前绑定的是先前定义的VBO对象,顶点属性0现在会链接到它的顶点数据。
         */
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
        //启用顶点数据,因起始位置是0  所以传 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
            /**
             1,第一个参数:图元的类型   三角形
             2,第二参数:顶点数组的起始索引,为 0
             3,第三个参数:总共绘制的顶点数:为3
             */
            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);
        //删除着色器程序
        glDeleteProgram(shaderProgram);
    
        // 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);
    }
    

    上一篇:openGL 第三篇:创建窗口

    下一篇:openGL 第五篇:创建一个三角形 (使用索引缓冲对象)

    展开全文
    z1067832450_ 2021-01-18 20:59:44
  • 上一章简单介绍了一下着色器的基础语法,复盘了一下第一章中讲到的两个着色器的代码。...简单回顾一下这一个点的数据传递:顶点着色器定义了一个顶点属性通过顶点数组的赋值形式(glVertexAttribPointer)将点的坐...

    上一章简单介绍了一下着色器的基础语法,复盘了一下第一章中讲到的两个着色器的代码。这一章主要讲述数据传递:Android 环境与顶点着色器的数据传递,顶点着色器与片元着色器的数据传递等。这篇文章还是以需求来驱动知识点的学习。

    三个点

    之前的demo,咱们是绘制了一个点。简单回顾一下这一个点的数据传递:

    顶点着色器定义了一个顶点属性

    通过顶点数组的赋值形式(glVertexAttribPointer)将点的坐标信息从Android环境传递到了顶点着色器

    顶点着色器告诉OpenGL ES要绘制的点的位置

    片元着色器告诉OpenGL ES绘制点的颜色(片元着色器中写死的色值)

    通过上边四步,咱们就可以在手机屏幕上指定位置绘制一个指定颜色的点了。那么现在需求升级,咱们需要绘制三个点。

    按着之前开发的思维惯性,很容易想到给顶点着色器定义三个顶点属性不就行了么。还真不行,在第一章中已经指出来了,顶点着色器只会负责一个顶点,也就是说不管你定义多少个顶点属性,用来控制OpenGL ES顶点位置的输出属性只有一个”gl_Position“

    #version 300 es

    layout (location = 0) in vec4 av_Position;

    void main() {

    gl_Position = av_Position;

    gl_PointSize = 10.0;

    }

    也就是你定义100个av_Position,但最终只会有一个赋值给gl_Position。除非你把100个值同时赋予gl_Position让gl_Position处于一个100个状态的叠加态(手动狗头🐶)。

    确定三个顶点位置的一步其实是告诉OpenGL ES你要绘制三个顶点,这个时候根据之前开发的思维惯性是不是觉得OpenGL ES应该有个setVertexCount的Api,哈 并没有。那么OpenGL ES是怎么确认顶点数量的呢,看回第一章的顶点属性赋值代码:

    private val POSITION_VERTEX = floatArrayOf(

    0.0f, 0.0f, 0.0f

    )

    init {

    vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)

    .order(ByteOrder.nativeOrder())

    .asFloatBuffer()

    .put(POSITION_VERTEX)

    vertexBuffer.position(0)

    }

    GLES30.glVertexAttribPointer(avPosition, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)

    其实是把一个float数组按照指定规格赋值给了顶点属性avPosition,glVertexAttribPointer方法的参数中,avPosition代表了顶点属性的位置索引,第二参数”3"则是代表了赋值数组的长度,也就是把float数组POSITION_VERTEX中三个元素装成一个数组赋值给顶点属性四维向量avPosition。

    这块其实就可以看出来一些问题的,我的POSITION_VERTEX数组就三个元素为什么还要指定传入的数组长度呢?盲生你发现了华点🤓🤓🤓🤓

    其实这就是OpenGL ES指定顶点个数的方式。OpenGL ES的顶点数量=给顶点属性赋值的数组总长度/顶点属性需要的数组长度

    上边代码的顶点数量就是POSITION_VERTEX.length/3 = 1,知道了这个三个点其实就很好画了,下边贴出关键代码

    private val POSITION_VERTEX = floatArrayOf(

    0.0f, 0.5f, 0.0f,

    -0.25f, -0.25f, 0.0f,

    0.25f, -0.25f, 0.0f

    )

    init {

    vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)

    .order(ByteOrder.nativeOrder())

    .asFloatBuffer()

    .put(POSITION_VERTEX)

    vertexBuffer.position(0)

    }

    override fun onDrawFrame(gl: GL10?) {

    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

    GLES30.glUseProgram(pointProgram)

    GLES30.glEnableVertexAttribArray(avPosition)

    GLES30.glVertexAttribPointer(avPosition, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)

    GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)

    GLES30.glDisableVertexAttribArray(avPosition)

    }

    补充一下,GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3),这一句话并不是指明了OpenGL ES有三个顶点,而是指明了屏幕上要绘制三个点,这个三个点是OpenGL ES中前三个顶点。

    看下手机绘制的效果

    18618eacce46?utm_campaign=haruki

    三个点.png

    标题中的顶点数组指的就是POSITION_VERTEX这个用来指定顶点属性的关键数组。顶点数组的意义则是更加高效、快捷的指定顶点属性的值。

    顶点数组指定每个顶点的属性,是保存在应用程序地址空间(OpenGL ES称为客户空间)的缓冲区。它们作为顶点缓冲对象的基础,提供指定顶点属性数据的一种高效、灵活的手段。顶点数组用glVertexAttribPointer或 glVertexAttribIPointer函数指定。

    自定义点的颜色

    现在咱们升级需求,画的点除了可以在Android层定义位置,咱们还要定义颜色。

    在现有的代码中可以发现点的颜色时在片元着色器写死的,再次根据开发的思维惯性,有顶点属性,是不是也有片元属性,然后用片元数组赋值不就完了嘛!

    18618eacce46?utm_campaign=haruki

    yougedidi.jpg

    哈,并没有片元属性,因为片元着色器的个数是不确定的。比如你画一个三角形,顶点三个,但是片元是三条线占用的像素数,最终生成多少个片元完全由OpenGL ES控制的。顶点数组的控制逻辑是需要明确顶点数量的,片元着色器显然不适用。

    片元着色器的变量传递一般用使用顶点着色器的输出变量。还记得第二章中介绍的in 和out 两个修饰符吗,也就是将顶点着色器的一个变量用out修饰,片元着色器中一个变量用in修饰,且两个变量同名,这个时候顶点着色器的值就能传递到片元着色器来了。咱们的需求也就变成了将顶点的坐标和位置传入顶点着色器。

    直接上代码,顶点着色器:

    #version 300 es

    layout (location = 0) in vec4 av_Position;

    layout (location = 1) in vec4 point_Color;

    out vec4 frag_point_Color;

    void main() {

    gl_Position = av_Position;

    frag_point_Color = point_Color;

    gl_PointSize = 10.0;

    }

    片元着色器

    #version 300 es

    precision mediump float;

    out vec4 fragColor;

    in vec4 frag_point_Color;

    void main() {

    fragColor = frag_point_Color;

    }

    可以看到顶点着色器其实是做了一个转发的操作,把通过顶点数组传下来的顶点属性用out修饰的frag_point_Color传到了片元着色器。这就是in 和 out 关键字的一个典型应用,只要记得in代表输入属性,out代表输出属性就可以了。然后再看kotlin代码

    var pointProgram = -1

    var vertexBuffer: FloatBuffer

    var avPosition = -1

    val POSITION_SIZE = 3

    val COLOR_SIZE = 3

    val VERTEX_ATTRIBUTES_SIZE = 4*(POSITION_SIZE+COLOR_SIZE)

    private val POSITION_VERTEX = floatArrayOf(

    0.0f, 0.5f, 0.0f, 0.0f, 0.1f, 1.0f,

    -0.25f, -0.25f, 0.0f, 1.0f, 0.1f, 1.0f,

    0.25f, -0.25f, 0.0f, 0.0f, 1.1f, 1.0f

    )

    init {

    vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)

    .order(ByteOrder.nativeOrder())

    .asFloatBuffer()

    .put(POSITION_VERTEX)

    vertexBuffer.position(0)

    }

    override fun onDrawFrame(gl: GL10?) {

    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

    GLES30.glUseProgram(pointProgram)

    GLES30.glEnableVertexAttribArray(avPosition)

    GLES30.glEnableVertexAttribArray(1)

    vertexBuffer.position(0)

    GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false,VERTEX_ATTRIBUTES_SIZE, vertexBuffer)

    vertexBuffer.position(3)

    GLES30.glVertexAttribPointer(1, COLOR_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer)

    GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)

    GLES30.glDisableVertexAttribArray(0)

    GLES30.glDisableVertexAttribArray(1)

    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {

    GLES30.glViewport(0, 0, width, height)

    }

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {

    pointProgram = ShaderUtil.loadProgramFromAssets(

    "vertex_point_1.glsl",

    "frag_point_1.glsl",

    context.resources)

    avPosition = GLES30.glGetAttribLocation(pointProgram, "av_Position")

    }

    这里的关注点主要在于怎样用一个数组给两个顶点属性赋值。

    现在咱们要处理的数据是分别给三个顶点着色器的两个属性传输一组值,两个属性分别是位置和颜色,位置需要用一组三个元素的float数组标识(x,y,z),颜色也是一样的(r,g,b)。所以现在咱们把一个顶点着色器中的两组数组糅合成一组数组,也就是用一个6个元素的数组同时来标识位置和颜色。

    18618eacce46?utm_campaign=haruki

    array.png

    所以生成了新的数组,每一行是一个顶点的数据,前三位代表点的位置,后三位是点的颜色

    private val POSITION_VERTEX = floatArrayOf(

    0.0f, 0.5f, 0.0f, 0.0f, 0.1f, 1.0f,

    -0.25f, -0.25f, 0.0f, 1.0f, 0.1f, 1.0f,

    0.25f, -0.25f, 0.0f, 0.0f, 1.1f, 1.0f

    )

    然后再看一下赋值的代码

    vertexBuffer.position(0)

    GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer)

    vertexBuffer.position(3)

    GLES30.glVertexAttribPointer(1, COLOR_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer)

    这种混合数组的赋值方式,关键就是告诉OpenGL ES指定每个顶点着色器所占用的数据总长度,以及这组数据中哪段数据对应着那个属性。

    结合上文对glVertexAttribPointer api的介绍,glVertexAttribPointer api中的第四个参数,就是用来描述顶点属性的总长度,也就是(三位位置数据+三位颜色数据)*(folat类型数据长度),告诉了OpenGL ES每组数据有六个元素,glVertexAttribPointer api的第二个参数,则表示了该顶点属性使用的数据个数(这里不用计算数据占位长度 ),vertexBuffer.position(0)这表示了赋值时数组的游标定位。

    好了介绍完基础知识在翻译上面的代码:

    游标定位到0

    给索引位置0的顶点属性(av_Position)赋值,需要数据长度为3,一个顶点着色器需要的完整数据总长度为24字节(6个float类型)

    OpenGL ES 就会从零个元素读起,把读到的前三个赋值给av_Position属性,然后跳过三个值,循环三次。

    游标定位到3

    给索引位置0的顶点属性(point_Color)赋值,需要数据长度为3,一个顶点着色器需要的完整数据总长度为24字节(6个float类型)

    OpenGL ES 就会从第四个元素开始读,把读到的前三个赋值给point_Color属性,然后跳过三个值,循环三次。

    这样就完成使用一个数组,给两个顶点属性赋值的过程。咱们现在看一下效果:

    18618eacce46?utm_campaign=haruki

    Screenshot_2020-09-17-16-55-32-1656217187.png

    ok,三个不同颜色的点就完成了。这个时候可能有的同学会问了,一个数组赋值有点麻烦我使用文章最上边的那种指定属性值的方式,分别用两个数组给两个属性赋值不可以吗?当然可以。用一组数组指定所有属性的方式叫做结构数组,多个数组指定多个属性的方式叫做数组结构........🤢🤢🤢🤢🤢🤢

    我们已经描述了两种最常用的顶点属性存储方法∶结构数组和数组结构。问题是,对于OpenGLES3.0硬件实现,哪种分配方法最高效?在大部分情况下,答案是结构数组(一个数组方式)。原因是,每个顶点的属性数据可以顺序方式读取,这最有可能造成高效的内存访问模式。使用结构数组的缺点在应用程序需要修改特定属性时变得很明显。如果顶点属性数据的一个子集需要修改(例如,纹理坐标),这将造成顶点缓冲区的跨距更新。当顶点缓冲区以缓冲区对象的形式提供时,需要重新加载整个顶点属性缓冲区。可以通过将动态的顶点属性保存在单独的缓冲区来避免这种效率低下的情况。

    顶点缓冲区对象(VBO)

    上边描述了如何用glVertexAttribPointer指定这3个顶点属性。注意,我们在此介绍如何使用客户端顶点数组,以便解释逐顶点数据指定的概念。我们建议应用程序使用顶点缓冲区对象,避免使用客户端顶点数组,以实现最佳性能。在OpenGL ES 3.0 中支持客户端顶点数组只是为了与OpenGLES 2.0兼容。在OpenGLES3.0中,总是建议使用顶点缓冲区对象。

    使用顶点数组指定的顶点数据保存在客户内存中。在进行绘图调用时,这些数据必须从客户内存复制到图形内存。但是,如果我们没有必要在每次绘图调用时都复制顶点数据,而是在图形内存中缓存这些数据,那就好得多了。这种方法可以显著地改进渲染性能,也会降低内存带宽和电力消耗需求,对于移动设备相当重要。这是顶点缓冲区对象发挥作用的地方。顶点缓冲区对象使OpenGL ES 3.0应用程序可以在高性能的图形内存中分配和缓存顶点数据,并从这个内存进行渲染,从而避免在每次绘制图元的时候重新发送数据。不仅是顶点数据,描述图元顶点索引、作为 glDrawElements 参数传递的元素索引也可以缓存。

    OpenGL ES 3.0支持两类缓冲区对象,用于指定顶点和图元数据∶数组缓冲区对象和元素数组缓冲区对象。GL_ARRAY_BUFFER标志指定的数组缓冲区对象用于创建保存顶点数据的缓冲区对象。GL_ELEMENT ARRAY BUFFER标志指定的元素数组缓冲区对象用于创建保存图元索引的缓冲区对象。

    简单介绍一下数组缓冲区对象使用方式。

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {

    pointProgram = ShaderUtil.loadProgramFromAssets(

    "vertex_point_1.glsl",

    "frag_point_1.glsl",

    context.resources

    )

    GLES30.glGenBuffers(1, vboIds, 0);

    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0])

    GLES30.glBufferData(

    GLES30.GL_ARRAY_BUFFER,

    POSITION_VERTEX.size * 4,

    vertexBuffer,

    GLES30.GL_STATIC_DRAW

    );

    GLES30.glEnableVertexAttribArray(0);

    GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, 0);

    GLES30.glEnableVertexAttribArray(1);

    GLES30.glVertexAttribPointer(1, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, POSITION_SIZE*4);

    }

    override fun onDrawFrame(gl: GL10?) {

    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

    GLES30.glUseProgram(pointProgram)

    GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3);

    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);

    }

    我们可以看到其实和顶点数组方式基本流程差不多,只不过是多了一个生成缓冲区对象,并绑定的流程。有一点需要注意的是glVertexAttribPointer api中最后一个参数变成了一个int型变量,他设置了缓冲区数据偏移量,作用和顶点数组的数组结构方式中的vertexBuffer.position(3) 效果是一样的。还有一个需要注意的是这个偏移量的单位是字节,也就是数据偏移量*数据类型所占字节数。

    顶点数组对象(VAO)

    除了顶点缓冲区对象,OPENGL ES还提供了一种性能更高的方式∶顶点数组对象(VAO)。

    正如我们已经看到的,使用顶点缓冲区对象设置绘图操作可能需要多次调用glBindBuffr、glVertexAtribPointer和glEnableVertexAtribAray。为了更快地在顶点数组配置之间切换,OpenGL ES3.0推出了顶点数组对象。VAO提供包含在顶点数组/顶点缓冲区对象配置之间切换所需要的所有状态的单一对象。

    实际上,OpenGL ES3.0中总是有一个活动的顶点数组对象。本章目前为止的所有例子都在默认的顶点数组对象上操作(默认VAO的ID为0)。要创建新的顶点数组对象,可以使用 glGen VertexArays 函数。

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {

    pointProgram = ShaderUtil.loadProgramFromAssets(

    "vertex_point_1.glsl",

    "frag_point_1.glsl",

    context.resources

    )

    GLES30.glGenBuffers(1, vaoIds, 0);

    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vaoIds[0])

    GLES30.glBufferData(

    GLES30.GL_ARRAY_BUFFER,

    POSITION_VERTEX.size * 4,

    vertexBuffer,

    GLES30.GL_STATIC_DRAW

    );

    GLES30.glGenVertexArrays(1, vaoIds, 0)

    GLES30.glBindVertexArray(vaoIds[0])

    GLES30.glGenBuffers(1, vboIds, 0)

    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0])

    GLES30.glBufferData(

    GLES30.GL_ARRAY_BUFFER,

    POSITION_VERTEX.size * 4,

    vertexBuffer,

    GLES30.GL_STATIC_DRAW

    )

    GLES30.glEnableVertexAttribArray(0);

    GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, 0);

    GLES30.glEnableVertexAttribArray(1);

    GLES30.glVertexAttribPointer(1, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, POSITION_SIZE*4);

    GLES30.glBindVertexArray(0)

    }

    override fun onDrawFrame(gl: GL10?) {

    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

    GLES30.glUseProgram(pointProgram)

    GLES30.glBindVertexArray(vaoIds[0])

    GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)

    GLES30.glBindVertexArray(0)

    }

    可以看到使用方式上和顶点缓冲区对象是十分相似的,只是又多了一个顶点数组对象。

    常量顶点属性和统一变量

    上文介绍了OPENGL ES中最常用的指定顶点数组属性的方式。现在呢咱们把上文的需求稍微再改变一下,现在不是绘制三个不同位置不同颜色的点了,而是三个不同位置相同颜色的点。是不是感觉需求变简单了,新的需求只是上文中需求的一种特殊情况。所以用上面说的任何一种方式都可以完成这个需求的。只不过是顶点数组的原始数据变化一下:

    private val POSITION_VERTEX = floatArrayOf(

    0.0f, 0.5f, 0.0f, 0.1f, 0.1f, 1.0f,

    -0.25f, -0.25f, 0.0f, 0.1f, 0.1f, 1.0f,

    0.25f, -0.25f, 0.0f, 0.1f, 0.1f, 1.0f

    )

    代表颜色的数据全部变成了一种颜色。

    现在咱们是绘制三个点,假如是三十个、三百个呢,相信大家都可以看出来这种方式会重复大量相同数值的数据。所以这个时候大家就需要一种新的赋值方式。也就是给所有着色器中统一值变量赋值。这种赋值方式有两种:常量顶点属性和统一变量。

    常量顶点属性

    常量顶点属性对于一个图元的所有顶点都相同,所以对一个图元的所有顶点只需指定一个值。可以用如下任何一个函数指定∶

    void glvertexAttriblf (int index, float x);

    void glVertexAttrib2f(int index, float x, float y);

    void givertexattrib3f(int index, float x, float y, float z);

    void g1Vertexλttrib4f(int index, float x, float y, float z, float w);

    void glVertexttriblfv(int index, float[] values, int offset);

    void glVertexAttrib2fv(int index, float[] values, int offset);

    void glVertexAttrib3fv(int index, float[] values, int offset);

    void g1Vertexttrib4fv(int index, float[] values, int offset);

    gIVerexAtrib*命令用于加载index指定的通用顶点属性。glVertexAtriblf和glVertexAtriblfv 函数在通用顶点属性中加载(x,0.0,0.0,1.0)。glVerexAtrib2f和glVertexAtrib2v在通用顶点属性中加载(x,y,0.0,1.0)。glVertexAtri3f和glVertexAtrb3fv在通用顶点属性中加载(x,y,z,1.0)。glVertexAtrib4f和glVertexAtrib4fv在通用顶点属性中加载(x,y,z,w)。在实践中,常量顶点属性提供与使用标量/向量统一变量等价的功能,两者都是可以接受的选择。

    统一变量

    OpenGL ES着色语言中的变量类型限定符之一是统一变量。统一变量存储应用程序通过OpenGL ES3.0API传入着色器的只读值,对于保存着色器所需的所有数据类型(如变换矩阵、照明参数和颜色)都很有用。本质上,一个着色器的任何参数在所有顶点或者片段中都应该以统一变量的形式传入。在编译时已知值的变量应该是常量,而不是统一变量,这样可以提高效率。

    统一变量在全局作用域中声明,只需要统一限定符。下面是统一变量的一些例子∶

    uniform mat4 viewMatrix;

    uniform vec3 lightPosition;

    效果上统一变量和常量顶点属性基本上是差不多的,区别是同一变量是支持片元着色器。需要注意的是,统一变量的命名空间在顶点着色器和片段着色器中都是共享的。也就是说,如果顶点和片段着色器一起链接到一个程序对象,它们就会共享同一组统一变量。因此,如果在顶点着色器和片段着色器中都声明一个统一变量,那么两个声明必须匹配。应用程序通过API加载统一变量时,它的值在顶点和片段着色器中都可用。

    上边简单介绍了一下两种全局统一变量的赋值方式,下边是实际代码应用。

    顶点着色器:

    #version 300 es

    layout (location = 0) in vec4 av_Position;

    layout (location = 1) in vec4 point_Size;

    void main() {

    gl_Position = av_Position;

    gl_PointSize = 10.0;

    }

    片元着色器

    #version 300 es

    precision mediump float;

    out vec4 fragColor;

    uniform vec4 frag_point_Color;

    void main() {

    fragColor = frag_point_Color;

    }

    可以看到在之前的demo中固定大小的画笔这次变成了一个顶点属性,片元着色器的颜色值则变成了一个统一变量。同一变量基本使用形式类似顶点属性,有一套相应的Api,唯一不同的是同一变量不能用layout修饰符修饰,也就是只能用api来获取统一变量的index值。

    kotlin代码:

    class ThreePointColorUniformRenderer(var context: Context) : GLSurfaceView.Renderer {

    var pointProgram = -1

    var vertexBuffer: FloatBuffer

    var fragPointColorIndex = -1

    val POSITION_SIZE = 3

    val COLOR_SIZE = 3

    val VERTEX_ATTRIBUTES_SIZE = 4 * (POSITION_SIZE + COLOR_SIZE)

    private val POSITION_VERTEX = floatArrayOf(

    0.0f, 0.5f, 0.0f,

    -0.25f, -0.25f, 0.0f,

    0.25f, -0.25f, 0.0f

    )

    init {

    vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)

    .order(ByteOrder.nativeOrder())

    .asFloatBuffer()

    .put(POSITION_VERTEX)

    vertexBuffer.position(0)

    }

    override fun onDrawFrame(gl: GL10?) {

    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

    GLES30.glUseProgram(pointProgram)

    GLES30.glEnableVertexAttribArray(0)

    GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)

    GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)

    GLES30.glDisableVertexAttribArray(0)

    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {

    GLES30.glViewport(0, 0, width, height)

    }

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {

    pointProgram = ShaderUtil.loadProgramFromAssets(

    "vertex_point_2.glsl",

    "frag_point_2.glsl",

    context.resources

    )

    GLES30.glUseProgram(pointProgram)

    fragPointColorIndex = GLES30.glGetUniformLocation(pointProgram, "frag_point_Color")

    GLES30.glUniform4fv(fragPointColorIndex, 1, floatArrayOf(1.0f, 0.5f, 0.1f, 1.0f), 0)

    GLES30.glVertexAttrib1f(1, 20.0f)

    }

    }

    总结

    这一章主要是详细讲述了从应用层把参数值传递到OPENGL ES工作环境的几种方式。我在刚学这一块的时候书本上直接把这几种方式罗列了出来,也没有系统的讲解这几种方式的应用场景,我当时是看的一脸懵逼。在本章教程中从需求作为出发点展示了几种传值方式的异同。希望可以更好地帮到想学习OPENGL ES的同学们。然后就是OPENGL ES的api的设计方式有浓烈的C语言风格,写习惯了java代码的同学肯定是适应不过来的。java代码可能调用一个对象的一个方法就能方法完成的工作,在OPENGL ES中一般来说都是需要调用gen系列的api生成一个id,然后使用bind api绑定id,也就是确定调用对象,然后才是调用相关的方法api,最后还得解绑这个id。这个还是需要多多练习来适应。

    18618eacce46?utm_campaign=haruki

    swdt.png

    一般来讲,每个顶点着色器都不一样的顶点属性,比如点位置可以使用顶点数组赋值,当点的位置恒定时就可以使用顶点缓冲区或者顶点数组对象方式来优化性能。当处理每个着色器都是统一值的属性时,比如说当所有点的颜色都一样时则需要考虑使用常量顶点属性或者统一变量的形式来进行赋值,这两个在使用上的区别则是是否支持片元着色器。

    本章内容到这基本就结束了,下章会开始纹理部分的相关知识。

    展开全文
    weixin_39567870 2021-06-03 16:50:13
  • weixin_39758696 2021-06-07 08:17:36
  • weixin_31034309 2021-06-10 05:32:18
  • m0_47652477 2021-04-18 10:42:33
  • weixin_45961774 2021-01-16 16:26:20
  • weixin_39900206 2021-03-17 13:20:31
  • Blackoutdragon 2020-12-19 17:04:46
  • zgsdlr 2021-11-12 16:50:35
  • weixin_46127065 2021-10-10 14:54:48
  • weixin_30363285 2021-02-07 01:29:17
  • weixin_29503445 2021-05-22 18:53:36
  • weixin_39858124 2021-04-19 07:04:27
  • qq_54505761 2021-12-06 19:38:59
  • weixin_33268464 2021-05-19 04:08:30
  • Ezicat 2021-06-08 09:20:46
  • chengqiuming 2021-04-07 17:42:50
  • uncle_ll 2021-10-14 23:47:28
  • weixin_42510262 2021-02-25 18:53:03
  • Jason_Lee155 2021-11-08 21:25:37
  • rebortt 2021-09-18 09:41:25
  • qq_52252193 2021-11-13 18:02:58
  • u014535072 2021-04-04 12:34:40
  • SeeDoubleU 2021-07-21 23:31:47
  • SSYLDBDSY 2021-01-02 21:10:10
  • weixin_39750731 2021-05-28 08:48:56

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 88,849
精华内容 35,539
关键字:

顶点数组