OpenGL ES 给着色器(Shader)中传递数据

一、前言

在 OpenGL 开发中,肯定会接触到顶点着色器(vertex shader)和片段着色器(fragment shader),着色器语言的语法这里不做过多介绍,下面是一些着色器的示例:

OpenGL ES 2.0 顶点着色器示例:

uniform mat4 u_Matrix; // 统一变量
attribute vec4 a_Position; // 属性(输入)

void main() {
    gl_Position = u_Matrix * a_Position;
}

OpenGL ES 3.0 顶点着色器示例:

#version 300 es

uniform mat4 u_Matrix; // 统一变量
layout(location = 0) in vec4 v_Position; // 属性(输入)

void main() {
    gl_Position = u_Matrix * v_Position;
}

在OpenGL ES 2.0 中,使用 attributevarying 表示输入输出变量;而在 OpenGL ES 3.0 中,直接使用 inout 表示输入输出变量。虽然存在这样的差异,但是传值的接口和参数都是一致的。

那么,在应用程序中,是如何将数据传入到这些统一变量和属性中的呢?

声明:

后面的接口都是提供的 c++ 接口,java 接口函数命名和参数均与之相同,仅做了一次 native 的调用。

二、统一变量

1. 概念

统一变量(uniform)是存储应用程序通过 API 传递给着色器的 只读常量 数值的变量。例如上例中的 u_Matrix

如果统一变量在顶点着色器和片段着色器中均有声明,则声明的类型必须相同,且值也需相同。

2. 获取

我们想要给某个变量赋值,首先我们要获取到它,之后才能操作它。通过下面这个接口我们可以获取到一个统一变量。

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

参数解释:

  • program:你创建的着色器程序返回的id。
  • name:着色器语言中该变量的名称,例如 u_Matrix

值得注意的是,在 OpenGL ES 3.0 中,我们可以通过 layout(location = 0) 的形式直接指定变量的位置。

3. 传值

通过上述函数我们可以获取到统一变量的 location,接下来通过这个 location 我们就可以给其赋值了。

给统一变量赋值有很多函数,不同的统一变量类型对应不同的函数,如下:

void glUniform1f(GLint location, GLfloat v0);
void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
void glUniform1i(GLint location, GLint v0);
void glUniform1iv(GLint location, GLsizei count, const GLint *value);

void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
void glUniform2i(GLint location, GLint v0, GLint v1);
void glUniform2iv(GLint location, GLsizei count, const GLint *value);

void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
void glUniform3iv(GLint location, GLsizei count, const GLint *value);

void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
void glUniform4iv(GLint location, GLsizei count, const GLint *value);

void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
void glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

参数解释:

  • location:统一变量的位置,即上述步骤中获取到的值。
  • count:需要加载的元素数量,针对向量类型和矩阵类型。例如我们定义的是 uniform mat4 u_matrix 那么数量就是 1,如果是 uniform vec3 u_Position[3] 那么数量就是 3。
  • transpose:针对矩阵类型,指定矩阵采用列优先顺序(GL_FALSE)还是行顺序优先(GL_TRUE)。
  • v0, v1, v2, v3:输入的统一变量值。
  • value:输入的统一变量的数组指针。

三、属性

1. 概念

这里的属性只是指顶点着色器的输入变量,因为片段着色器的输入是顶点着色器的输出。

在 OpenGL ES 2.0 中,是由 attribute 修饰的变量,而在 OpenGL ES 3.0 中,则是由 in 或者 inout 修饰的变量。

2. 获取

同理,首先我们要获取到它,才能操作它。通过下面这个接口我们可以获取到一个统一变量。

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

参数解释:

  • program:你创建的着色器程序返回的id。
  • name:着色器语言中该变量的名称,例如 a_Position

值得注意的是,在 OpenGL ES 3.0 中,我们可以通过 layout(location = 0) 的形式直接指定变量的位置。

3. 传值

通过上述函数我们可以获取属性的 location,接下来通过这个 location 我们就可以给其赋值了。

常量顶点属性

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

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);

其中顶点的默认值为 (0.0, 0.0, 0.0, 1.0),上述函数分别覆盖指定的值。

顶点数组

顶点数组指定每个顶点的属性,使用如下函数传值:

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:属性的索引,同 location,即上述步骤中获取到的值。
  • size:顶点属性的分量数量,有效值为 1 ~ 4。
  • type:数据格式,包括 GL_BYTEGL_UNSIGNED_BYTEGL_SHORTGL_UNSIGNED_SHORTGL_INTGL_UNSIGNED_INTGL_FLOATGL_HALF_FLOATGL_FIXED 等。
  • normalized:表示非浮点数据格式转换为浮点值时,是否应该规范化。
  • stride:相邻索引间的数据的跨度。如果为0,则每个顶点数据按顺序,一个紧挨着一个地存储。
  • pointer:顶点属性数据的指针。

四、实例

1. 顶点着色器

分别有一个统一变量 u_Matrix 和属性 a_Position

uniform mat4 u_Matrix;
attribute vec4 a_Position;

void main() {
    gl_Position = u_Matrix * a_Position;
}

2. 代码片段

#include 
#include "ShaderUtils.h"

const char* vertexShaderSource =
        "uniform mat4 u_Matrix;\n"
        "attribute vec4 a_Position;\n"
        "\n"
        "void main() {\n"
        "    gl_Position = u_Matrix * a_Position;\n"
        "}";

const char* fragmentShaderSource =
        "precision mediump float;\n"
        "\n"
        "uniform vec4 u_Color;\n"
        "\n"
        "void main() {\n"
        "    gl_FragColor = u_Color;\n"
        "}";

GLuint g_program; // program id

void demo() {
    // 1. 创建着色器程序
    g_program = CreateProgram(vertexShaderSource, fragmentShaderSource);
    glUseProgram(g_program);

    // 2. 获取统一变量和属性
    GLint matrix_location = glGetUniformLocation(g_program, "u_Matrix");
    GLint position_location = glGetAttribLocation(g_program, "a_Position");

    // 3. 传值
    const GLfloat matrix[] = {
            1.0, 0.0, 0.0, 0.0,
            0.0, 1.0, 0.0, 0.0,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0
    };
    glUniformMatrix4fv(matrix_location, 1, GL_FALSE, matrix);
    const GLint vertex_size = 3; // 我们的顶点数组只有x, y, z三个分量
    const GLfloat vertices[] = {
            0.0f, 0.5f, 0.0f, // 第一个点(x, y, z)
            -0.5f, -0.5f, 0.0f, // 第二个点(x, y, z)
            0.5f, -0.5f, 0.0f // 第三个点(x, y, z)
    };
    glVertexAttribPointer(position_location, vertex_size, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(position_location); // 需要使用enable使其生效

    // 4. 绘制
    glDrawArrays(GL_TRIANGLES, 0, vertex_size);
    glDisableVertexAttribArray(position_location);
}

上面的代码不一定能运行,主要就是展示一下基本的使用方式。当然,上面介绍的这些内容也是基础中的基础了,对初学者有所帮助就行。

你可能感兴趣的:(OpenGL)