Хотела поделиться своим небольшим опытом оптимизации шейдеров на IOS, и по возможности услышать дельные советы на этот счет. Вроде бы есть прекрасный инструмент OpenGl ES 2.0, и можно сделать неплохие эффекты, но при этом получить более-менее вменяемый fps не всегда получается.
Начала с простого. Сделана небольшая программка построения бесконечного газопровода, обходя препятствия и подключая объекты. Задача – отрисовать газ, заполняющий трубу. Программирую я на Cocos2d, материалов по шейдерам там не очень-то много, но по большому счету, можно легко пристраивать любые примеры доступные для ios и андроида. Небольшое отличие, которое вносит непосредственно Cocos2d, — это передача координат вершинных шейдеров в экранных размерах, а не в диапазоне [-1.0, 1.0]. Это весьма удобно.
Для конвертации диапазонов кокос сам формирует матрицу CC_MVPMatrix, и добавляет ее в вертексный шейдер.
За основу газа взят шейдер, который комбинирует цвета на основе входящей текстуры шума Перлина.
precision mediump float; varying vec4 Position; varying vec2 v_texCoord; uniform float Offset; uniform sampler2D uTextNoise; void main (void) { vec4 noisevec; vec3 color; float intensity; vec3 FireColor1 = vec3(0.5, 0.7, 0.8); vec3 FireColor2 = vec3(0.1, 0.3, 0.8); noisevec = texture2D(uTextNoise, Position.xy); noisevec = texture2D(uTextNoise, vec2 (Position.x+noisevec[1]+Offset, Position.y-noisevec[2]+Offset)); intensity = 1.5 * (noisevec[0] + noisevec[1] + noisevec[2] + noisevec[3]); intensity = 1.95 * abs(intensity - 0.35); intensity = clamp(intensity, 0.0, 1.0); color = mix(FireColor1, FireColor2, intensity) * 1.8; gl_FragColor = vec4(color,1.0); }
Первоначально, я планировала сгенерить газ на весь экран, а далее вырезать нужную облать для рендеринга. Проблема в том, что такой простенький шейдер снижал fps до 22 кадров на iphone4. На других устройствах производительность была лучше, но не брилиантли, что называется.
Наиболее подходящим выходом в данной ситуации является генерация небольшой промежуточной текстуры в фреймбуфере. Эту текстуру в дальнейшем можно растянуть или размножить, установив соответствующие параметры текстуры. Этот способ можно использовать для генерации воды, тумана, газа.
Создание фреймбуфера в кокосе:
-(void) createFBO { GLint _oldFBO; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO); ccGLBindTexture2DN(0, texOut.name); glGenFramebuffers(1, &_noiseFBO); glBindFramebuffer(GL_FRAMEBUFFER, _noiseFBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texOut.name, 0); glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO); glActiveTexture(GL_TEXTURE0); }
Отрисовка будет выглядеть примерно так:
-(void)draw { ccGLBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLint _oldFBO; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO); glBindFramebuffer(GL_FRAMEBUFFER, _noiseFBO); [shaderProgramNoise use]; [shaderProgramNoise setUniformsForBuiltins]; glUniform1f(quOffset0, offset); ccGLBindTexture2DN(0, texNoise.name); ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position|kCCVertexAttribFlag_TexCoords); glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertexArr); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, vertexTex); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO); [shaderProgramAlpha use]; [shaderProgramAlpha setUniformsForBuiltins]; ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position|kCCVertexAttribFlag_TexCoords); ccGLBindTexture2DN(0, texOut.name); ccGLBindTexture2DN(1, texTemplate.name); glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertexArrOut); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, vertexTexOut); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glActiveTexture(GL_TEXTURE0); }
В отрисовке мы сначала подключаем фрейм буффер, в котором shaderProgramNoise формирует текстуру texNoise маленького размера.
Далее в рендер буффере shaderProgramAlpha отрисовывает обработку двух текстур: газа и трафарета. Текстуру трафарета первоначально я отрисовывала в другой фрейм буфер, используя картинки полных и частичных труб.
Fps значительно улучшился. Тем не менее, полученный вариант был вовсе не оптимальным. Как выяснилось, увеличение количества frambuffer’ов (! хотя их всего было 2!) нагружает систему, и fps сразу падает на десяток. Понятное дело, что такая драматическая ситуация наблюдалась только на iphone4, тем не менее это надо учитывать при разработке и не плодить дополнительные frambuffer’ы без необходимости.
Хорошим решением было бы разместить трафаретную текстуру в stencil буффер frameBuffera c газом. Там где значение стенсила 0, не происходит вызов пиксельного шейдера. Однако, неприятным моментом в использовании stencil- буфферов является невозможность запихать туда текстуру. Т.е. рисовать туда придется треугольниками – а это полное ФУ (для данной задачи). Но раз уж я упоминула о stencil-буффере, скажу, что все-таки запихать туда текстуру мне удалось. Поскольку вызов glDrawArrays для stencil’а тоже использует шейдерную программу, то используя в этой программе discard для прозрачных областей, мы все-таки можем получить там трафарет текстуры. Если кому-то интересно смотрите такой способ:
-(void)draw { glDisable(GL_DEPTH_TEST); ccGLBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(0.0/255.0, 200.0/255.0, 245.0/255.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClearStencil(0); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NEVER, 1, 0); glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); ccGLBindTexture2DN(0, stencilTexture.name); [shaderProgramStencil use]; [shaderProgramStencil setUniformsForBuiltins]; ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position|kCCVertexAttribFlag_Color|kCCVertexAttribFlag_TexCoords); glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertexArr); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, vertexTex); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // теперь устанавливаем stencil buffer как маску glStencilFunc(GL_EQUAL, 1, 255); [self.shaderProgram use]; [self.shaderProgram setUniformsForBuiltins]; ccGLBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); ccGLBindTexture2DN(0, tex0.name); ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position|kCCVertexAttribFlag_Color|kCCVertexAttribFlag_TexCoords); glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertexArr); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, vertexTex); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glActiveTexture(GL_TEXTURE0); }
Шейдер для отрисовки трафарета текстуры, будет выглядеть так:
precision lowp float; varying vec2 v_texCoord; uniform sampler2D CC_Texture0; // по умолчанию в кокосе void main() { vec4 color = texture2D(CC_Texture0, v_texCoord); if (color.a == 0.0) { discard; } gl_FragColor = color; }
Разумеется использование функции discard (а также функции if и непоследовательного доступа к текстурам) в пиксельном шейдере – это зло и низкий fps. Поэтому никакого смысла в таком надругательстве над stencil буффером нет, вот собственно поэтому его мало используют в 2d.
В итоге, я решила избавится от отдельного “запекания” текстуры трафарета в фреймбуфер, а также от загрузки ее в stencil буффер. И пришла к такому решению: делаю один фреймбуфер c маленькой текстурой газа, строю VBO трафарета, в который складирую координаты текстур трубопровода, координаты текстур газа, и координаты экрана и рендерю на экран. Вижу отличный fps.
Видео результата можно посмотреть здесь.
Мне эти “упражнения” очень помогли разобраться в пучине “open gl”. Возможно, кому-то тоже будут интересны и полезны эти знания.
ссылка на оригинал статьи http://habrahabr.ru/post/227491/
Добавить комментарий