...

среда, 25 июня 2014 г.

Газовый шейдер в Cocos2d

Добрый день.

Хотела поделиться своим небольшим опытом оптимизации шейдеров на 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”. Возможно, кому-то тоже будут интересны и полезны эти знания.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий