📜 ⬆️ ⬇️

Cocos2d gas shader

Good day.
I wanted to share my little experience in optimizing shaders on IOS, and if possible to hear practical advice on this matter. It seems to be a great tool OpenGl ES 2.0, and you can make good effects, but at the same time get more or less sane fps does not always work.



Start simple. Made a small program to build an infinite pipeline, bypassing obstacles and connecting objects. The task is to draw the gas filling the pipe. I program on Cocos2d, there are not very many materials on shaders, but by and large, you can easily attach any examples available for ios and android. The small difference that Cocos2d directly introduces is the transfer of the coordinates of the vertex shaders in screen sizes, not in the range [-1.0, 1.0]. This is quite convenient.
To convert ranges, the coconut itself forms the CC_MVPMatrix matrix, and adds it to the vertex shader.
The gas is based on a shader that combines colors based on the incoming texture of Perlin noise.

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); } 

')
Initially, I planned to generate gas on the whole screen, and then cut out the necessary area for rendering. The problem is that such a simple shader reduced fps to 22 frames on iphone4. On other devices, the performance was better, but not the diamond, as they say.

The most appropriate solution in this situation is to generate a small intermediate texture in the framebuffer. This texture can be further stretched or multiplied by setting the appropriate texture parameters. This method can be used to generate water, fog, gas.
Creating a framebuffer in a coconut:

 -(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); } 


Drawing will look something like this:

 -(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); } 


In the drawing, we first connect the frame buffer in which the shaderProgramNoise forms a small size texNoise texture.
Next, in the render buffer shaderProgramAlpha renders the processing of two textures: gas and stencil. The texture of the stencil, I originally drew a buffer into another frame, using pictures of full and partial tubes.

Fps has improved significantly. However, the resulting option was not at all optimal. As it turned out, an increase in the number of frambuffers (! Although there were only 2 of them!) Loads the system, and fps immediately drops to a dozen. It is clear that such a dramatic situation was observed only on the iphone4, nevertheless, this should be taken into account when developing and not creating additional frambuffers without need.
A good solution would be to place the stencil texture in stencil frameBuffera buffer with gas. Where the stencil value is 0, the pixel shader is not called. However, an unpleasant moment in the use of stencil buffers is the inability to push a texture there. Those. there will have to draw triangles - and this is a complete FU (for this task). But since I mentioned the stencil-buffer, I’ll say that I managed to push the texture into it. Since the glDrawArrays call for stencil also uses a shader program, using the discard for transparent areas in this program, we can still get a stencil texture there. If anyone is interested, see this method:

 -(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); } 


The shader for drawing a stencil texture will look like this:

 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; } 


Of course, using the discard function (as well as the if function and inconsistent access to textures) in a pixel shader is evil and low fps. Therefore, there is no point in abusing stencil with such a buffer, which is why it is in fact little used in 2d.

In the end, I decided to get rid of the separate “baking” of the stencil texture in the framebuffer, as well as from loading it into the stencil buffer. And I came to this decision: I make one framebuffer with a small gas texture, build a VBO stencil in which I store the coordinates of the pipeline textures, the coordinates of the gas textures, and the coordinates of the screen and render it on the screen. I see a great fps.

Video of the result can be viewed here .
These “exercises” helped me a lot in understanding the depth of “open gl”. Perhaps someone will also be interested and useful this knowledge.

Source: https://habr.com/ru/post/227491/


All Articles