返回

GLES2战记第五集——宇宙之光

Android

前言

在上一篇中,我们学习了如何使用GLES2实现一个简单的球面。在本篇中,我们将在这个基础上,进一步实现一个宇宙之光的效果。

理论知识

宇宙之光的效果其实很简单,它就是通过在球面上绘制一层光晕来实现的。光晕的绘制可以通过使用一种叫做“纹理贴图”的技术来实现。纹理贴图可以将一张图片贴到三维物体上,从而使三维物体看起来更加真实。

在GLES2中,纹理贴图的实现非常简单,只需要使用glTexImage2D()函数将图片数据上传到GPU,然后使用glBindTexture()函数将图片绑定到三维物体上即可。

实现代码

下面是实现宇宙之光效果的代码:

// 顶点着色器代码
attribute vec3 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;

uniform mat4 u_MVPMatrix;

void main() {
    gl_Position = u_MVPMatrix * vec4(a_Position, 1.0);
    v_TexCoord = a_TexCoord;
}

// 片元着色器代码
precision mediump float;
varying vec2 v_TexCoord;
uniform sampler2D u_Texture;

void main() {
    gl_FragColor = texture2D(u_Texture, v_TexCoord);
}

// 主函数
int main() {
    // 初始化EGL和GLES2环境
    // ...

    // 创建一个纹理对象
    GLuint textureId;
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);

    // 将图片数据上传到GPU
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 创建一个球面的顶点数据
    float radius = 1.0f;
    int numSlices = 32;
    int numStacks = 32;
    float[] vertices = new float[numSlices * numStacks * 6];
    float[] texCoords = new float[numSlices * numStacks * 6];
    int index = 0;
    for (int i = 0; i < numSlices; i++) {
        float theta = (float)i * 2.0f * Math.PI / numSlices;
        float sinTheta = (float)Math.sin(theta);
        float cosTheta = (float)Math.cos(theta);

        for (int j = 0; j < numStacks; j++) {
            float phi = (float)j * 2.0f * Math.PI / numStacks;
            float sinPhi = (float)Math.sin(phi);
            float cosPhi = (float)Math.cos(phi);

            vertices[index++] = radius * sinTheta * sinPhi;
            vertices[index++] = radius * cosPhi;
            vertices[index++] = radius * cosTheta * sinPhi;

            texCoords[index++] = (float)i / numSlices;
            texCoords[index++] = (float)j / numStacks;

            vertices[index++] = radius * sinTheta * sinPhi;
            vertices[index++] = radius * cosPhi;
            vertices[index++] = radius * cosTheta * sinPhi;

            texCoords[index++] = (float)i / numSlices;
            texCoords[index++] = (float)(j + 1) / numStacks;

            vertices[index++] = radius * sinTheta * sinPhi;
            vertices[index++] = radius * cosPhi;
            vertices[index++] = radius * cosTheta * sinPhi;

            texCoords[index++] = (float)(i + 1) / numSlices;
            texCoords[index++] = (float)(j + 1) / numStacks;

            vertices[index++] = radius * sinTheta * sinPhi;
            vertices[index++] = radius * cosPhi;
            vertices[index++] = radius * cosTheta * sinPhi;

            texCoords[index++] = (float)(i + 1) / numSlices;
            texCoords[index++] = (float)j / numStacks;
        }
    }

    // 创建一个顶点缓冲对象
    GLuint vertexBufferId;
    glGenBuffers(1, &vertexBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    glBufferData(GL_ARRAY_BUFFER, vertices.length * 4, FloatBuffer.wrap(vertices), GL_STATIC_DRAW);

    // 创建一个纹理坐标缓冲对象
    GLuint texCoordBufferId;
    glGenBuffers(1, &texCoordBufferId);
    glBindBuffer(GL_ARRAY_BUFFER, texCoordBufferId);
    glBufferData(GL_ARRAY_BUFFER, texCoords.length * 4, FloatBuffer.wrap(texCoords), GL_STATIC_DRAW);

    // 创建一个程序对象
    GLuint programId = createProgram(vertexShaderCode, fragmentShaderCode);

    // 获取顶点属性的位置
    int a_PositionLocation = glGetAttribLocation(programId, "a_Position");
    int a_TexCoordLocation = glGetAttribLocation(programId, "a_TexCoord");

    // 获取uniform变量的位置
    int u_MVPMatrixLocation = glGetUniformLocation(programId, "u_MVPMatrix");
    int u_TextureLocation = glGetUniformLocation(programId, "u_Texture");

    // 启用顶点属性数组
    glEnableVertexAttribArray(a_PositionLocation);
    glEnableVertexAttribArray(a_TexCoordLocation);

    // 绑定顶点属性数组
    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
    glVertexAttribPointer(a_PositionLocation, 3, GL_FLOAT, false, 0, 0);
    glBindBuffer(GL_ARRAY_BUFFER, texCoordBufferId);
    glVertexAttribPointer(a_TexCoordLocation, 2, GL_FLOAT, false, 0, 0);

    // 激活纹理单元
    glActiveTexture(GL_TEXTURE0);

    // 绑定纹理对象
    glBindTexture(GL_TEXTURE_2D, textureId);

    // 设置uniform变量
    glUniformMatrix4fv(u_MVPMatrixLocation, 1, false, mvpMatrix, 0);
    glUniform1i(u_TextureLocation, 0);

    // 绘制球面
    glDrawArrays(GL_TRIANGLES, 0, numSlices * numStacks * 6);

    // 禁用顶点属性数组
    glDisableVertexAttribArray(a_PositionLocation);
    glDisableVertexAttribArray(a_TexCoordLocation);

    // 解绑纹理对象
    glBindTexture(GL_TEXTURE_2D, 0);

    // 删除纹理对象
    glDeleteTextures(1, new int[]{textureId});

    // 删除缓冲对象
    glDeleteBuffers(1, new int[]{vertexBufferId, texCoordBufferId});

    // 删除程序对象
    glDeleteProgram(programId);

    // 退出EGL和GLES2环境
    // ...
}

运行结果

运行代码后,你将看到一个旋转的球面,球面上有一个发光的光晕。

总结

本篇中,我们学习了如何使用GLES2实现一个宇宙之光的效果。我们首先介绍了纹理贴图的技术,然后给出了实现代码。最后,我们展示了运行结果。