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 для моделирования жидкости Jos Stam (скомпилированный с 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 (и т. д.), поэтому большая часть точности с плавающей запятой будет потеряна? В этом случае может быть лучше переосмыслить реализацию симулятора для вывода фиксированной точки. Это может быть сделано легко, Стам даже описывает это в своей статье.
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, поэтому любая дополнительная точность в любом случае теряется.