Vertex data, also known as vertex attributes, specifies data for each vertex. This per-vertex data can be specified per vertex, or constant for all vertices.
For example, if you want to draw a triangle with a fixed color (as in the image below, assuming the color is black), you can specify a constant value for all 3 vertices of the triangle. However, the positions of the 3 vertices that make up the triangle are different, so we have to specify a vertex array to store the 3 position values.
1. Specify vertex attribute data
Vertex attribute data can be specified per vertex using a vertex array, or a constant value can be used for all vertices of a primitive.
All OpenGL ES 3.0 implementations must support a minimum of 16 vertex attributes. Applications can query the exact number of vertex attributes supported by a particular implementation.
The following code shows how an application can query the OpenGL ES 3.0 implementation for the number of vertex attributes that are actually supported,
GLint maxVertexAttribs;// n will be >= 16 glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
1.1 Constant vertex attributes
Constant vertex attributes are the same for all vertices of a primitive, so only one value needs to be specified for all vertices of a primitive.
It can be specified with any of the following functions:
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);
The glVertexAttrib* commands are used to load generic attributes specified by index.
- glVertexAttrib1f and glVertexAttrib1fv functions load in generic vertex attributes (x, 0.0, 0.0, 1.0)
- glVertexAttrib2f and glVertexAttrib2fv functions load in generic vertex attributes (x, y, 0.0, 1.0)
- glVertexAttrib3f and glVertexAttrib3fv functions load in generic vertex attributes (x, y, z, 1.0)
- glVertexAttrib4f and glVertexAttrib4fv functions load in generic vertex attributes (x, y, z, w)
In practice, constant vertex attributes provide equivalent functionality to using scalar/vector uniform variables, and both are acceptable choices.
1.2 Vertex Array
Vertex arrays specify attributes for each vertex and are buffers that are stored in the application's address space (called client space in OpenGL ES).
They serve as the basis for vertex buffer objects, providing an efficient and flexible means of specifying vertex attribute data.
Vertex arrays are specified with the glVertexAttribPointer or glVertexAttribIPointer functions.
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);
Parameter Description:
-
index
Specifies a generic vertex attribute index. This value ranges from 0 to the maximum number of vertex attributes supported - 1. -
size
The number of components in the vertex array specified for the vertex attribute referenced by the index. Valid values are 1~4 -
type
Data Format. Valid values included for both functions are:#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
Valid values for glVertexAttribPointer also include:
#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
The glVertexAttribPointer function only, used to identify whether non-floating-point data formats should be normalized when converted to floating-point values. For glVertexAttribIPointer, these values are treated as integers -
stride
Each vertex is stored sequentially by the vertex attribute components specified by size. stride specifies the displacement between the vertex index I and the vertex data represented by (I+1). If stride is 0, attribute data for each vertex is stored sequentially. If stride is greater than 0, use that value as the stride to get the vertex data represented by the next index. -
pointer
If using a client-side vertex array, a pointer to a buffer that holds vertex attribute data. If a vertex buffer object is used, the offset within that buffer.
Next, we present a few examples of how to specify vertex attributes with the glVertexAttribPointer function.
1.3 There are two common methods for allocating and storing vertex attribute data
There are two common methods of allocating and storing vertex attribute data:
- array of structures
Array of Structures: Stores vertex attributes in a buffer. The structure represents all the attributes of a vertex, each vertex consists of an array of attributes. - array structure
Array structure: holds each vertex attribute in a separate buffer.
Assume that each vertex has 4 vertex attributes: position, normal and two texture coordinates. These attributes are kept together in one buffer allocated for all vertices.
- Vertex Position Properties: Specified as a vector of 3 floats (x, y, z).
- Vertex Normals: Also specified as a vector of 3 floats (x, y, z).
- Each texture coordinate: Specified as a vector (s, t) of 2 floats.
The figure below shows the memory layout of this buffer.
In this example, the stride of the buffer is the total size of all the attributes that make up the vertex. (A vertex is equal to 10 floats or 40 bytes: 12 bytes for position, 12 bytes for normal, 8 bytes for Tex0, 8 bytes for Tex1)
Example 1 below describes how to specify these four vertex attributes with glVertexAttribPointer.
Note: The use of client-side vertex arrays is described here in order to explain the concept of per-vertex data specification.
However, we recommend that applications use vertex buffer objects and avoid client-side vertex arrays for best performance.
- Client-side vertex arrays are supported in OpenGL ES 3.0 only for compatibility with OpenGL ES 2.0.
- In OpenGL ES 3.0, vertex buffer objects are always recommended.
1.3.1 Example 1: Array of Structures
// // 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 Example 2: Array structure
// // 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 How to store different attributes of vertices
1.4.1 Structure array and array structure, which allocation method is more efficient?
We have described two common vertex attribute storage methods: struct array and array struct.
The question is, which allocation method is more efficient for OpenGL ES 3.0 hardware implementation? In most cases, the answer is an array of structures.
The reason is that attribute data for each vertex can be read in a sequential manner, which most likely results in an efficient memory access pattern.
1.4.2 Disadvantages of using arrays of structures
The disadvantage of using an array of structures becomes apparent when an application needs to modify certain properties. If a subset of vertex attribute data needs to be modified (for example, texture coordinates), this will cause a stride update of the vertex buffer. When the vertex buffer is provided as a buffer object, the entire vertex attribute buffer needs to be reloaded.
- How to solve?
This inefficiency can be avoided by keeping dynamic vertex attributes in separate buffers.
1.5 Which data format is used for vertex attributes
The vertex attribute data format specified with the type parameter in glVertexAttribPointer affects not only the graphics memory storage requirements for vertex attribute data, but also the overall performance, which is a function of the memory bandwidth required to render the frame.
The smaller the data space occupation, the smaller the memory bandwidth required.
OpenGL ES 3.0 supports a 16-bit floating point vertex format named GL_HALF_FLOAT. It is recommended to use GL_HALF_FLOAT in applications whenever possible.
- Texture coordinates, normals, binormals, tangent vectors, etc. are all candidates for storing each component using GL_HALF_FLOAT.
- Colors can be stored as GL_UNSIGNED_BYTE with 4 components per vertex color.
- It is recommended to use GL_HALF_FLOAT to store vertex positions, but have found this option to be infeasible in many cases. For these cases, vertex positions can be stored as GL_FLOAT
1.6 How the normalization flag in glVertexAttribPointer works
Vertex attributes are stored internally as single-precision floating point numbers before being used in vertex shaders. If the data type represents a vertex attribute with many floats, the vertex attribute will be converted to single-precision float before being used in the vertex shader.
The normalization flag controls the conversion of non-floating point vertex attribute data to single-precision floating-point values.
- If the normalize flag is false, vertex data is converted directly to floating point values.
This is similar to converting a variable of non-floating type to a floating-point variable. The following code provides an example:
GLfloat f; GLbyte b; f = (GLfloat) b; // f represents values in the range [ -128.0 , 127.0 ]
- If the normalization flag is true, and if the data type is GL_BYTE, GL_SHORT, or GL_FIXED, then vertex data is mapped in the range [ -1.0, 1.0 ]. If the data type is GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT, it is mapped to the range [ 0.0, 1.0 ].
The following table illustrates the conversion of non-floating-point data types when the normalization flag is set. The c value in column 2 of the table refers to a value in the specified format in column 1.
In vertex shaders, it is also possible to access integer vertex attribute data as integers without converting it to floating point.
In this case the glVertexAttribIPointer function will be used and the vertex attribute should be declared as an integer type in the vertex shader.
1.7 Choosing between constant vertex attributes and vertex arrays
Applications can have OpenGL ES use constant vertex attributes or data from vertex arrays.
The diagram below describes how this is implemented in OpenGL ES 3.0.
The glEnableVertexAttribArray and glDisableVertexAttribArray commands enable and disable the generic vertex attribute array, respectively.
If vertex attribute arrays for a generic attribute index are disabled, the constant vertex attribute array specified for that index will be used.
void glDisableVertexAttribArray (GLuint index); void glEnableVertexAttribArray (GLuint index);
- index
Specifies a generic vertex attribute index. This value ranges from 0 to the maximum number of vertex attributes supported minus 1.
1.7.1 Example 3 Using Constants and Vertex Array Properties
Example 3 shows how to draw a triangle whose one vertex attribute is constant and the other attributes are specified using vertex arrays.
The following code can be found in GitHub - ouyangpeng/OpenGLESDemo: Android OpenGL ES 3.0 hands-on learning Demo to view the source code
- 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" // You can refer to this explanation: https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ namespace NAMESPACE_NativeTriangle2 { // vertex shader 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"; // fragment shader 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"; // All the coordinates we specify in OpenGL are 3D coordinates (x, y and z) // Since we want to render a triangle, we need to specify a total of three vertices, each with a 3D position. // We will define them as an array of float s in normalized device coordinates (the visible area of OpenGL). // https://learnopengl-cn.github.io/img/01/04/ndc.png // https://developer.android.com/guide/topics/graphics/opengl#kotlin // In OpenGL, a face of a shape is a surface defined by three or more points in three-dimensional space. // A collection of three or more three-dimensional points (called vertices in OpenGL) has a front face and a back face. // How do you know which side is the front and which is the back? Good question! The answer has to do with wrapping (i.e. the orientation of the points where you define the shape). // View image: https://developer.android.com/images/opengl/ccw-winding.png // Or view the local image: Android_Java/Chapter_2/Hello_Triangle/ccw-winding.png // In this example, the points of the triangle are defined in the order that they are drawn counterclockwise. // The order in which these coordinates are drawn defines the wrapping direction of the shape. By default, in OpenGL, faces drawn counterclockwise are front faces. // So what you see is the front side of the shape (as interpreted by OpenGL), and the other side is the back side. // // Why is it so important to know which side of a shape is the front? // The answer has to do with a common feature of OpenGL's "face culling". // Face culling is an option of the OpenGL environment that allows the rendering pipeline to ignore (not compute or draw) the back faces of shapes, saving time and memory and reducing processing cycles: GLfloat vVertices[] = { // Counterclockwise three vertices 0.0f, 0.5f, 0.0f, // upper corner -0.5f, -0.5f, 0.0f, // bottom left 0.5f, -0.5f, 0.0f // bottom right }; // Set the color value of the vertex here is set to blue 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; } // set clear color glClearColor(1.0f, 1.0f, 1.0f, 0.0f); } void NativeTriangle::draw() { // Clear the color buffer // clear screen // In OpenGL ES, there are multiple buffer types involved in drawing: color, depth, stencil. // This example, which draws the triangle, only draws the graphics to the color buffer. At the beginning of each frame, we clear the color buffer with the glClear function // The buffer will be cleared with the color specified by glClearColor. // In this example, we call GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f); so the screen is cleared to white. // The clear color should be set by the application before calling glClear on the color buffer. glClear(GL_COLOR_BUFFER_BIT); // Use the program object // After the glUseProgram function call, every shader call and render call will use this program object (that is, the shader written earlier). // To use a shader program when we render an object, set it as the active program. This will start rendering glUseProgram(mProgram); // Load the vertex data // Vertex shaders allow us to specify any input in the form of vertex attributes. While this makes it highly flexible, // It does mean that we have to manually specify which part of the input data corresponds to which vertex attribute of the vertex shader. // So, we have to specify how OpenGL should interpret vertex data before rendering. // Our vertex buffer data will be parsed like this: https://learnopengl-cn.github.io/img/01/04/vertex_attribute_pointer.png // . Position data is stored as a 32-bit (4-byte) floating point value. // . Each position contains 3 such values. // . There are no gaps (or other values) between these 3 values. These values are tightly packed in the array (Tightly Packed). // . The first value in the data is at the beginning of the buffer. // With this information we can use the glVertexAttribPointer function to tell OpenGL how to parse the vertex data (applied to per-vertex attributes): // Load the vertex data // Specifies an array of generic vertex attributes // The first parameter specifies the vertex attribute we want to configure. Because we want to pass data into this one vertex attribute, here we pass in 0. // The second parameter specifies the size of the vertex attribute. The vertex attribute is a vec3 which consists of 3 values, so the size is 3. // The third parameter specifies the type of data, here is GL_FLOAT (vec* in GLSL are all composed of floating point values). // The fourth parameter defines whether we want the data to be normalized (Normalize). If we set it to GL_TRUE, all data will be mapped between 0 (-1 for signed data) and 1. We set it to GL_FALSE. // The fifth parameter is called the stride and it tells us the interval between successive sets of vertex attributes. We set it to 0 to let OpenGL decide what the exact step size is (only available if the values are closely packed). // Once we have more vertex attributes, we have to be more careful to define the spacing between each vertex attribute, // (Annotation: The meaning of this parameter is simply how many bytes are between the second occurrence of this attribute and the 0 position of the entire array). // The type of the last parameter is void*, so we need to do this weird cast. It represents the offset (Offset) of the starting position of the position data in the buffer. glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices); // Now that we have defined how OpenGL interprets vertex data, // We should now use glEnableVertexAttribArray with the vertex attribute position value as a parameter to enable vertex attributes; vertex attributes are disabled by default. glEnableVertexAttribArray(0); // Set the vertex color to red // Set vertex color value // Load the generic vertex attribute specified by index, load (x,y,z,w) // opengl coordinate system understanding and conversion formula https://blog.csdn.net/grace_yi/article/details/109341926 // x, y, z, w: refers to not four dimensions, where w refers to the scaling factor // The X axis is the horizontal direction, the Y axis is the vertical direction, and X and Y are perpendicular to each other // The Z axis is perpendicular to both the X and Y axes. The actual meaning of the Z axis represents the depth of the three-dimensional object glVertexAttrib4fv(1, color); // Compared with the following sentence, it is set to blue here // glVertexAttrib4f(1,0.0,0.0,1.0f,1.0f); // The first parameter of the glDrawArrays function is the type of OpenGL primitive we intend to draw. What we want to draw is a triangle, here we pass GL_TRIANGLES to it. // The second parameter specifies the starting index of the vertex array, we fill in 0 here. // The last parameter specifies how many vertices we intend to draw, here 3 (we are only rendering a triangle from our data, which is only 3 vertices long). // 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); // Disable Generic Vertex Attribute Array 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 // Notifies OpenGL ES of the origin, width, and height of the 2D rendering surface used for drawing. // In OpenGL ES, the Viewport defines the 2D rectangle that all OpenGL ES rendering operations ultimately display // Viewport is defined by origin coordinates (x,y) and width (width) and height (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(); } }
In the code example,
- The vertex attribute color used is a constant, specified with glVertexAttrib4fv(1, color);, vertex attribute array 1 is not enabled
- The vVertices attribute is specified as a vertex array with glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices); and enables array 0 with glEnableVertexAttribArray(0);.
- The color value is the same for all vertices of the drawn triangle, whereas the vVertices property can be different for each vertex of the triangle.
2. Declare vertex attribute variables in the vertex shader
Now that we know the definition of vertex attributes, and considering how to specify vertex attributes in OpenGL ES, we now discuss how to declare vertex attributes in vertex shaders.
In a vertex shader, variables are declared as vertex attributes by using the in qualifier.
The property variable can also optionally contain a layout qualifier that provides the property index.
Here are a few examples of vertex attribute declarations:
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;
The in qualifier can only be used with the following data types:
- float,vec2,vec3,vec4,
- int,ivec2,ivec3,ivec4,
- uint,uvec2,uvec3,uvec4
- mat2 ,mat2x3 ,mat2x4 ,mat3x2 ,mat3,mat3x4 ,mat4x2 ,mat4x3 ,mat4
Property variables cannot be declared as arrays or structures.
The following is an example of an invalid vertex attribute declaration that should generate a compile error:
in foo_t a_A; //foo_t is a structure int vec4 a_B[10];
Variables declared as vertex attributes in a vertex shader are read-only and cannot be modified.
The following code will result in a compilation error:
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 Use the glGetProgramiv command to query the number of active vertex attributes
Attributes can be declared inside a vertex shader, but if they are not used, they are not considered active attributes and thus do not count towards the limit.
If the number of attributes used in a vertex shader is greater than GL_MAX_VERTEX_ATTRIBS, the vertex shader will fail to link.
Once the program is linked, then we need to find out the number of active vertex attributes used by the vertex shader connected to the program.
Note that this step is only necessary if you do not use input layout qualifiers for properties. In OpenGL ES 3.0, it is recommended to use layout qualifiers so that you don't have to look up this information after the fact.
The following code shows how to get the number of active vertex attributes:
on the blog [My OpenGL Learning Advanced Journey] Program Object introduced
void glGetProgramiv (GLuint program, GLenum pname, GLint *params);
2.2 Use the glGetActiveAttrib command to query the list of active vertex attributes and their data types
The list of active vertex attributes used by the program and their data types can be queried with the glGetActiveAttrib command.
void glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name);
Parameter Description:
-
program
The name of the program object previously linked successfully -
index
Specifies the vertex attributes to query, with values from 0 to GL_ACTIVIE_ATTRIBUTES -1 . The value of GL_ACTIVIE_ATTRIBUTES is determined with glGetProgramiv. -
bufSize
Specifies the maximum number of characters that can be written to name, including the Null terminator -
length
Returns the number of characters written to name, without the null terminator if length is not NULL -
type
Returns the property type, valid values are:- 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
Returns the property size. This is specified in the number of type cells returned by type. If quantity is not an array, the total size is 1. If the variable is an array, size returns the size of the array. -
name
The attribute variable name declared in the vertex shader
3. Bind vertex attributes to attribute variables in the vertex shader
We discussed earlier that in vertex shaders, vertex attribute variables are specified by the in qualifier, the number of active attributes can be queried with glGetProgramiv, and the list of active attributes in a program can be queried with glGetActiveAttrib.
We also discussed how to specify constant or per-vertex (vertex array) values using the glVertexAttrib* and glVertexAtrtribPointer commands.
Now, we consider how to map this generic attribute index to the for attribute variable declared in the vertex shader. This mapping enables the corresponding vertex data to be read into the correct vertex attribute variables in the vertex shader.
The following figure describes how to specify generic vertex attributes and attribute names bound to the vertex shader.
3.1 3 ways to map a generic vertex attribute index to an attribute variable name in a vertex shader
In OpenGL ES 3.0, there are 3 ways to map a generic vertex attribute index to an attribute variable name in a vertex shader.
These methods can be divided into the following categories:
- The index can be specified with the layout( location = N ) qualifier in the vertex shader source code (recommended).
- OpenGL ES 3.0 binds generic vertex attribute indices to attribute names.
- Applications can bind vertex attribute indices to attribute names.
The easiest way to bind a property to a location is to simply use the layout( location = N ) qualifier, which requires the least amount of code.
However, in some cases the other two options may be more appropriate.
3.2 glBindAttribLocation command
The glBindAttribLocation command can be used to bind a generic vertex attribute index to an attribute variable in a vertex shader. This binding expires the next time the program is linked and does not change the bindings used in the currently linked program.
void glBindAttribLocation (GLuint program, GLuint index, const GLchar *name);
Parameter Description:
- program
program object name - index
Generic vertex attribute index - name
attribute variable name
If name was previously bound, the binding it specifies is replaced by the index. The glBindAttribLocation command can be called even before the vertex shader is attached to the program object, so this call can be used to bind any attribute name. Attribute names that do not exist or that are inactive in vertex shaders connected to the program object are ignored.
Another option is to have OpenGL ES 3.0 bind the attribute variable name to a generic vertex attribute index. This binding takes place at program link time.
During the linking phase, the OpenGL ES 3.0 implementation does the following for each property variable:
For each attribute variable, check whether a binding has been specified via the glBindAttribLocation command. If a binding is specified, the specified pair property index is used. Otherwise, the OpenGL ES implementation will assign a generic vertex attribute index.
This assignment is implementation specific. That is, it may be different in one OpenGL ES 3.0 implementation and another.
3.2 glGetAttribLocation command
Applications can query assigned bindings using the glGetAttribLocation command
GLint glGetAttribLocation (GLuint program, const GLchar *name);
Parameter Description:
-
program
program object name -
name
attribute variable name
The glGetAttribLocation command returns the generic attribute index bound to the attribute variable name when the program object defined by program was last linked.
If name is not an active property variable, or program is not a valid program object, or if no linking succeeded, return -1 for an invalid property index.