OpenGL ES SDK for Android - 4

Integer Logic

该应用程序根据规则30使用OpenGL ES 3.0模拟细胞自动机现象。

它使用两个程序对象,这两个程序以乒乓方式使用两个纹理。 第一个程序将ping纹理(ping)作为输入,并将输出呈现给第二个纹理(pong)。 在这种情况下的渲染是通过一次绘制一行来执行的,每行具有1像素的高度并且具有屏幕宽度。 排除第一行,通过读取当前处理的行上方的一行并应用元胞自动机规则来绘制每一行。 第一行的内容由应用程序设置。 由于我们无法一次绘制和读取相同的纹理,因此绘图一次执行一行。 在将一行绘制到纹理A之后,应用程序将纹理B绑定到绘图并使用纹理A来读取上一行。 最后,纹理A包含偶数行,纹理B包含奇数行。

完成了对这两个纹理的绘制后,我们运行了另一个GL程序,它将两个纹理合并为一个纹理,使用纹理A表示偶数行,纹理B表示奇数行。

为了能够渲染到纹理,我们使用自定义帧缓冲区。

对于第一次运行,输入行只有一个像素点亮,因此它生成了众所周知的Rule 30模式。 然后,每隔5秒,重置纹理并随机生成输入。

Prepare Textures

应用程序中使用了两个纹理对象。 所以第一步是生成它们并准备使用。 我们使用乒乓技术,因此我们将使用相应的名称来存储纹理ID的变量,以使算法更加清晰。

生成纹理对象。

GL_CHECK(glGenTextures(2, textureIDs));
pingTextureID = textureIDs[0];
pongTextureID = textureIDs[1];
复制代码

将纹理对象绑定到特定纹理单元并为ping纹理设置其属性,

/* Load ping texture data. */
GL_CHECK(glActiveTexture(GL_TEXTURE0 + pingTextureUnit));
GL_CHECK(glBindTexture  (GL_TEXTURE_2D,
                         pingTextureID));
GL_CHECK(glTexImage2D   (GL_TEXTURE_2D,
                         0,
                         GL_R8UI,
                         windowWidth,
                         windowHeight,
                         0,
                         GL_RED_INTEGER,
                         GL_UNSIGNED_BYTE,
                         pingTextureData));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_WRAP_S,
                         GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_WRAP_T,
                         GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_MAG_FILTER,
                         GL_NEAREST));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_MIN_FILTER,
                         GL_NEAREST));
复制代码

将纹理对象绑定到特定纹理单元并为pong纹理设置其属性,

/* Prepare pong texture object. */
GL_CHECK(glActiveTexture(GL_TEXTURE0 + pongTextureUnit));
GL_CHECK(glBindTexture  (GL_TEXTURE_2D,
                         pongTextureID));
GL_CHECK(glTexStorage2D (GL_TEXTURE_2D,
                         1,
                         GL_R8UI,
                         windowWidth,
                         windowHeight));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_WRAP_S,
                         GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_WRAP_T,
                         GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_MAG_FILTER,
                         GL_NEAREST));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D,
                         GL_TEXTURE_MIN_FILTER,
                         GL_NEAREST));
复制代码

将纹理单元定义为

/* Texture unit used for configuring 2D texture binding for ping textures. */
const GLuint pingTextureUnit = 0;
/* Texture unit used for configuring 2D texture binding for pong textures. */
const GLuint pongTextureUnit = 1;
复制代码

请注意,仅在其中一个纹理(ping)的情况下,它已经填充了数据,如果是pong,则只定义了一个数据存储。 用作ping纹理输入的数据是在generateRule30Input()调用中生成的。

generateRule30Input   (windowWidth / 2,
                       windowWidth,
                       windowHeight,
                       1,
                      &pingTextureData);
复制代码
/* Please see the specification above. */
void generateRule30Input(unsigned int xoffset,
                         unsigned int width,
                         unsigned int height,
                         unsigned int nComponents,
                         GLvoid** textureData)
{
    ASSERT(textureData != NULL, "Null data passed");
    for(unsigned int channelIndex = 0; channelIndex < nComponents; ++channelIndex)
    {
        (*(unsigned char**)textureData)[(height - 1) * width * nComponents + xoffset * nComponents + channelIndex] = 255;
    }
}
复制代码

在应用程序中,我们将使用渲染到纹理技术,这就是我们需要创建帧缓冲对象的原因。 只是为了澄清:渲染到纹理可以通过使用(在绘制调用期间)帧缓冲对象(ID不同于0)来实现,其中绑定了纹理对象。

GL_CHECK(glGenFramebuffers(1,
                          &framebufferID));
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER,
                           framebufferID));
GL_CHECK(glDrawBuffers    (1,
                           offscreenFBODrawBuffers));
复制代码

然后,如果绑定了一个帧缓冲对象,

GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferID));
复制代码

我们可以调用下面显示的函数之一来指示将在哪个纹理对象中存储渲染的结果。

GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
                                GL_COLOR_ATTACHMENT0,
                                GL_TEXTURE_2D,
                                pingTextureID,
                                0) );
复制代码
GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
                                GL_COLOR_ATTACHMENT0,
                                GL_TEXTURE_2D,
                                pongTextureID,
                                0));
复制代码

如果我们不再对渲染到纹理对象感兴趣并希望渲染结果显示在屏幕上,我们需要使用默认的framebuffer对象,这意味着我们必须调用

GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
复制代码

Program objects

在应用程序中,我们使用两个程序对象:第一个负责根据规则30生成数据,而第二个用于合并计算数据并在屏幕上显示结果。 生成程序对象的主要机制如下所示(为第一个程序对象显示的想法):

  1. 创建程序对象ID;
rule30ProgramID = GL_CHECK(glCreateProgram());
复制代码
  1. 创建着色器对象的ID(对于shaderType,应调用两次,等于GL_FRAGMENT_SHADER和GL_VERTEX_SHADER);
*shaderObjectIdPtr = GL_CHECK(glCreateShader(shaderType));
复制代码
  1. 设置着色器源;
strings[0]         = loadShader(filename);
复制代码
GL_CHECK(glShaderSource(*shaderObjectIdPtr, 1, strings, NULL));
复制代码
  1. 编译着色器(检查编译是否成功总是一个好主意:GL_COMPILE_STATUS设置为GL_TRUE;
GL_CHECK(glCompileShader(*shaderObjectIdPtr));
GL_CHECK(glGetShaderiv(*shaderObjectIdPtr, GL_COMPILE_STATUS, &compileStatus));
复制代码
  1. 将着色器附加到程序对象;
GL_CHECK(glAttachShader(rule30ProgramID, vertexRule30ShaderID));
GL_CHECK(glAttachShader(rule30ProgramID, fragmentRule30ShaderID));
复制代码
  1. 链接程序对象;
GL_CHECK(glLinkProgram(rule30ProgramID));
复制代码
  1. 使用程序。
GL_CHECK(glUseProgram(rule30ProgramID) );
复制代码

接下来要做的是检索着色器中使用的uniforms和attributes的位置。

rule30ProgramLocations.inputNeighbourLocation      = GL_CHECK(glGetUniformLocation(rule30ProgramID, "inputNeighbour")      );
rule30ProgramLocations.inputTextureLocation        = GL_CHECK(glGetUniformLocation(rule30ProgramID, "inputTexture")        );
rule30ProgramLocations.inputVerticalOffsetLocation = GL_CHECK(glGetUniformLocation(rule30ProgramID, "inputVerticalOffset") );
rule30ProgramLocations.mvpMatrixLocation           = GL_CHECK(glGetUniformLocation(rule30ProgramID, "mvpMatrix")           );
rule30ProgramLocations.positionLocation            = GL_CHECK(glGetAttribLocation (rule30ProgramID, "position")            );
rule30ProgramLocations.texCoordLocation            = GL_CHECK(glGetAttribLocation (rule30ProgramID, "vertexTexCoord")      );
rule30ProgramLocations.verticalOffsetLocation      = GL_CHECK(glGetUniformLocation(rule30ProgramID, "verticalOffset")      );
复制代码

验证attributes和uniforms在程序对象中是否被视为活动的始终是一个好主意。 这就是我们需要检查返回的位置值的原因(-1位置值意味着尚未找到attribute或uniform)。

ASSERT(rule30ProgramLocations.inputNeighbourLocation      != -1, "Could not find location of a uniform in rule30 program: inputNeighbour"     );
ASSERT(rule30ProgramLocations.inputTextureLocation        != -1, "Could not find location of a uniform in rule30 program: inputTexture"       );
ASSERT(rule30ProgramLocations.inputVerticalOffsetLocation != -1, "Could not find location of a uniform in rule30 program: inputVerticalOffset");
ASSERT(rule30ProgramLocations.mvpMatrixLocation           != -1, "Could not find location of a uniform in rule30 program: mvpMatrix"          );
ASSERT(rule30ProgramLocations.positionLocation            != -1, "Could not find location of an attribute in rule30 program: position"        );
ASSERT(rule30ProgramLocations.texCoordLocation            != -1, "Could not find location of an attribute in rule30 program: vertexTexCoord");
ASSERT(rule30ProgramLocations.verticalOffsetLocation      != -1, "Could not find location of a uniform in rule30 program: verticalOffset");
复制代码

现在,如果对设置属性的值感兴趣,并且我们知道并且想要设置四边形坐标和纹理UV,那么就足以准备一个顶点数组对象。

首先,创建对象

GL_CHECK(glGenVertexArrays(1, &lineVAOID));
复制代码

并绑定它。

GL_CHECK(glBindVertexArray(lineVAOID));
复制代码

现在,当顶点数组对象变为活动状态时,我们可以为它设置输入数据。 它是通过使用数组缓冲区对象实现的。

GL_CHECK(glGenBuffers(4, boIDs));
linePositionBOID = boIDs[0];
lineUVBOID       = boIDs[1];
quadPositionBOID = boIDs[2];
quadUVBOID       = boIDs[3];
复制代码

请注意,在本节中我们只描述了一个程序对象,因此我们将仅讨论前两个缓冲区对象的使用(linePositionBOID和lineUVBOID)。

使用四坐标数据填充其中一个缓冲区对象。

/* Fill buffers with line vertices attribute data. */
GL_CHECK(glBindBuffer             (GL_ARRAY_BUFFER,
                                   linePositionBOID));
GL_CHECK(glBufferData             (GL_ARRAY_BUFFER,
                                   sizeof(lineVertices),
                                   lineVertices,
                                   GL_STATIC_DRAW));
GL_CHECK(glVertexAttribPointer    (rule30ProgramLocations.positionLocation,
                                   4,
                                   GL_FLOAT,
                                   GL_FALSE,
                                   0,
                                   0));
GL_CHECK(glEnableVertexAttribArray(rule30ProgramLocations.positionLocation));
复制代码

用纹理UV数据填充第二个。

/* Fill buffers with line U/V attribute data. */
GL_CHECK(glBindBuffer             (GL_ARRAY_BUFFER,
                                   lineUVBOID));
GL_CHECK(glBufferData             (GL_ARRAY_BUFFER,
                                   sizeof(lineTextureCoordinates),
                                   lineTextureCoordinates,
                                   GL_STATIC_DRAW));
GL_CHECK(glVertexAttribPointer    (rule30ProgramLocations.texCoordLocation,
                                   2,
                                   GL_FLOAT,
                                   GL_FALSE,
                                   0,
                                   0));
GL_CHECK(glEnableVertexAttribArray(rule30ProgramLocations.texCoordLocation));
复制代码

可以在上面找到的重要事项是,我们通过调用glVertexAttribPointer()将属性位置与特定的输入数据集绑定。

如果要在绘制调用期间使用特定属性输入数据,则应记住通过调用glBindVertexArray()使绘制调用激活所需的顶点数组对象。

如果现在想要为uniforms设置数据,则应调用glUniform()系列函数之一,如下所示。 应该使用的功能取决于uniform类型。 如果均匀是matrix4x4类型,我们应该调用glUniformMatrix4fv(),一个浮点值:glUniform1f(),一个纹理采样器:glUniform1i()。 如果在渲染过程中uniform值是常量,我们可以设置一次,如果它正在改变,我们应该在每次想要更新uniform值时调用特定的glUniform()函数。

GL_CHECK(glUniformMatrix4fv(rule30ProgramLocations.mvpMatrixLocation,
                            1,
                            GL_FALSE,
                            modelViewProjectionMatrix.getAsArray()));
GL_CHECK(glUniform1f       (rule30ProgramLocations.inputNeighbourLocation,
                            1.0f / windowWidth) );
复制代码

Perform the rendering

该算法基于两个主要步骤:

  1. 生成数据(渲染到纹理)
  2. 在屏幕上渲染纹理

第一步考虑如下:我们想渲染一行,然后根据第一行,我们想要绘制第二行,依此类推,直到渲染整个屏幕。问题是,我们无法同时读取和渲染到同一个纹理对象。这就是为什么我们需要两个纹理对象,每个纹理对象将存储偶数或奇数行数据。所以看起来如下所述:我们渲染第一行并将结果存储在ping纹理中。然后我们想要根据第一行中的数据渲染第二行:在程序对象中,我们对ping纹理进行采样,并根据检索到的数据生成当前行并将结果存储在pong纹理中。然后,在第三行渲染期间,我们使用pong纹理作为输入并在ping纹理中存储数据。它似乎很复杂,但概念非常简单,并在下面的代码中显示。请注意,对于每次迭代,都会切换输入和输出纹理。通过调用glFramebufferTexture2D()和调用glUniform1i()和输出纹理来更新输入纹理。

/* Please see the specification above. */
void performOffscreenRendering()
{
    /* Offset of the input line passed to the appropriate uniform. */
    float inputVerticalOffset = 0.0f;
    /* Activate the first program. */
    GL_CHECK(glUseProgram(rule30ProgramID));
    GL_CHECK(glBindVertexArray(lineVAOID));
    /* [Bind the framebuffer object] */
    GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebufferID));
    /* [Bind the framebuffer object] */
    /* Render each line, beginning from the 2nd one, using the input from the previous line.*/
    for (unsigned int y = 1; y <= windowHeight; ++y)
    {
        /* Even lines should be taken from the ping texture, odd from the pong. */
        bool  isEvenLineBeingRendered = (y % 2 == 0) ? (true) : (false);
        /* Vertical offset of the currently rendered line. */
        float verticalOffset          = (float) y / (float) windowHeight;
        /* Pass data to uniforms. */
        GL_CHECK(glUniform1f(rule30ProgramLocations.verticalOffsetLocation,      verticalOffset));
        GL_CHECK(glUniform1f(rule30ProgramLocations.inputVerticalOffsetLocation, inputVerticalOffset));
        if (isEvenLineBeingRendered)
        {
            /* [Bind ping texture to the framebuffer] */
            GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
                                            GL_COLOR_ATTACHMENT0,
                                            GL_TEXTURE_2D,
                                            pingTextureID,
                                            0) );
            /* [Bind ping texture to the framebuffer] */
            GL_CHECK(glUniform1i           (rule30ProgramLocations.inputTextureLocation,
                                            pongTextureUnit) );
        }
        else
        {
            /* [Bind pong texture to the framebuffer] */
            GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
                                            GL_COLOR_ATTACHMENT0,
                                            GL_TEXTURE_2D,
                                            pongTextureID,
                                            0));
            /* [Bind pong texture to the framebuffer] */
            GL_CHECK(glUniform1i           (rule30ProgramLocations.inputTextureLocation,
                                            pingTextureUnit) );
        }
        /* Drawing a horizontal line defined by 2 vertices. */
        GL_CHECK(glDrawArrays(GL_LINES, 0, 2));
        /* Update the input vertical offset after the draw call, so it points to the previous line. */
        inputVerticalOffset = verticalOffset;
    }
    /* Unbind the framebuffer. */
    GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
}
复制代码

完成此步骤后,我们就可以执行第二步了。 这里的主要思想是采用这两种纹理:ping和pong,并合并它们,如下所示。 请注意,默认的帧缓冲对象处于活动状态,这意味着结果将显示在屏幕上。

/* Please see the specification above. */
void renderToBackBuffer()
{
    /* Activate the second program. */
    GL_CHECK(glUseProgram     (mergeProgramID));
    GL_CHECK(glBindVertexArray(quadVAOID));
    /* Draw a quad as a triangle strip defined by 4 vertices. */
    GL_CHECK(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
}
复制代码

在应用程序中,我们希望根据不同的输入数据多次发出算法。 我们第一次使用上一节Prepare Textures中描述的生成数据。 对于每个后续迭代,我们使用通过下面给出的算法生成的数据。

/* Please see the specification above. */
void generateRule30Input(unsigned int width,
                         unsigned int height,
                         unsigned int nComponents,
                         GLvoid**     textureData)
{
    ASSERT(textureData != NULL, "Null data passed");
    for (unsigned int texelIndex = 0; texelIndex < width; ++texelIndex)
    {
        bool setWhite = (rand() % 2 == 0) ? true : false;
        if (setWhite)
        {
            for (unsigned int channelIndex = 0; channelIndex < nComponents; ++channelIndex)
            {
                (*(unsigned char**)textureData)[(height - 1) * width * nComponents + texelIndex * nComponents + channelIndex] = 255;
            }
        }
    }
}
复制代码

并通过调用将ping纹理数据替换为新的纹理数据

/* Since texture objects have already been created, we can substitute ping image using glTexSubImage2D call.
 * Pong texture does not require reset, because its content depends entirely on the first line of the ping texture. */
GL_CHECK(glActiveTexture(GL_TEXTURE0));
GL_CHECK(glTexSubImage2D(GL_TEXTURE_2D,
                         0,
                         0,
                         0,
                         windowWidth,
                         windowHeight,
                         GL_RED_INTEGER,
                         GL_UNSIGNED_BYTE,
                         pingTextureData));
复制代码

ETC2 Texture

在OpenGL ES 3.0中演示ETC2纹理压缩支持。

该应用程序循环使用OpenGL ES 3.0支持的所有纹理格式。

压缩的纹理被加载并显示在屏幕上。每个纹理的内部格式显示在屏幕的底部。该应用程序循环使用OpenGL ES 3.0支持的所有纹理格式。

格式:

GL_COMPRESSED_R11_EAC:单个通道的11位。适用于需要高于8位精度的单通道数据。例如,heightmaps。 GL_COMPRESSED_SIGNED_R11_EAC:GL_COMPRESSED_R11_EAC的签名版本,在需要签名数据时非常有用。 GL_COMPRESSED_RG11_EAC:两个通道的11位。适用于需要高于8位精度的双通道数据。例如,标准化的凹凸贴图,第三个组件可以从其他两个组件重建。 GL_COMPRESSED_SIGNED_RG11_EAC:GL_COMPRESSED_RG11_EAC的签名版本,在需要签名数据时非常有用。 GL_COMPRESSED_RGB8_ETC2:三个通道的8位。对于没有alpha值的普通纹理很有用。 GL_COMPRESSED_SRGB8_ETC2:GL_COMPRESSED_RGB8_ETC2的sRGB版本。 GL_COMPRESSED_RGBA8_ETC2_EAC:4个通道的8位。适用于具有不同alpha值的普通纹理。 GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:GL_COMPRESSED_RGBA8_ETC2_EAC的sRGB版本。 GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:3个通道的8位和1位alpha通道。对于具有二进制alpha值的普通纹理很有用。 GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2的sRGB版本。

Generating Compressed Images

生成ETC2图像的一种可能方法是使用纹理压缩工具,可以加载任何纹理(以jpg,png或任何其他支持的文件格式存储)。 然后尝试使用ETC2压缩器压缩图像。结果将存储在PKM文件中。

生成压缩纹理时,可以按以下步骤使用它们。

Render Compressed Textures on a Screen

在本节中,我们将介绍如何在屏幕上呈现压缩纹理。 对于OpenGL ES 3.0支持的每种ETC2压缩格式,我们将有一个单独的纹理对象。 在下面描述的步骤中,我们将专注于处理单个纹理对象,因为对于我们想要显示的所有纹理格式应该重复相同的步骤。

Generate Texture Object

我们需要从生成纹理对象ID开始。

GL_CHECK(glGenTextures(1,            &imageArray[textureIndex].textureId));
复制代码

一旦我们得到一个,我们需要将它绑定到GL_TEXTURE_2D目标。

GL_CHECK(glBindTexture(GL_TEXTURE_2D, imageArray[textureIndex].textureId));
复制代码

设置纹理对象的参数非常重要,这样纹理就可以在屏幕上正确显示。

/* Set parameters for a texture. */
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
复制代码

现在,我们用数据填充纹理对象。

/* Call CompressedTexImage2D() function which specifies texture with compressed image. */
GL_CHECK(glCompressedTexImage2D(GL_TEXTURE_2D,
                                0,
                                internalformat,
                                imageWidth,
                                imageHeight,
                                0,
                                imageSize,
                                imageData));
复制代码

如果考虑了internalformat参数值,则应使用下面列出的OpenGL ES 3.0支持的ETC2内部格式之一(当然,这对应于我们在此处使用的压缩纹理的internalformat)。

此时有一点不清楚:glCompressedTexImage2D()调用中的imageWidth,imageHeight,imageSize,imageData应该使用什么值? 这些值应对应于从PKM文件中检索的数据,如下所示。

Texture::loadPKMData(fileName, &etcHeader, &imageData);
复制代码
int     imageHeight = etcHeader.getHeight();
int     imageWidth  = etcHeader.getWidth();
GLsizei imageSize   = etcHeader.getSize(internalformat);
复制代码

Load PKM image data

应该做的第一件事是设置像素存储模式,以便在读取纹理图像时使用正确的对齐方式。

/* Set OpenGL to use right alignment when reading texture images. */
GL_CHECK(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
复制代码

然后,应该打开并读取下面的函数中实现的PKM文件。

void Texture::loadPKMData(const char *filename, ETCHeader* etcHeader, unsigned char **textureData)
{
    /* PKM file consists of a header with information about image (stored in 16 first bits) and image data. */
    const int       sizeOfETCHeader = 16; 
    unsigned char*  tempTextureData = NULL;
    loadData(filename, &tempTextureData);
    ASSERT(textureData     != NULL, "textureData is a NULL pointer.");
    ASSERT(etcHeader       != NULL, "etcHeader is a NULL pointer.");
    ASSERT(tempTextureData != NULL, "Could not load data from PKM file.");
    ETCHeader tempEtcHeader(tempTextureData);
    *etcHeader   = tempEtcHeader;
    *textureData = tempTextureData + sizeOfETCHeader;
}
复制代码
void Texture::loadData(const char *filename, unsigned char **textureData)
{
    FILE *file = fopen(filename, "rb");
    if(file == NULL)
    {
        LOGE("Failed to open '%s'\n", filename);
        exit(1);
    }
    fseek(file, 0, SEEK_END);
    unsigned int   length        = ftell(file);
    unsigned char *loadedTexture = (unsigned char *)calloc(length, sizeof(unsigned char));
    ASSERT(loadedTexture != NULL, "Could not allocate memory to store PKM file data.")
    fseek(file, 0, SEEK_SET);
    size_t read = fread(loadedTexture, sizeof(unsigned char), length, file);
    ASSERT(read == length, "Failed to read PKM file.");
    fclose(file);
    *textureData = loadedTexture;
}
复制代码

获得结果后,可以在glCompressedTexImage2D()调用中使用检索到的数据。

Render texture on a screen

在OpenGL ES中,基本的几何渲染技术是渲染三角形。 在我们的例子中,我们想要渲染一个简单的四边形,其中将应用纹理图像。

第一步是生成构成四边形的三角形的顶点坐标(请注意,我们对XY空间中的四边形感兴趣)。

请看下面的图片。 架构显示了我们在应用程序中使用的坐标。

四边形坐标(蓝色)和相应的纹理UV(红色)。

用于渲染四边形的三角形的坐标。

float vertexData[] = {-1.0f, -1.0f, 0.0f,
                       1.0f, -1.0f, 0.0f,
                      -1.0f,  1.0f, 0.0f,
                      -1.0f,  1.0f, 0.0f,
                       1.0f, -1.0f, 0.0f,
                       1.0f,  1.0f, 0.0f};
复制代码

相应的纹理UV。

float textureCoordinatesData[] = {0.0f, 1.0f,
                                  1.0f, 1.0f,
                                  0.0f, 0.0f,
                                  0.0f, 0.0f,
                                  1.0f, 1.0f,
                                  1.0f, 0.0f};
复制代码

我们需要数组缓冲区对象来存储上述坐标。

/* Generate buffers. */
GL_CHECK(glGenBuffers(2,
                      bufferObjectIds));
/* Fill buffer object with vertex data. */
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER,
                      bufferObjectIds[0]));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER,
                      sizeof(vertexData),
                      vertexData,
                      GL_STATIC_DRAW));
/* Fill buffer object with texture coordinates data. */
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER,
                      bufferObjectIds[1]));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER,
                      sizeof(textureCoordinatesData),
                      textureCoordinatesData,
                      GL_STATIC_DRAW));
复制代码

OpenGL ES 3.0渲染需要程序对象:

  1. 创建程序对象ID,
programId = GL_CHECK(glCreateProgram());
复制代码
  1. 创建着色器对象的ID(对于shaderType应该调用两次,等于GL_FRAGMENT_SHADER和GL_VERTEX_SHADER),
*shaderObjectIdPtr = GL_CHECK(glCreateShader(shaderType));
复制代码
  1. 设置着色器源,
strings[0]         = loadShader(filename);
复制代码
GL_CHECK(glShaderSource(*shaderObjectIdPtr, 1, strings, NULL));
复制代码
  1. 编译着色器(检查编译是否成功总是一个好主意:GL_COMPILE_STATUS设置为GL_TRUE,
GL_CHECK(glCompileShader(*shaderObjectIdPtr));
复制代码
GL_CHECK(glGetShaderiv(*shaderObjectIdPtr, GL_COMPILE_STATUS, &compileStatus));
复制代码
  1. 将着色器附加到程序对象,
GL_CHECK(glAttachShader(programId, vertexShaderId));
GL_CHECK(glAttachShader(programId, fragmentShaderId));
复制代码
  1. 链接程序对象,
GL_CHECK(glLinkProgram(programId));
复制代码
  1. 使用程序。
GL_CHECK(glUseProgram(programId));
复制代码

顶点着色器代码:

in        vec4 attributePosition;
in        vec2 attributeTextureCoordinate;
uniform   mat4 modelViewMatrix;
out       vec2 varyingTextureCoordinate;
void main()
{
    varyingTextureCoordinate = attributeTextureCoordinate;
    gl_Position              = modelViewMatrix * attributePosition;
}
复制代码

片段着色器代码:

precision mediump float;
uniform sampler2D uniformTexture;
in      vec2      varyingTextureCoordinate;
out     vec4      colour;
void main()
{
    colour = texture(uniformTexture, varyingTextureCoordinate);
}
复制代码

下一步是为着色器中使用的attributes和uniforms设置值。 为此,我们需要检索他们的位置。 在API中,我们使用程序的ID和作为参数给出的着色器中使用的uniform名称来调用glGetUniformLocation()。 类似的情况是调用glGetAttribLocation(),但这次我们使用的是attribute名称。

/* Get attributes and uniforms locations from shaders attached to the program. */
modelViewMatrixLocation   = GL_CHECK(glGetUniformLocation(programId, "modelViewMatrix"));
positionLocation          = GL_CHECK(glGetAttribLocation (programId, "attributePosition"));
textureCoordinateLocation = GL_CHECK(glGetAttribLocation (programId, "attributeTextureCoordinate"));
textureLocation           = GL_CHECK(glGetUniformLocation(programId, "uniformTexture"));
复制代码

确保找到uniform和attributes(这意味着它们由程序对象声明和使用)始终是一个好主意。 如果返回的值等于-1,则attribute/uniform被认为是非活动的。

ASSERT(modelViewMatrixLocation   != -1, "Could not retrieve uniform location: modelViewMatrix.");
ASSERT(positionLocation          != -1, "Could not retrieve attribute location: attributePosition.");
ASSERT(textureCoordinateLocation != -1, "Could not retrieve attribute location: attributeTextureCoordinate.");
ASSERT(textureLocation           != -1, "Could not retrieve uniform location: uniformTexture.");
复制代码

一旦我们确定检索到的位置有效,我们就可以为uniforms和attributes设置值。

GL_CHECK(glUniform1i(textureLocation,
                            0));
复制代码

我们告诉OpenGL ES使用当前绑定到GL_TEXTURE0纹理单元的GL_TEXTURE_2D目标的纹理对象(如果使用GL_TEXTURE1作为纹理单元,uniform的值应该等于1)作为程序对象的输入。

我们希望应用程序循环遍历OpenGL ES 3.0支持的所有纹理格式,这就是我们多次重新绑定纹理的原因。 这样做如下所示。

    /* Draw texture-mapped quad. */
    GL_CHECK(glActiveTexture(GL_TEXTURE0));
    GL_CHECK(glBindTexture  (GL_TEXTURE_2D,
                             imageArray[currentAssetIndex].textureId));
复制代码

如何设置attributes的值? 最好的方法是使用Vertex Attrib Arrays。 还记得我们创建的缓冲区对象并用四边形坐标和纹理UV数据填充吗?

GL_CHECK(glBindBuffer             (GL_ARRAY_BUFFER,
                                   bufferObjectIds[0]));
GL_CHECK(glVertexAttribPointer    (positionLocation,
                                   3,
                                   GL_FLOAT,
                                   GL_FALSE,
                                   0,
                                   0));
GL_CHECK(glEnableVertexAttribArray(positionLocation));
GL_CHECK(glBindBuffer             (GL_ARRAY_BUFFER,
                                   bufferObjectIds[1]));
GL_CHECK(glVertexAttribPointer    (textureCoordinateLocation,
                                   2,
                                   GL_FLOAT,
                                   GL_FALSE,
                                   0,
                                   0));
GL_CHECK(glEnableVertexAttribArray(textureCoordinateLocation));
复制代码

最后要做的是发出绘制调用。

GL_CHECK(glDrawArrays(GL_TRIANGLES, 0, 6));
复制代码

Boids

在OpenGL ES 3.0中演示变换反馈功能。

该应用程序显示30个球体:1个领导者和29个跟随者。

演示使用uniform缓冲区。 应用程序在屏幕上显示30个球体。 3D空间中球体的位置和速度会定期更新。 有1个领导球(红色)和29个粉丝(绿色)。 领导者遵循一个循环的路径和追随者'群'相对于领导者和其他追随者。 在渲染场景之前,使用顶点着色器在每个帧上在GPU上完成boids位置的计算。

boids的所有数据都保留在GPU内存中(通过使用缓冲区),并且不会传输回CPU。 变换反馈缓冲区用于存储从顶点着色器输出的运动信息,然后该数据用作下一遍的输入数据。 渲染场景时使用相同的数据。

Render a Geometry

要渲染任何类型的几何体:四边形,立方体,球体或任何更复杂的模型,最好的方法是渲染构成所需形状的三角形。 所以我们需要做的第一件事就是生成这些三角形的坐标。 请记住,在计算坐标时遵循一个顺序(顺时针或逆时针)非常重要,否则(如果混合它们),OpenGL ES可能在检测正面和背面时遇到一些问题。 我们将使用逆时针顺序,因为这是OpenGL ES的默认设置。

构成我们想要渲染的几何体的三角形的点坐标(在我们的情况下将是一个球体)在以下函数中生成

SphereModel::getTriangleRepresentation(radius,
                                       numberOfSamples,
                                   &numberOfSphereTriangleCoordinates,
                                      &numberOfSphereTrianglePoints,
                                      &sphereTrianglesCoordinates);
复制代码

下一步是将生成的数据传输到缓冲区对象中,并在渲染时使用它。 但是让我们在基本步骤中描述问题。

生成缓冲区对象。

/* Generate buffers. */
GL_CHECK(glGenBuffers(numberOfBufferObjectIds, bufferObjectIds));
sphereCoordinatesBufferObjectId             = bufferObjectIds[0];
sphereColorsBufferObjectId                  = bufferObjectIds[1];
spherePingPositionAndVelocityBufferObjectId = bufferObjectIds[2];
spherePongPositionAndVelocityBufferObjectId = bufferObjectIds[3];
复制代码

在应用程序中我们需要更多的缓冲区对象,但是此时我们只对一个感兴趣,所以你可以使用下面的函数。

GL_CHECK(glGenbuffers(1, &sphereCoordinatesBufferObjectId));
复制代码

生成缓冲区对象ID后,我们可以使用该对象存储坐标数据。

GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER,
                      sphereCoordinatesBufferObjectId));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER,
                      numberOfSphereTriangleCoordinates,
                      sphereTrianglesCoordinates,
                      GL_STATIC_DRAW));
复制代码

下一步是将程序对象的输入数据与特定的数组缓冲区对象相关联,然后启用顶点属性数组。 这可以如下完成。

GL_CHECK(glBindBuffer             (GL_ARRAY_BUFFER,
                                   sphereCoordinatesBufferObjectId));
GL_CHECK(glEnableVertexAttribArray(positionLocation));
GL_CHECK(glVertexAttribPointer    (positionLocation,
                                   3,
                                   GL_FLOAT,
                                   GL_FALSE,
                                   0,
                                   0));
复制代码

我们将很快解释应该为positionLocation参数使用什么值。

在OpenGL ES 3.0中,没有程序对象就没有渲染。 首先,我们需要:

  1. 创建程序对象ID,
renderingProgramId = GL_CHECK(glCreateProgram());
复制代码
  1. 创建着色器对象的ID(对于shaderType应该调用两次,等于GL_FRAGMENT_SHADER和GL_VERTEX_SHADER),
*shaderObjectIdPtr = GL_CHECK(glCreateShader(shaderType));
复制代码
  1. 设置着色器源,
strings[0]         = loadShader(filename);
复制代码
GL_CHECK(glShaderSource(*shaderObjectIdPtr, 1, strings, NULL));
复制代码
  1. 编译着色器(检查编译是否成功总是一个好主意:GL_COMPILE_STATUS设置为GL_TRUE,
GL_CHECK(glCompileShader(*shaderObjectIdPtr));
复制代码
GL_CHECK(glGetShaderiv(*shaderObjectIdPtr, GL_COMPILE_STATUS, &compileStatus));
复制代码
  1. 将着色器附加到程序对象,
GL_CHECK(glAttachShader(programId, vertexShaderId));
GL_CHECK(glAttachShader(programId, fragmentShaderId));
复制代码
  1. 链接程序对象,
GL_CHECK(glLinkProgram(programId));
复制代码
  1. 使用程序。
GL_CHECK(glUseProgram(programId));
复制代码

现在我们已经准备好告诉你positionLocation代表什么。 这是程序对象中attribute的位置。 我们可以通过调用来检索这个值

positionLocation = GL_CHECK(glGetAttribLocation   (renderingProgramId, "attributePosition"));
复制代码

第二个参数attributePosition与顶点着色器中使用的名称相同。

顶点着色器源:

const int numberOfSpheres = 30;
const float pi = 3.14159265358979323846;
in      vec4 attributePosition;
in      vec4 attributeColor;
out     vec4 vertexColor;
uniform vec4 perspectiveVector;
uniform vec3 scalingVector;
uniform vec3 cameraVector;
/* 
 * We use uniform block in order to reduce amount of memory transfers to minimum. 
 * The uniform block uses data taken directly from a buffer object. 
 */
uniform BoidsUniformBlock
{
    vec4 sphereLocation[numberOfSpheres];
};
void main()
{
    float fieldOfAngle     = 1.0 / tan(perspectiveVector.x * 0.5);
    vec3  locationOfSphere = vec3 (sphereLocation[gl_InstanceID].x, sphereLocation[gl_InstanceID].y, sphereLocation[gl_InstanceID].z);
    
    /* Set red color for leader and green color for followers. */
    if(gl_InstanceID == 0)
    {
        vertexColor = vec4(attributeColor.x, 0.5 * attributeColor.y, 0.5 * attributeColor.z, attributeColor.w);
    }
    else
    {
        vertexColor = vec4(0.5 * attributeColor.x, attributeColor.y, 0.5 * attributeColor.z, attributeColor.w);
    }
    
    /* Create transformation matrices. */
    mat4 translationMatrix = mat4(1.0,                 0.0,                   0.0,                 0.0, 
                                  0.0,                 1.0,                   0.0,                 0.0, 
                                  0.0,                 0.0,                   1.0,                 0.0, 
                                  locationOfSphere.x,  locationOfSphere.y,    locationOfSphere.z,  1.0);
                                  
    mat4 cameraMatrix      = mat4(1.0,                 0.0,                   0.0,                 0.0, 
                                  0.0,                 1.0,                   0.0,                 0.0, 
                                  0.0,                 0.0,                   1.0,                 0.0, 
                                  cameraVector.x,      cameraVector.y,        cameraVector.z,      1.0);
                                  
    mat4 scalingMatrix     = mat4(scalingVector.x,     0.0,                   0.0,                 0.0, 
                                  0.0,                 scalingVector.y,       0.0,                 0.0, 
                                  0.0,                 0.0,                   scalingVector.z,     0.0, 
                                  0.0,                 0.0,                   0.0,                 1.0);
                                  
    mat4 perspectiveMatrix = mat4(fieldOfAngle/perspectiveVector.y,  0.0,            0.0,                                                                                              0.0, 
                                  0.0,                               fieldOfAngle,   0.0,                                                                                              0.0, 
                                  0.0,                               0.0,            -(perspectiveVector.w + perspectiveVector.z) / (perspectiveVector.w - perspectiveVector.z),       -1.0, 
                                  0.0,                               0.0,            (-2.0 * perspectiveVector.w * perspectiveVector.z) / (perspectiveVector.w - perspectiveVector.z), 0.0);
    /* Compute scaling. */
    mat4 tempMatrix = scalingMatrix;
    
    /* Compute translation. */
    tempMatrix      = translationMatrix * tempMatrix;
    tempMatrix      = cameraMatrix      * tempMatrix;
                
    /* Compute perspective. */
    tempMatrix      = perspectiveMatrix * tempMatrix;
                
    /* Return gl_Position. */
    gl_Position     = tempMatrix * attributePosition;
}
复制代码

片段着色器源:

precision mediump float;
in vec4 vertexColor;
out vec4 fragColor;
void main()
{
    fragColor = vertexColor;
}
复制代码

在顶点着色器中,还有一个属性使用。 现在应该知道如何为它设置输入数据。 还有一些使用的uniforms,我们现在将描述如何处理它们。 首先,我们需要检索他们的位置。

scalingMatrixLocation = GL_CHECK(glGetUniformLocation  (renderingProgramId, "scalingVector"));
perspectiveMatrixLocation = GL_CHECK(glGetUniformLocation  (renderingProgramId, "perspectiveVector"));
cameraPositionLocation = GL_CHECK(glGetUniformLocation  (renderingProgramId, "cameraVector"));
复制代码

请注意,验证返回的数据是否有效总是一个好主意。

ASSERT(positionLocation          != -1,               "Could not retrieve attribute location: attributePosition");
ASSERT(sphereVertexColorLocation != -1,               "Could not retrieve attribute location: attributeColor");
ASSERT(scalingMatrixLocation     != -1,               "Could not retrieve uniform location: scalingMatrixLocation");
ASSERT(perspectiveMatrixLocation != -1,               "Could not retrieve uniform location: perspectiveMatrixLocation");
ASSERT(cameraPositionLocation    != -1,               "Could not retrieve uniform location: cameraPositionLocation");
ASSERT(movementUniformBlockIndex != GL_INVALID_INDEX, "Could not retrieve uniform block index: BoidsUniformBlock")
复制代码

然后,如果我们想为uniforms设置数据,就足以调用

GL_CHECK(glUniform3fv(scalingMatrixLocation,     1, scalingVector));
GL_CHECK(glUniform4fv(perspectiveMatrixLocation, 1, perspectiveVector));
GL_CHECK(glUniform3fv(cameraPositionLocation,    1, cameraVector));
复制代码

最后,发出绘制调用。 通常,我们会调用

GL_CHECK(glDrawArrays(GL_TRIANGLES, 0, numberOfSphereTrianglePoints));
复制代码

但是,在这种情况下,我们想要渲染同一对象的多个实例(30个球体)。 这就是我们需要调用的原因

GL_CHECK(glDrawArraysInstanced(GL_TRIANGLES,
                               0,
                               numberOfSphereTrianglePoints,
                               numberOfSpheresToGenerate));
复制代码

请注意,只有在请求的程序对象处于活动状态时才调用上述大多数函数(使用与程序对象ID对应的参数调用glUseProgram())。

完成上述所有步骤后,我们将在屏幕上绘制30个球:1个红色和29个绿色。 我们准备稍微移动它们了。

Transform Feedback

有一个领导者和一些粉丝,他们都试图保持其他人之间的距离。

领导者在不断的轨道上前进。它的位置每帧都会更新。追随者的新位置是根据领导者的位置计算的,但也有其他球体位置被考虑在内(因此它们之间的距离不是太小)。

变换反馈用于设置每个球体的位置和速度。无法同时读取和写入同一缓冲区对象,因此我们使用乒乓方法。在第一次调用期间,pong缓冲区用于读取和ping缓冲区用于写入。在第二次调用期间,ping缓冲区用于读取,pong缓冲区用于写入。

首先,我们将有两个缓冲区对象,以ping和pong前缀命名。其中只有一个需要使用原始数据(起始位置和速度)进行初始化,但第二个应该准备好存储更新的值。

设置缓冲区对象存储。

/* Buffers holding coordinates of sphere positions and velocities which are used by transform feedback
 * (to read from or write computed data). */
/* Set buffers' size and usage, but do not fill them with any data. */
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER,
                      spherePingPositionAndVelocityBufferObjectId));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER,
                      spherePositionsAndVelocitiesLength * sizeof(float),
                      NULL,
                      GL_STATIC_DRAW));
GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER,
                      spherePongPositionAndVelocityBufferObjectId));
GL_CHECK(glBufferData(GL_ARRAY_BUFFER,
                      spherePositionsAndVelocitiesLength * sizeof(float),
                      NULL,
                      GL_STATIC_DRAW));
复制代码

用数据填充一个缓冲区对象。

GL_CHECK(glBindBuffer   (GL_ARRAY_BUFFER,
                         spherePongPositionAndVelocityBufferObjectId));
GL_CHECK(glBufferSubData(GL_ARRAY_BUFFER,
                         0,
                         sizeof(startPositionAndVelocity),
                         startPositionAndVelocity));
复制代码

我们希望存储空间中球体位置的更新值及其速度。 程序对象需要附加顶点着色器和片段着色器。 但是在这种情况下我们并不需要任何片段操作,因此片段着色器的main()函数保持为空。 整个算法在顶点着色器中实现,如下所示:

const int numberOfSpheres = 30;
/* 
 * We use uniform block in order to reduce amount of memory transfers to minimum. 
 * The uniform block uses data taken directly from a buffer object. 
 */
uniform inputData
{
  vec4 inLocation[numberOfSpheres]; /* Current location of spheres. */
  vec4 inVelocity[numberOfSpheres]; /* Current velocity of spheres. */
};
out vec4 location; /* Transformed sphere location. */
out vec4 velocity; /* Transformed sphere velocity. */
uniform float time; /* Time value used for determining new leader's position. */
/* Boids fly toward center of the mass. */
vec4 moveToCenter()
{
    vec4 center = vec4(0.0);
    /* Calculate the center of mass for all other boids (average of their locations). */
    for (int i = 0; i < numberOfSpheres; i++)
    {
        if (i != gl_InstanceID)
        {
            center = center + inLocation[i];
        }
    }
    center = center / float(numberOfSpheres - 1);
    return (center - inLocation[gl_InstanceID]) / 100.0;
}
/* Boids keep their distance from other boids. */
vec4 keepDistanceBetweenBoids()
{
    vec4 result = vec4(0.0);
    
    for (int i = 0; i < numberOfSpheres; i++)
    {
        if (i != gl_InstanceID)
        {
            /* Compute distance between boids. */
            float xDistance   = inLocation[i].x - inLocation[gl_InstanceID].x;
            float yDistance   = inLocation[i].y - inLocation[gl_InstanceID].y;
            float zDistance   = inLocation[i].z - inLocation[gl_InstanceID].z;
            float xyzDistance = sqrt(xDistance * xDistance + yDistance * yDistance + zDistance * zDistance);
            
            /* If distance between boids is too small, update result vector. */
            /* Radius of sphere (which represents a single boid) is set to 10, scaling factor is set to 0.1, which means that the boids start to overlap if the distance gets below 2. 
            * Minimum distance is set to 4 so that boids start to run away from each other if the distance between them is too low. 
            * We use smoothstep() function to smoothen the "run-away".
            */
            if (xyzDistance < 4.0)
            {
                result = result - (1.1 - smoothstep(0.0, 4.0, xyzDistance)) * (inLocation[i] - inLocation[gl_InstanceID]);
            }
        }
    }
    
    return result;
}
/* Boids try to match velocity with other boids. */
vec4 matchVelocity()
{
    vec4 result = vec4(0.0);
    
    /* Compute average velocity of all other boids. */
    for (int i = 0; i < numberOfSpheres; i++)
    {
        if (i != gl_InstanceID)
        {
            result = result + inVelocity[i];
        }
    }
    
    result = result / float(numberOfSpheres - 1);
    
    return (result - inVelocity[gl_InstanceID]) / 2.0;  
}
/* Compute followers' positions and velocities. */
void setFollowersPosition()
{
    vec4 result1 = moveToCenter();
    vec4 result2 = keepDistanceBetweenBoids();
    vec4 result3 = matchVelocity();
        
    velocity = inVelocity[gl_InstanceID] + result1 + result2 + result3;
    location = inLocation[gl_InstanceID] + velocity;
}
/* Calculate leader's position using a certain closed curve. */ 
void setLeaderPosition()
{
    location = vec4(15.0 * (1.0 + cos(time) - 1.0), 
                    15.0 * sin(time), 
                    2.0 * 15.0 * sin(time / 2.0), 
                    1.0);
    velocity = vec4(0.0);
}
void main()
{
    /* Use a different approach depending on whether we are dealing with a leader or a follower. */
    if (gl_InstanceID == 0)
    {
        setLeaderPosition();
    }
    else
    {
        setFollowersPosition();
    }
}
复制代码

在API中,我们需要声明将存储Transform Feedback操作结果的输出变量。

const GLchar* varyingNames[] = {"location", "velocity"};
复制代码
/*
 * Specify varyings which are used with transform feedback buffer.
 * In shader we are using uniform block for holding location and velocity data.
 * Uniform block takes data from buffer object. Buffer object is filled with position data for each sphere first, and then with velocity data for each sphere.
 * Setting mode to GL_SEPARATE_ATTRIBS indicates that data are written to output buffer in exactly the same way as in input buffer object.
 */
GL_CHECK(glTransformFeedbackVaryings(movementProgramId,
                                     2,
                                     varyingNames,
                                     GL_SEPARATE_ATTRIBS));
复制代码

请注意,除非调用glLinkProgram(),否则glTransformFeedbackVaryings()函数无效。 所以请确保后面跟着上面的功能

GL_CHECK(glLinkProgram(movementProgramId));
复制代码

在渲染单个帧之前,我们需要设置正确的输入和输出数据存储(因为我们无法读取和写入相同的缓冲区对象,我们使用上述的ping-pong技术)。

设置输出缓冲区对象

/*
 * Configure transform feedback.
 * Bind buffer object to first varying (location) of GL_TRANSFORM_FEEDBACK_BUFFER - binding point index equal to 0.
 * Use the first half of the data array, 0 -> sizeof(float) * 4 * numberOfSpheresToGenerate (4 floating point position coordinates per sphere).
 * Bind buffer object to first varying (velocity) of GL_TRANSFORM_FEEDBACK_BUFFER - binding point index equal to 1.
 * Use the second half of the data array, from the end of the position data until the end of the velocity data.
 * The size of the velocity data is sizeof(float) * 4 * numberOfSpheresToGenerate values (4 floating point velocity coordinates per sphere).
 *
 * The buffer bound here is used as an output from the movement vertex shader. The output variables in the shader that are bound to this buffer are
 * given by the call to glTransformFeedbackVaryings earlier.
 */
if (usePingBufferForTransformFeedbackOutput)
{
    GL_CHECK(glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER,
                               0,
                               spherePingPositionAndVelocityBufferObjectId,
                               0,
                               sizeof(float) * 4 * numberOfSpheresToGenerate));
    GL_CHECK(glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER,
                               1,
                               spherePingPositionAndVelocityBufferObjectId,
                               sizeof(float) * 4 * numberOfSpheresToGenerate,
                               sizeof(float) * 4 * numberOfSpheresToGenerate));
}
else
{
    GL_CHECK(glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER,
                               0,
                               spherePongPositionAndVelocityBufferObjectId,
                               0,
                               sizeof(float) * 4 * numberOfSpheresToGenerate));
    GL_CHECK(glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER,
                               1,
                               spherePongPositionAndVelocityBufferObjectId,
                               sizeof(float) * 4 * numberOfSpheresToGenerate,
                               sizeof(float) * 4 * numberOfSpheresToGenerate));
}
复制代码

设置输入缓冲区对象

/*
 * The buffer bound here is used as the input to the movement vertex shader. The data is mapped to the uniform block, and as the size of the
 * arrays inside the uniform block is known, the data is mapped to the correct variables.
 */
if (usePingBufferForTransformFeedbackOutput)
{
    GL_CHECK(glBindBufferBase(GL_UNIFORM_BUFFER, 0, spherePongPositionAndVelocityBufferObjectId));
}
else
{
    GL_CHECK(glBindBufferBase(GL_UNIFORM_BUFFER, 0, spherePingPositionAndVelocityBufferObjectId));
}
复制代码

在渲染第一帧时,请确保填充原始数据的缓冲区对象用作输入。

我们现在准备调用转换反馈。

/*
 * Perform the boids transformation.
 * This takes the current boid data in the buffers and passes it through the movement vertex shader.
 * This fills the output buffer with the updated location and velocity information for each boid.
 */
GL_CHECK(glEnable(GL_RASTERIZER_DISCARD));
{
    GL_CHECK(glUseProgram(movementProgramId));
    GL_CHECK(glBeginTransformFeedback(GL_POINTS));
    {
        GL_CHECK(glUniform1f(timeLocation, timerTime));
        GL_CHECK(glDrawArraysInstanced(GL_POINTS, 0, 1, numberOfSpheresToGenerate));
    }
    GL_CHECK(glEndTransformFeedback());
}
GL_CHECK(glDisable(GL_RASTERIZER_DISCARD));
复制代码

现在我们可以使用更新的数据作为负责渲染球体的程序对象的输入。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值