Русский
Русский
English
Статистика
Реклама

Перевод Как можно использовать шейдеры в Android View и как Android View может использовать шейдеры

Перевод статьи подготовлен в преддверии старта курсов "Android Developer. Basic" и "Android Developer. Professional".


Для полноценной работы с динамическими элементами пользовательского интерфейса, которые используют тени, фильтры в реальном времени для фото или видео, или адаптивный пользовательский интерфейс и освещение, недостаточно использовать только Canvas. Было бы куда лучше, если бы у нас в распоряжении было что-то помощнее. Раньше мы могли использовать RenderScript, но поддерживается ли он сейчас?

В этой статье я расскажу как использовать стандартные GLSL шейдеры OpenGL в вашем пользовательском view, которое является наследником класса Android View (android.view.View). Я предлагаю вам использовать это решение, если вы работаете над чем-нибудь из нижеперечисленного:

  • Шейдеры или коррекция цвета в реальном времени для видеопотоков.

  • Динамические тени и освещение для кастомных элементов пользовательского интерфейса.

  • Продвинутая попиксельная анимация.

  • Какие-либо эффекты пользовательского интерфейса, наподобие размытия (blurring), искажения (distortion), пикселизации и т. д.

  • Если вы создаете новый нейроморфный адаптивный пользовательский интерфейс.

Это решение предоставит вам надежную среду и множество примеров шейдеров, которые вы можете легко использовать в своем приложении. И я покажу вам, как легко это сделать!

Идея

Нам нужно, чтобы в нашем стандартном лэйауте лежал класс, который ведет себя так же, как Android View (android.view.View), и мы cможем использовать фрагментный шейдер OpenGL для визуализации его содержимого.

Демо

Демо-приложение с несколькими ShaderViews. Динамический свет и видео фильтры.

Как это работает на абстрактном примере

Предположим, мы хотим заказать у одного известного художника новую картину, написанную волшебными красками, и повесить ее на стену. Что мы имеем в нашей ситуации:

  • Волшебные краски GLSL шейдеры OpenGL.

  • Холст четырехугольник, который заполнит все пространство нашего кастомного view.

  • Известный художник класс, реализующий интерфейс Render. Этот художник, в свою очередь, использует волшебные краски, чтобы нарисовать картину на холсте.

  • Картина кастомный view-класс, который задействует художника с его/ее холстом и волшебными красками.

  • Стена Activity или Fragment android.

Как это работает с технической точки зрения

  1. Давайте выберем родительский view для нашего кастомного view-класса (кстати, мы назовем наш view-класс ShaderView). Тут у нас есть два варианта: SurfaceView и TextureView. Я вернусь к разнице между ними через пару мгновений.

  2. Создадим класс Render, который будет отображать view с использованием шейдеров.

  3. Создадим 3D-модель четырехугольника (quadrangle), который заполнит все пространство view (3D, поскольку OpenGL был создан для 3D-сцен). Не беспокойтесь об этом; это стандартное решение, и с ним не связано никаких трудностей.

Четырехугольник OpenGL внутри TextureView.Четырехугольник OpenGL внутри TextureView.

SurfaceView или TextureView

SurfaceView и TextureView оба наследуются от класса Android View, но между ними есть некоторые различия.

На сегодняшний день SurfaceView имеет класс наследник, который отлично работает с OpenGL и обеспечивает отличную производительность. Этот класс называется GLSurfaceView. Но главная проблема этого класса в том, что мы не можем перекрывать один GLSurfaceView другим. Следовательно, мы не можем использовать его в нашей иерархии лейаутов, и мы не можем преобразовывать, анимировать или масштабировать view таким образом.

TextureView ведет себя как обычный android.view.View, и вы можете анимировать, преобразовывать, масштабировать или даже наслаивать его с другими экземплярами. Но это преимущество дается на ценой потребления большего количества памяти, чем SurfaceView, и вы теряете в производительности (в среднем 13 кадра).

Возвращаясь к сути вопроса, поскольку мы хотели, чтобы наше кастомное view вело себя как обычное view Android, мы должны использовать TextureView.

Следующая проблема для нас заключается в том, что нет встроенного класса, который использует OpenGL render и TextureView. Но не спешите расстраиваться GLSurfaceView подходит как раз для того, что нам нужно, но только с SurfaceView, поэтому давайте поразмыслим о том, как мы можем использовать этот класс для нашего собственного GLTextureView.

Создание GLTextureView

Итак, в GLSurfaceView есть почти все, что нам нужно для рендеринга OpenGL. Нам всего лишь нужно скопировать пригодный код в наш класс и внести некоторые изменения.

  1. Создайте новый класс GLTextureView.kt, который наследуется от TextureView и расширяет TextureView.SurfaceTextureListener и View.OnLayoutChangeListener. Добавьте конструкторы.

open class GLTextureView @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = 0) :    TextureView(context, attrs, defStyleAttr),    TextureView.SurfaceTextureListener,    View.OnLayoutChangeListener {}

GLTextureView.kt на GitHub

5. Обновите метод finalize() до стандарта Kotlin. (Если у вас есть лучшее решение, напишите в комментариях).

6. Замените SurfaceHolder на SurfaceTexture.

7. Замените все упоминания GLSurfaceView на GLTextureView.

8. Обновите импорты, исключая использование GLSurfaceView. Также проверьте оставшиеся импорты и удалите все, что связано с GLSurfaceView.

9. Устранение проблемы с допустимостью нулевых значений после автоматического преобразования кода Java в Kotlin. В моем случае мне пришлось обновить методы переопределения и некоторые параметры, допускающие значение NULL (например, egl: EGL10 должно быть egl: EGL10?).

10. Переместите константы в объект-компаньон или на верхний уровень.

11. Удалите неподдерживаемые аннотации.

12. Добавьте методы интерфейса SurfaceTextureListener.

 override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {        surfaceCreated(surface)        surfaceChanged(surface, 0, width, height)    }    override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {        surfaceChanged(surface, 0, width, height)    }    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {        surfaceDestroyed(surface)        return true    }    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {    }

GLTextureView.kt на GitHub

13. В createSurface() вы наткнетесь на неработающую строчку, замените view.holder на view.surfaceTexture.

14. Переопределите onLayoutChange.

 override fun onLayoutChange(        v: View?, left: Int, top: Int,        right: Int,        bottom: Int,        oldLeft: Int,        oldTop: Int,        oldRight: Int,        oldBottom: Int    ) {        surfaceChanged(surfaceTexture, 0, right - left, bottom - top)    }

GLTextureView.kt на GitHub

В результате у вас получится что-то вроде этого.

Расширения

Чтобы облегчить себе работу, мы создадим расширения, которые загружают исходный код шейдера из каталога ресурсов.

fun Resources.getRawTextFile(@RawRes resource: Int): String =   openRawResource(resource).bufferedReader().use { it.readText() }

extensions.kt на GitHub

Код шейдеров

В этой статье мы будем использовать самые простые шейдеры, чтобы сделать наш код менее сложным. Если вас интересуют более сложные шейдеры, вы можете найти их здесь или здесь.

Вершинный шейдер (Vertex Shader)

Для наших целей нам достаточно простого вершинного шейдера для рендеринга нашего четырехугольника (мы не потратим кучу времени на его код).

#version 300 esuniform mat4 uMVPMatrix;uniform mat4 uSTMatrix;in vec3 inPosition;in vec2 inTextureCoord;out vec2 textureCoord;void main() {   gl_Position = uMVPMatrix * vec4(inPosition.xyz, 1);   textureCoord = (uSTMatrix * vec4(inTextureCoord.xy, 0, 0)).xy;}

vertex.vsh на GitHub

Фрагментный/пиксельный шейдер (Fragment Shader)

Код довольно прост, но давайте посмотрим, что у нас здесь есть.

Прежде всего, мы определяем версию GLSL.

#version 300 es

Затем мы определяем пользовательские параметры, которые мы собираемся отправить шейдеру.

uniform vec4 uMyUniform;

Определяем параметры ввода и вывода для нашего фрагментного шейдера. In что мы получаем от вершинного шейдера (в нашем случае координаты текстуры), а out что отправляем в результате (цвет пикселя).

in vec2 textureCoord;out vec4 fragColor;

Теперь напишем функцию, которая будет выполняться для каждого пикселя нашего Android View и возвращать его цвет.

void main() {   fragColor = vec4(textureCoord.x, textureCoord.y, 1.0, 1.0) * uMyUniform;}

В результате мы получим следующее:

#version 300 esprecision mediump float;uniform vec4 uMyUniform;in vec2 textureCoord;out vec4 fragColor;void main() {    fragColor = vec4(textureCoord.x, textureCoord.y, 1.0, 1.0) * uMyUniform;}

fragment_shader.fsh на GitHub

QuadRender

Следующий класс, который нам понадобится, это класс рендеринга. Этот класс будет отрисовывать четырехугольник размера ShaderView с помощью шейдеров.

Четырехугольник OpenGL в проекции камеры. Камера это точка зрения пользователя, который смотрит на устройство.

Наш класс должен расширить интерфейс GLTextureView.Renderer тремя методами:

onSurfaceCreated() Создает программу шейдера, связывает некоторые параметры формы (uniform) и отправляет атрибуты в вершинный шейдер.

onDrawFrame() Обновление на каждом кадре. В этом методе мы отрисовываем четырехугольник экрана и при необходимости обновляем параметры формы.

onSurfaceChanged() Обновляет вьюпорт.

Итак, давайте будем писать код шаг за шагом. Я не буду вдаваться в подробное описание того, как работает OpenGL, потому что это выходит за рамки данной статьи. Я также хочу упомянуть, что мы фокусируемся только на фрагментном шейдере и не касаемся деталей вершинного шейдера, так как они должны быть одинаковыми практически для любых возможных требований фрагментного шейдера.

Определите константы.

private const val FLOAT_SIZE_BYTES = 4 // размер Floatprivate const val TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES // 5 floatов для каждой вершины (3 floatа на позицию и 2 на координаты текстуры)private const val TRIANGLE_VERTICES_DATA_POS_OFFSET = 0 // позиция начинается с начала массива каждой вершиныprivate const val TRIANGLE_VERTICES_DATA_UV_OFFSET = 3 // координаты текстуры начиная с 3-го floatа (4-й и 5-й floatы)// атрибуты вершинного шейдераconst val VERTEX_SHADER_IN_POSITION = "inPosition"const val VERTEX_SHADER_IN_TEXTURE_COORD = "inTextureCoord"const val VERTEX_SHADER_UNIFORM_MATRIX_MVP = "uMVPMatrix"const val VERTEX_SHADER_UNIFORM_MATRIX_STM = "uSTMatrix"const val FRAGMENT_SHADER_UNIFORM_MY_UNIFORM = "uMyUniform"private const val UNKNOWN_PROGRAM = -1private const val UNKNOWN_ATTRIBUTE = -1

QuadRender.kt на GitHub

Две переменные, которые будут содержать исходный код наших вершинного и фрагментного шейдеров.

private var vertexShaderSource : String, // исходный код вершинного шейдераprivate var fragmentShaderSource : String, // исходный код фрагментного шейдераQuadRender.kt на GitHub

Определите список вершин для буфера вершин.

private val quadVertices: FloatBufferinit {// задаем массив вершин четырехугольникаval quadVerticesData = floatArrayOf(// [x,y,z, U,V]-1.0f, -1.0f, 0f, 0f, 1f,1.0f, -1.0f, 0f, 1f, 1f,-1.0f, 1.0f, 0f, 0f, 0f,1.0f, 1.0f, 0f, 1f, 0f)quadVertices = ByteBuffer.allocateDirect(quadVerticesData.size * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer().apply {put(quadVerticesData).position(0)}}

QuadRender.kt на GitHub

Определите матрицы.

private val matrixMVP = FloatArray(16)private val matrixSTM = FloatArray(16)

QuadRender.kt на GitHub

И добавить инициализацию в init{} блок.

init {// код, который мы добавили ранееMatrix.setIdentityM(matrixSTM, 0)}

QuadRender.kt на GitHub

Вершинный шейдер, атрибуты вершин и расположение матриц.

private var inPositionHandle = UNKNOWN_ATTRIBUTEprivate var inTextureHandle = UNKNOWN_ATTRIBUTEprivate var uMVPMatrixHandle = UNKNOWN_ATTRIBUTEprivate var uSTMatrixHandle = UNKNOWN_ATTRIBUTEprivate var uMyUniform = UNKNOWN_ATTRIBUTE

QuadRender.kt на GitHub

Локатор программы шейдера.

private var program = UNKNOWN_PROGRAM

QuadRender.kt на GitHub

Отлично, мы закончили с инициализацией. Теперь давайте напишем метод onSurfaceCreated(). Мы загрузим и инициализируем наши шейдеры и получим указатели для атрибутов, включая параметр формы uMyUniform, который мы будем использовать для отправки некоторых пользовательских векторных данных во фрагментный шейдер.

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {//создаем программу шейдера из исходного кодаcreateProgram(vertexShaderSource, fragmentShaderSource)// связываем вектор атрибутов шейдераinPositionHandle = GLES20.glGetAttribLocation(program, VERTEX_SHADER_IN_POSITION)checkGlError("glGetAttribLocation $VERTEX_SHADER_IN_POSITION")if (inPositionHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get attrib location for $VERTEX_SHADER_IN_POSITION")inTextureHandle = GLES20.glGetAttribLocation(program, VERTEX_SHADER_IN_TEXTURE_COORD)checkGlError("glGetAttribLocation $VERTEX_SHADER_IN_TEXTURE_COORD")if (inTextureHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get attrib location for $VERTEX_SHADER_IN_TEXTURE_COORD")uMVPMatrixHandle = GLES20.glGetUniformLocation(program, VERTEX_SHADER_UNIFORM_MATRIX_MVP)checkGlError("glGetUniformLocation $VERTEX_SHADER_UNIFORM_MATRIX_MVP")if (uMVPMatrixHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get uniform location for $VERTEX_SHADER_UNIFORM_MATRIX_MVP")uSTMatrixHandle = GLES20.glGetUniformLocation(program, VERTEX_SHADER_UNIFORM_MATRIX_STM)checkGlError("glGetUniformLocation $VERTEX_SHADER_UNIFORM_MATRIX_STM")if (uSTMatrixHandle == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get uniform location for $VERTEX_SHADER_UNIFORM_MATRIX_STM")// (!) связываем атрибуты фрагментного шейдераuMyUniform = GLES30.glGetUniformLocation(program, FRAGMENT_SHADER_UNIFORM_MY_UNIFORM)checkGlError("glGetUniformLocation $FRAGMENT_SHADER_UNIFORM_MY_UNIFORM")if (uMyUniform == UNKNOWN_ATTRIBUTE) throw RuntimeException("Could not get uniform location for $FRAGMENT_SHADER_UNIFORM_MY_UNIFORM")}

QuadRender.kt на GitHub

Обратите внимание на последние три строки, где мы получаем расположение нашей кастомной формы (uMyUniform) для фрагментного шейдера. Для более сложных шейдеров нам придется добавить больше таких параметров.

В onSurfaceCreated() мы использовали специальные методы для создания и связывания программы.

/*** Создаем программу шейдера из исходного кода вершинного и фрагментного шейдера*/private fun createProgram(vertexSource: String, fragmentSource: String): Boolean {if (program != UNKNOWN_PROGRAM) {// удаляем программуGLES30.glDeleteProgram(program)program = UNKNOWN_PROGRAM}// загружаем вершинный шейдерval vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource)if (vertexShader == UNKNOWN_PROGRAM) {return false}// загружаем фрагментный шейдерval pixelShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentSource)if (pixelShader == UNKNOWN_PROGRAM) {return false}program = GLES30.glCreateProgram()if (program != UNKNOWN_PROGRAM) {GLES30.glAttachShader(program, vertexShader)checkGlError("glAttachShader: vertex")GLES30.glAttachShader(program, pixelShader)checkGlError("glAttachShader: pixel")return linkProgram()}return true}private fun linkProgram(): Boolean {if (program == UNKNOWN_PROGRAM) {return false}GLES30.glLinkProgram(program)val linkStatus = IntArray(1)GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0)if (linkStatus[0] != GLES30.GL_TRUE) {Log.e(TAG, "Could not link program: ")Log.e(TAG, GLES30.glGetProgramInfoLog(program))GLES30.glDeleteProgram(program)program = UNKNOWN_PROGRAMreturn false}return true}private fun loadShader(shaderType: Int, source: String): Int {var shader = GLES30.glCreateShader(shaderType)if (shader != UNKNOWN_PROGRAM) {GLES30.glShaderSource(shader, source)GLES30.glCompileShader(shader)val compiled = IntArray(1)GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0)if (compiled[0] == UNKNOWN_PROGRAM) {Log.e(TAG, "Could not compile shader $shaderType:")Log.e(TAG, GLES30.glGetShaderInfoLog(shader))GLES30.glDeleteShader(shader)shader = UNKNOWN_PROGRAM}}return shader}private fun checkGlError(op: String) {var error: Intwhile (GLES30.glGetError().also { error = it } != GLES30.GL_NO_ERROR) {Log.e(TAG, "$op: glError $error")throw RuntimeException("$op: glError $error")}

QuadRender.kt на GitHub

Следующий метод, который мы должны реализовать, это onDrawFrame().

override fun onDrawFrame(gl: GL10?) {// очищаем наш "экран"GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT or GLES30.GL_COLOR_BUFFER_BIT)// используем программуGLES30.glUseProgram(program)// устанавливаем ввод шейдера (встроенные атрибуты)setAttribute(inPositionHandle, VERTEX_SHADER_IN_POSITION, 3, TRIANGLE_VERTICES_DATA_POS_OFFSET) // 3 потому что 3 floatа на позициюsetAttribute(inTextureHandle, VERTEX_SHADER_IN_TEXTURE_COORD, 2, TRIANGLE_VERTICES_DATA_UV_OFFSET) // 2 потому что 2 floatа на координаты текстуры// обновляем матрицуMatrix.setIdentityM(matrixMVP, 0)GLES30.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, matrixMVP, 0)GLES30.glUniformMatrix4fv(uSTMatrixHandle, 1, false, matrixSTM, 0)// (!) обновляем формы для фрагментного шейдераval uMyUniformValue = floatArrayOf(1.0f, 0.75f, 0.95f, 1f) // некоторые значения, которые мы собираемся передать фрагментному шейдеруGLES30.glUniform4fv(uMyUniform, 1, uMyUniformValue, 0)// активируем смешивание текстур (для поддержки прозрачности)GLES30.glBlendFunc(GLES30.GL_SRC_ALPHA, GLES30.GL_ONE_MINUS_SRC_ALPHA)GLES30.glEnable(GLES20.GL_BLEND)// отрисовываем наши четырехугольникиGLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)checkGlError("glDrawArrays")GLES30.glFinish()}

QuadRender.kt на GitHub

Обратите внимание на строки, в которых мы отправляем кастомное значение (uMyUniformValue) в форму (uMyUniform) во фрагментный шейдер.

И последнее, surfaceChange() довольно простой метод.

override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {GLES30.glViewport(0, 0, width, height)}

QuadRender.kt на GitHub

Полный код этого класса вы можете найти здесь.

ShaderView

Отлично, все, что нам нужно для нашего ShaderView, готово. Теперь мы можем использовать мощь фрагментного шейдера для рендеринга его содержимого! Создадим ShaderView.

private const val OPENGL_VERSION = 3class ShaderView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0) :GLTextureView(context, attrs, defStyleAttr) {init {// определяем версию OpenGLsetEGLContextClientVersion(OPENGL_VERSION)// загружаем исходный код шейдеров из файловval vsh = context.resources.getRawTextFile(R.raw.vertex_shader)val fsh = context.resources.getRawTextFile(R.raw.fragment_shader)// устанавливаем рендерерsetRenderer(QuadRender(vertexShaderSource = vsh, fragmentShaderSource = fsh))// устанавливаем режим рендеринга RENDERMODE_WHEN_DIRTY или RENDERMODE_CONTINUOUSLYsetRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY) // или GLSurfaceView.RENDERMODE_CONTINUOUSLY если нужно обновлять его на каждом кадре}}

ShaderView.kt на GitHub

Дополнительно: Использование текстур в фрагментных шейдерах

Приятно, что мы научились общаться и работать с фрагментными шейдерами, но во многих случаях вам приходится иметь дело с текстурами (например, для размытия или других видеоэффектов).

Вам нужно определить форму во фрагментном шейдере как sampler2D и получить текущий пиксель текстуры по координатам текстуры с помощью метода texture() из GLSL.

Вот полный код шейдера.

#version 300 esprecision mediump float;uniform sampler2D uTexture;in vec2 textureCoord;out vec4 fragColor;void main() {fragColor = texture(uTexture, textureCoord);}

fragment_texture_shader.fsh на GitHub

Затем нам понадобятся два расширения для загрузки и использования растрового изображения в качестве текстур OpenGL.

fun Resources.loadBitmapForTexture(@DrawableRes drawableRes: Int): Bitmap {val options = BitmapFactory.Options()options.inScaled = false // по умолчанию true. false, если нам нужно масштабируемое изображение// загрузка из ресурсовreturn BitmapFactory.decodeResource(this, drawableRes, options)}/*** Загрузка текстуры из Bitmap и запись ее в видеопамять* @needToRecycle - нужно ли нам повторно использовать текущий Bitmap, когда пишем это GPI?*/@Throws(RuntimeException::class)fun Bitmap.toGlTexture(needToRecycle: Boolean = true, textureSlot: Int = GLES30.GL_TEXTURE0): Int {// инициализация текстурыval textureIds = IntArray(1)GLES30.glGenTextures(1, textureIds, 0) // генерируем ID для текстурыif (textureIds[0] == 0) {throw java.lang.RuntimeException("It's not possible to generate ID for texture")}   GLES30.glActiveTexture(textureSlot) // активируем слот #0 для текстурыGLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds[0]) // привязываем текстуру по ID к активному слоту// фильтры текстурыGLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR)GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)// записываем растровое изображение в GPUGLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, this, 0)// нам больше не нужно это растровое изображениеif (needToRecycle) {this.recycle()}// отвязываем текстуру от слотаGLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)return textureIds[0]}

extensions.kt на GitHub

Теперь мы готовы загрузить текстуру из каталога ресурсов в виде растрового изображения (bitmap), используя loadBitmapForTexture(), а затем метод QuadRender.onSurfaceCreated(). Мы привяжем текстуру к слоту текстуры OpenGL (доступны слоты от GL_TEXTURE0 до GL_TEXTURE31).

Не забывайте утилизировать растровое изображение, когда оно вам больше не нужно.

uTextureHandle = GLES30.glGetUniformLocation(program, FRAGMENT_SHADER_UNIFORM_TEXTURE)uTextureId = textureBitmap.toGlTexture(needToRecycle = true, GLES30.GL_TEXTURE0)

QuadRender.kt на GitHub

После этого, мы устанавливаем эту текстуру в качестве активной и видимой для фрагментного шейдера в QuadRender.onDrawFrame().

Полный код примера использования текстуры вы можете найти в этой ветке.

GLES30.glUniform1i(uTextureHandle, 0) // 0 as far as it's slot number 0// 0 если номер слота 0GLES30.glActiveTexture(GLES30.GL_TEXTURE0) // тот же слот текстуры, который мы использовали при инициализацииGLES30.glBindTexture(GLES30.GL_TEXTURE_2D, uTextureId)

QuadRender.kt на GitHub

Ссылки

Исходный код этой статьи можно найти в моем репозитории.

С библиотекой ShaderView с помощью дружественного высокоуровневого API можно познакомиться здесь.


Узнать подробнее о курсах: "Android Developer. Basic" / "Android Developer. Professional".

Также предлагаем посмотреть вебинары:

1)
Рисуем свой график котировок в Android:
- Рассмотрим основные инструменты для рисования
- Изучим возможности классов Canvas, Path, Paint
- Нарисуем кастомизируемый график котировок и добавим в него анимаций

2)
Крестики-нолики на минималках Игра на Android менее чем за 2 часа, использующийся язык Kotlin.

Источник: habr.com
К списку статей
Опубликовано: 22.03.2021 18:15:22
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании otus

Программирование

Разработка под android

Kotlin

Android development

Java

Shaders

Opengl

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru