Добрый день.
Хотела поделиться своим небольшим опытом оптимизации шейдеров на IOS, и по возможности услышать дельные советы на этот счет. Вроде бы есть прекрасный инструмент OpenGl ES 2.0, и можно сделать неплохие эффекты, но при этом получить более-менее вменяемый fps не всегда получается.
Начала с простого. Сделана небольшая программка построения бесконечного газопровода, обходя препятствия и подключая объекты. Задача – отрисовать газ, заполняющий трубу. Программирую я на Cocos2d, материалов по шейдерам там не очень-то много, но по большому счету, можно легко пристраивать любые примеры доступные для ios и андроида. Небольшое отличие, которое вносит непосредственно Cocos2d, — это передача координат вершинных шейдеров в экранных размерах, а не в диапазоне [-1.0, 1.0]. Это весьма удобно.
Для конвертации диапазонов кокос сам формирует матрицу CC_MVPMatrix, и добавляет ее в вертексный шейдер.
За основу газа взят шейдер, который комбинирует цвета на основе входящей текстуры шума Перлина.
Первоначально, я планировала сгенерить газ на весь экран, а далее вырезать нужную облать для рендеринга. Проблема в том, что такой простенький шейдер снижал fps до 22 кадров на iphone4. На других устройствах производительность была лучше, но не брилиантли, что называется.
Наиболее подходящим выходом в данной ситуации является генерация небольшой промежуточной текстуры в фреймбуфере. Эту текстуру в дальнейшем можно растянуть или размножить, установив соответствующие параметры текстуры. Этот способ можно использовать для генерации воды, тумана, газа.
Создание фреймбуфера в кокосе:
Отрисовка будет выглядеть примерно так:
В отрисовке мы сначала подключаем фрейм буффер, в котором shaderProgramNoise формирует текстуру texNoise маленького размера.
Далее в рендер буффере shaderProgramAlpha отрисовывает обработку двух текстур: газа и трафарета. Текстуру трафарета первоначально я отрисовывала в другой фрейм буфер, используя картинки полных и частичных труб.
Fps значительно улучшился. Тем не менее, полученный вариант был вовсе не оптимальным. Как выяснилось, увеличение количества frambuffer’ов (! хотя их всего было 2!) нагружает систему, и fps сразу падает на десяток. Понятное дело, что такая драматическая ситуация наблюдалась только на iphone4, тем не менее это надо учитывать при разработке и не плодить дополнительные frambuffer’ы без необходимости.
Хорошим решением было бы разместить трафаретную текстуру в stencil буффер frameBuffera c газом. Там где значение стенсила 0, не происходит вызов пиксельного шейдера. Однако, неприятным моментом в использовании stencil- буфферов является невозможность запихать туда текстуру. Т.е. рисовать туда придется треугольниками – а это полное ФУ (для данной задачи). Но раз уж я упоминула о stencil-буффере, скажу, что все-таки запихать туда текстуру мне удалось. Поскольку вызов glDrawArrays для stencil’а тоже использует шейдерную программу, то используя в этой программе discard для прозрачных областей, мы все-таки можем получить там трафарет текстуры. Если кому-то интересно смотрите такой способ:
Шейдер для отрисовки трафарета текстуры, будет выглядеть так:
Разумеется использование функции discard (а также функции if и непоследовательного доступа к текстурам) в пиксельном шейдере – это зло и низкий fps. Поэтому никакого смысла в таком надругательстве над stencil буффером нет, вот собственно поэтому его мало используют в 2d.
В итоге, я решила избавится от отдельного “запекания” текстуры трафарета в фреймбуфер, а также от загрузки ее в stencil буффер. И пришла к такому решению: делаю один фреймбуфер c маленькой текстурой газа, строю VBO трафарета, в который складирую координаты текстур трубопровода, координаты текстур газа, и координаты экрана и рендерю на экран. Вижу отличный fps.
Видео результата можно посмотреть здесь.
Мне эти “упражнения” очень помогли разобраться в пучине “open gl”. Возможно, кому-то тоже будут интересны и полезны эти знания.
Хотела поделиться своим небольшим опытом оптимизации шейдеров на 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”. Возможно, кому-то тоже будут интересны и полезны эти знания.