В настоящее время я делаю плагин для Android-плеера для Unity. Основная идея заключается в том, что я буду воспроизводить видео с помощью MediaPlayer
на Android, который предоставляет API setSurface
, получающий SurfaceTexture
в качестве параметра конструктора и в конце связывающийся с текстурой OpenGL-ES. В большинстве других случаев, таких как показ изображения, мы можем просто отправить эту текстуру в виде указателя / идентификатора в Unity, вызвать Texture2D.CreateExternalTexture
там, чтобы сгенерировать объект Texture2D
, и установить его в UI GameObject
для рендеринга изображения. Однако, когда дело доходит до отображения видеокадров, это немного отличается, так как для воспроизведения видео на Android требуется текстура типа GL_TEXTURE_EXTERNAL_OES
, тогда как Unity поддерживает только универсальный тип GL_TEXTURE_2D
.
.
Чтобы решить эту проблему, я некоторое время гуглил и знал, что мне следует применить технологию, называемую «Render to texture» . Проще говоря, я должен сгенерировать 2 текстуры: одну для MediaPlayer
и SurfaceTexture
в Android для получения видеокадров, а другую для Unity, в которой также должны быть данные изображения. Первый должен иметь тип GL_TEXTURE_EXTERNAL_OES
(для краткости будем называть его текстурой OES), а второй - тип GL_TEXTURE_2D
(назовем его текстурой 2D). Обе эти сгенерированные текстуры в начале пусты. При связывании с MediaPlayer
текстура OES будет обновляться во время воспроизведения видео, тогда мы можем использовать FrameBuffer
для рисования содержимого текстуры OES на 2D текстуре.
Я написал версию этого процесса для Android, и он работает очень хорошо, когда я наконец рисую 2D-текстуру на экране. Однако, когда я опубликую его как плагин Unity для Android и запустит тот же код на Unity, не будет никаких изображений. Вместо этого отображает только предустановленный цвет от glClearColor
, что означает две вещи:
- Процесс передачи текстуры OES ->
FrameBuffer
-> 2D текстуры завершен, и Unity получает окончательную 2D текстуру. Потому что glClearColor
вызывается только тогда, когда мы рисуем содержимое текстуры OES в FrameBuffer
.
- Есть некоторые ошибки во время рисования после
glClearColor
, потому что мы не видим изображения видеокадров. На самом деле, я также вызываю glReadPixels
после рисования и перед отменой привязки к FrameBuffer
, который будет считывать данные из FrameBuffer
, с которым мы связаны. И он возвращает значение одного цвета, совпадающее с цветом, который мы установили в glClearColor
.
Чтобы упростить код, который я должен предоставить здесь, я собираюсь нарисовать треугольник для 2D текстуры через FrameBuffer
. Если мы сможем выяснить, какая часть неправильна, то мы легко сможем решить аналогичную проблему для рисования видеокадров.
Функция будет вызываться в Unity:
public int displayTriangle() {
Texture2D texture = new Texture2D(UnityPlayer.currentActivity);
texture.init();
Triangle triangle = new Triangle(UnityPlayer.currentActivity);
triangle.init();
TextureTransfer textureTransfer = new TextureTransfer();
textureTransfer.tryToCreateFBO();
mTextureWidth = 960;
mTextureHeight = 960;
textureTransfer.tryToInitTempTexture2D(texture.getTextureID(), mTextureWidth, mTextureHeight);
textureTransfer.fboStart();
triangle.draw();
textureTransfer.fboEnd();
// Unity needs a native texture id to create its own Texture2D object
return texture.getTextureID();
}
Инициализация 2D текстуры:
protected void initTexture() {
int[] idContainer = new int[1];
GLES30.glGenTextures(1, idContainer, 0);
textureId = idContainer[0];
Log.i(TAG, "texture2D generated: " + textureId);
// texture.getTextureID() will return this textureId
bindTexture();
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
unbindTexture();
}
public void bindTexture() {
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
}
public void unbindTexture() {
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
}
draw()
из Triangle
:
public void draw() {
float[] vertexData = new float[] {
0.0f, 0.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f
};
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData);
vertexBuffer.position(0);
GLES30.glClearColor(0.0f, 0.0f, 0.9f, 1.0f);
GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
GLES30.glUseProgram(mProgramId);
vertexBuffer.position(0);
GLES30.glEnableVertexAttribArray(aPosHandle);
GLES30.glVertexAttribPointer(
aPosHandle, 3, GLES30.GL_FLOAT, false, 12, vertexBuffer);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 3);
}
вершинный шейдер Triangle
:
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
}
фрагментный шейдер Triangle
:
precision mediump float;
void main() {
gl_FragColor = vec4(0.9, 0.0, 0.0, 1.0);
}
Код ключа TextureTransfer
:
public void tryToInitTempTexture2D(int texture2DId, int textureWidth, int textureHeight) {
if (mTexture2DId != -1) {
return;
}
mTexture2DId = texture2DId;
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTexture2DId);
Log.i(TAG, "glBindTexture " + mTexture2DId + " to init for FBO");
// make 2D texture empty
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, textureWidth, textureHeight, 0,
GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
Log.i(TAG, "glTexImage2D, textureWidth: " + textureWidth + ", textureHeight: " + textureHeight);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
fboStart();
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0,
GLES30.GL_TEXTURE_2D, mTexture2DId, 0);
Log.i(TAG, "glFramebufferTexture2D");
int fboStatus = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER);
Log.i(TAG, "fbo status: " + fboStatus);
if (fboStatus != GLES30.GL_FRAMEBUFFER_COMPLETE) {
throw new RuntimeException("framebuffer " + mFBOId + " incomplete!");
}
fboEnd();
}
public void fboStart() {
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOId);
}
public void fboEnd() {
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
}
И, наконец, некоторый код на стороне Unity:
int textureId = plugin.Call<int>("displayTriangle");
Debug.Log("native textureId: " + textureId);
Texture2D triangleTexture = Texture2D.CreateExternalTexture(
960, 960, TextureFormat.RGBA32, false, true, (IntPtr) textureId);
triangleTexture.UpdateExternalTexture(triangleTexture.GetNativeTexturePtr());
rawImage.texture = triangleTexture;
rawImage.color = Color.white;
Ну, код выше не будет отображать ожидаемый треугольник, а только синий фон. Я добавляю glGetError
после почти каждого вызова функций OpenGL, пока не выдается никаких ошибок.
Моя версия Unity 2017.2.1. Для сборки Android я отключил экспериментальный многопоточный рендеринг, и все остальные настройки по умолчанию (без сжатия текстур, не использовать сборку разработки и т. Д.). Минимальный уровень API моего приложения - 5,0 Lollipop, а целевой уровень API - 9,0 шт.
Мне действительно нужна помощь, заранее спасибо!