Gas Shader in Cocos2d

    Guten Tag.
    Ich wollte meine kleinen Erfahrungen mit der Optimierung von Shadern auf iOS teilen und wenn möglich praktische Ratschläge dazu erhalten. Es scheint, als gäbe es ein großartiges Tool, OpenGl ES 2.0, und Sie können einige gute Effekte erzielen, aber Sie können nicht immer mehr oder weniger gute FPS erzielen.



    Beginnen Sie mit einem einfachen. Es wurde ein kleines Programm zum Bau einer Endlosgasleitung erstellt, das Hindernisse umgeht und Objekte verbindet. Die Aufgabe besteht darin, das die Leitung füllende Gas abzusaugen. Ich programmiere auf Cocos2d, es gibt dort nicht sehr viele Materialien für Shader, aber im Großen und Ganzen können Sie einfach alle verfügbaren Beispiele für iOS und Android anhängen. Ein kleiner Unterschied, den Cocos2d direkt macht, ist die Übertragung von Vertex-Shader-Koordinaten in Bildschirmgrößen und nicht im Bereich [-1.0, 1.0]. Das ist sehr praktisch.
    Um die Bereiche zu konvertieren, bildet die Kokosnuss selbst die Matrix CC_MVPMatrix und fügt sie dem Vertex-Shader hinzu.
    Das Gas basiert auf einem Shader, der Farben basierend auf der eingehenden Perlin-Rauschtextur kombiniert.

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


    Zunächst wollte ich Gas auf dem gesamten Bildschirm erzeugen und dann den gewünschten Bereich zum Rendern zerschneiden. Das Problem ist, dass solch ein einfacher Shader die fps auf 22 Frames auf dem iphone4 reduziert. Auf anderen Geräten war die Leistung besser, aber nicht brillant, wie es heißt.

    Der geeignetste Ausweg in dieser Situation ist die Erzeugung einer kleinen Zwischentextur im Framebuffer. Diese Textur kann durch Einstellen der entsprechenden Texturparameter weiter erweitert oder erweitert werden. Mit dieser Methode können Wasser, Nebel und Gas erzeugt werden.
    Erstellen eines Framebuffers in Kokosnuss:

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


    Das Rendering sieht ungefähr so ​​aus:

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


    Beim Rendern fügen wir zunächst einen Bildpuffer hinzu, in dem shaderProgramNoise eine kleine texNoise-Textur bildet.
    Als nächstes rendert ShaderProgramAlpha im Renderpuffer die Verarbeitung von zwei Texturen: Gas und Schablone. Die Textur der Schablone wurde ursprünglich in einem anderen Bildspeicher mit Bildern von Voll- und Teilrohren gezeichnet.

    Fps hat sich deutlich verbessert. Die resultierende Option war jedoch überhaupt nicht optimal. Wie sich herausstellte, wird das System durch eine Zunahme der Anzahl der Frambuffer (! Obwohl es nur 2 gab!) Belastet, und die Geschwindigkeit sinkt sofort um ein Dutzend. Es ist klar, dass eine derart dramatische Situation nur auf dem iPhone 4 zu beobachten war. Dies sollte jedoch bei der Entwicklung berücksichtigt werden und keine unnötigen zusätzlichen Frambuffer produzieren.
    Eine gute Lösung wäre, die Schablonentextur in einen Schablonengasrahmen-Puffer-Puffer zu legen. Wenn der Schablonenwert 0 ist, wird der Pixel-Shader nicht aufgerufen. Der unangenehme Moment bei der Verwendung von Schablonenpuffern ist jedoch die Unfähigkeit, die Textur dort zu stopfen. Das heißt Sie müssen dort mit Dreiecken zeichnen - und dies ist die volle FE (für diese Aufgabe). Aber da ich den Schablonenpuffer erwähnt habe, kann ich sagen, dass ich es geschafft habe, die Textur dort zu stopfen. Da beim Aufrufen von glDrawArrays für stencil auch ein Shader-Programm verwendet wird, können wir mit discard für transparente Bereiche in diesem Programm immer noch eine Textur-Schablone abrufen. Wenn jemand interessiert ist, sehen Sie diese Methode:

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


    Ein Shader zum Rendern einer Texturschablone sieht folgendermaßen aus:

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


    Natürlich ist die Verwendung der Discard-Funktion (sowie der If-Funktion und des inkonsistenten Texturzugriffs) in einem Pixel-Shader böse und mit niedrigen FPS. Daher ist ein solcher Missbrauch des Schablonenpuffers nicht sinnvoll, weshalb er in 2d nur wenig verwendet wird.

    Infolgedessen habe ich mich entschlossen, ein separates "Backen" der Textur der Schablone im Framebuffer sowie das Laden in den Schablonenpuffer zu vermeiden. Und ich bin zu dieser Entscheidung gekommen: Ich mache einen Framebuffer mit einer kleinen Gastextur, erstelle eine VBO-Schablone, in der ich die Koordinaten der Pipeline-Textur, die Koordinaten der Gastextur und die Koordinaten des Bildschirms speichere und auf dem Bildschirm rendere. Ich sehe tolle fps.

    Das Video des Ergebnisses können Sie hier ansehen .
    Diese „Übungen“ haben mir sehr geholfen, den Abgrund von „open gl“ zu verstehen. Vielleicht ist auch jemand an diesem Wissen interessiert und nützlich.

    Jetzt auch beliebt: