0

使用 Android Studio,我的代码将浮点数组渲染为传递给 GLSL 的纹理,每个纹理像素一个浮点数在 0 到 1 的范围内,就像灰度纹理一样。为此,我使用 GL_LUMINANCE 作为 glTexImage2D 的 internalFormat 和格式,使用 GL_FLOAT 作为类型。在 android 设备模拟器上运行应用程序可以正常工作(它使用我的 PC 的 GPU),但在真实设备(三星 Galaxy S7)上调用 glTexImage2D 会出现错误 1282,GL_INVALID_OPERATION。我认为这可能是两个纹理的非幂的问题,但宽度和高度肯定是二的幂。

该代码使用输出网格密度值的Jos Stam 流体模拟 C 代码(使用 NDK 编译,未移植)。

mSizeN 是流体模拟网格的宽度(与高度相同),虽然流体模拟为边界条件在其上加了 2,所以返回的数组的宽度为 mSizeN + 2;在这种情况下为 128。

坐标系设置为正投影,屏幕左上角为 0.0,0.0,右下角为 1.0,1.0。我只是绘制了一个全屏四边形,并使用 GLSL 中四边形的插值位置作为包含密度值的数组的纹理坐标。渲染它的好方法。

这是渲染器类。

public class GLFluidsimRenderer implements GLWallpaperService.Renderer {

    private final String TAG = "GLFluidsimRenderer";

    private FluidSolver mFluidSolver = new FluidSolver();

    private float[] mProjectionMatrix = new float[16];

    private final FloatBuffer mFullScreenQuadVertices;

    private Context mActivityContext;

    private int mProgramHandle;
    private int mProjectionMatrixHandle;
    private int mDensityArrayHandle;
    private int mPositionHandle;
    private int mGridSizeHandle;

    private final int mBytesPerFloat = 4;
    private final int mStrideBytes = 3 * mBytesPerFloat;
    private final int mPositionOffset = 0;
    private final int mPositionDataSize = 3;

    private int mDensityTexId;

    public static int mSizeN = 126;

    public GLFluidsimRenderer(final Context activityContext) {
        mActivityContext = activityContext;

        final float[] fullScreenQuadVerticesData = {
                0.0f, 0.0f, 0.0f,
                0.0f, 1.0f, 0.0f,
                1.0f, 0.0f, 0.0f,
                1.0f, 1.0f, 0.0f,
        };

        mFullScreenQuadVertices = ByteBuffer.allocateDirect(fullScreenQuadVerticesData.length * mBytesPerFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mFullScreenQuadVertices.put(fullScreenQuadVerticesData).position(0);
    }

    public void onTouchEvent(MotionEvent event) {

    }

    @Override
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);

        String vertShader = AssetReader.getStringAsset(mActivityContext, "fluidVertShader");
        String fragShader = AssetReader.getStringAsset(mActivityContext, "fluidFragDensityShader");

        final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertShader);
        final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragShader);

        mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
                new String[] {"a_Position"});

        mDensityTexId = TextureHelper.loadTextureLumF(mActivityContext, null, mSizeN + 2, mSizeN + 2);
    }


    @Override
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {
        mFluidSolver.init(width, height, mSizeN);

        GLES20.glViewport(0, 0, width, height);
        Matrix.setIdentityM(mProjectionMatrix, 0);
        Matrix.orthoM(mProjectionMatrix, 0, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f);
    }

    @Override
    public void onDrawFrame(GL10 glUnused) {
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

        GLES20.glUseProgram(mProgramHandle);

        mFluidSolver.step();

        TextureHelper.updateTextureLumF(mFluidSolver.get_density(), mDensityTexId, mSizeN + 2, mSizeN + 2);

        mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_ProjectionMatrix");
        mDensityArrayHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_aDensity");
        mGridSizeHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_GridSize");
        mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");

        double start = System.nanoTime();
        drawQuad(mFullScreenQuadVertices);
        double end = System.nanoTime();
    }

    private void drawQuad(final FloatBuffer aQuadBuffer) {
        // Pass in the position information
        aQuadBuffer.position(mPositionOffset);
        GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, aQuadBuffer);
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Attach density array to texture unit 0
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mDensityTexId);
        GLES20.glUniform1i(mDensityArrayHandle, 0);

        // Pass in the actual size of the grid.
        GLES20.glUniform1i(mGridSizeHandle, mSizeN + 2);

        GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }
}

这是纹理辅助函数。

public static int loadTextureLumF(final Context context, final float[] data, final int width, final int height) {
    final int[] textureHandle = new int[1];

    GLES20.glGenTextures(1, textureHandle, 0);

    if (textureHandle[0] != 0) {
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

        GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
        GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1);

        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
                (int) width, (int) height, 0, GLES20.GL_LUMINANCE, GLES20.GL_FLOAT,
                (data != null ? FloatBuffer.wrap(data) : null));
    }

    if (textureHandle[0] == 0)
        throw new RuntimeException("Error loading texture.");

    return textureHandle[0];
}

public static void updateTextureLumF(final float[] data, final int texId, final int w, final int h) {
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
    GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, (int)w, (int)h, GLES20.GL_LUMINANCE, GLES20.GL_FLOAT, (data != null ? FloatBuffer.wrap(data) : null));
}

片段着色器。

precision mediump float;

uniform sampler2D u_aDensity;
uniform int u_GridSize;

varying vec4 v_Color;
varying vec4 v_Position;

void main()
{
    gl_FragColor = texture2D(u_aDensity, vec2(v_Position.x, v_Position.y));
}

OpenGL ES 2 不支持 GL_FLOAT 和 GL_LUMINANCE 的组合吗?

安卓模拟器图片。

编辑:补充一下,我是否正确地说,当使用 glTexImage2D (等)传输时,每个浮点值将减少为 8 位整数分量,所以大部分浮点精度会丢失?在这种情况下,最好重新考虑模拟器的实现以输出定点。这很容易做到,Stam 甚至在他的论文中进行了描述。

4

1 回答 1

0

规范的表 3.4显示了用于 glTexImage2D 的“有效像素格式和类型组合”。对于 GL_LUMINANCE,唯一的选项是 GL_UNSIGNED_BYTE。

OES_texture_float是您需要检查的相关扩展。

另一种适用于更多设备的替代方法是将数据打包到 RGBA 的多个通道中。是一些关于将浮点值打包到 8888 中的讨论。但是请注意,并非所有 OpenGLES2 设备甚至都支持 8888 渲染目标,您可能必须打包到 4444 中。

或者您可以使用 OpenGLES 3。据此,Android 对 OpenGLES3 的支持率高达 61.3% 。

编辑:在更仔细地重新阅读时,使用高于 8 位的纹理可能没有任何好处,因为当您在片段着色器中将纹理写入 gl_FragColor 时,您正在复制到 565 或 8888 帧缓冲区中,所以在那一点上,任何额外的精度都会丢失。

于 2017-06-17T20:26:56.470 回答