OpenGL ES 2 с использованием GL_LUMINANCE с GL_FLOAT на устройствах Android

Используя Android Studio, мой код отображает массив значений с плавающей запятой в виде текстуры, передаваемой в GLSL, с одним числом с плавающей точкой на тексель в диапазоне от 0 до 1, как текстура в градациях серого. Для этого я использую GL_LUMINANCE в качестве внутреннего формата и формата для glTexImage2D и GL_FLOAT для типа. Запуск приложения на эмуляторе устройства Android работает нормально (который использует графический процессор моего ПК), но на реальном устройстве (Samsung Galaxy S7) вызов glTexImage2D дает ошибку 1282, GL_INVALID_OPERATION. Я думал, что это может быть проблема с не степенью двойки текстур, но ширина и высота, безусловно, являются степенями двойки.

В коде используется код Джоса Стама для моделирования жидкости на 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));
}

Комбинация GL_FLOAT и GL_LUMINANCE не поддерживается в OpenGL ES 2?

эмулятор Android рис.

редактировать: Чтобы добавить, я прав, говоря, что каждое значение с плавающей запятой будет уменьшено до 8-битного целочисленного компонента при передаче с помощью glTexImage2D (и т. д.), поэтому большая часть точности с плавающей запятой будет потеряна? В этом случае может быть лучше переосмыслить реализацию симулятора для вывода фиксированной точки. Это можно сделать легко, Стам даже описывает это в своей статье.


person Ryan Hackett    schedule 17.06.2017    source источник


Ответы (1)


В таблице 3.4 спецификации показаны " Допустимый формат пикселей и комбинации типов" для использования с glTexImage2D. Для GL_LUMINANCE единственным вариантом является GL_UNSIGNED_BYTE.

OES_texture_float — это соответствующее расширение, которое вам нужно проверить. .

Альтернативный подход, который будет работать на большем количестве устройств, состоит в том, чтобы упаковать ваши данные в несколько каналов RGBA. Вот некоторые обсуждение упаковки значения с плавающей запятой в 8888. Обратите внимание, однако, что не все устройства OpenGLES2 даже поддерживают цели рендеринга 8888, вам, возможно, придется упаковать в 4444.

Или вы можете использовать OpenGLES 3. Android поддерживает до 61,3% OpenGLES3 в соответствии с этим< /а>.

РЕДАКТИРОВАТЬ: при более внимательном прочтении, вероятно, нет никакой выгоды в использовании текстуры выше 8-битной, потому что, когда вы записываете текстуру в gl_FragColor в своем фрагментном шейдере, вы копируете в фреймбуфер 565 или 8888, поэтому любая дополнительная точность в любом случае теряется в этот момент.

person Columbo    schedule 17.06.2017
comment
К сожалению, S7 не поддерживает OES_texture_float. Я не могу найти страницу сейчас, но мне пришла в голову идея использовать GL_FLOAT с GL_LUMINANCE из какого-то списка рассылки. Я действительно должен был проверить документы. Однако спасибо, похоже, здесь нужно изменить формат. Я найду способ, надеюсь, тот, который не работает со скоростью 2 кадра в секунду! - person Ryan Hackett; 18.06.2017