Нарисуйте прозрачные отверстия в текстуре/однотонном цвете

У меня возникла проблема, и я не знаю, что лучше всего для этого. У меня есть фон, который движется вверх, что на самом деле является «срезами», которые движутся вместе, как если бы экран был разделен на 4-5 частей по горизонтали. Мне нужно иметь возможность нарисовать отверстие (круг) на заднем плане (прозрачное) в указанной позиции, которая будет динамически меняться в каждом кадре или около того.

Вот как я создаю зону, я не думаю, что в этом есть большая проблема:

// A 'zone' is simply the 'slice' of ground that moves upward. There's about 4 of
// them visible on screen at the same time, and they are automatically generated by
// a method irrelevant to the situation. Zones are Sprites.
// ---------
void LevelLayer::Zone::generate(LevelLayer *sender) {

    // [...]

    // Make a background for the zone
    Sprite *background = this->generateBackgroundSprite();
    background->setPosition(_contentSize.width / 2, _contentSize.height / 2);
    this->addChild(background, 0);
}

Это метод Zone::generateBackgroundSprite():

// generates dynamically a new background texture
Sprite *LevelLayer::Zone::generateBackgroundSprite() {

    RenderTexture *rt = RenderTexture::create(_contentSize.width, _contentSize.height);
    rt->retain();

    Color4B dirtColorByte = Color4B(/*initialize the color with bytes*/);
    Color4F dirtColor(dirtColorByte);
    rt->beginWithClear(dirtColor.r, dirtColor.g, dirtColor.b, dirtColor.a);

    // [Nothing here yet, gotta learn OpenGL m8]

    rt->end();

    // ++++++++++++++++++++
    // I'm just testing clipping node, it works but the FPS get significantly lower.
    // If I lock them to 60, they get down to 30, and if I lock them there they get
    // to 20 :(
    // Also for the test I'm drawing a square since ClippingNode doesn't seem to
    // like circles...
    DrawNode *square = DrawNode::create();
    Point squarePoints[4] = { Point(-20, -20), Point(20, -20), Point(20, 20), Point(-20, 20) };
    square->drawPolygon(squarePoints, 4, Color4F::BLACK, 0.0f, Color4F(0, 0, 0, 0));
    square->setPosition(0, 0);

    // Make a stencil
    Node *stencil = Node::create();
    stencil->addChild(square);

    // Create a clipping node with the prepared stencil
    ClippingNode *clippingNode = ClippingNode::create(stencil);
    clippingNode->setInverted(true);
    clippingNode->addChild(rt);

    Sprite *ret = Sprite::create();
    ret->addChild(clippingNode);

    rt->release();
    return ret;
}

**
Итак, я спрашиваю вас, ребята, что бы вы сделали в такой ситуации? Является ли то, что я делаю, хорошей идеей? Не могли бы вы сделать это другим, более изобретательным способом?

PS Это переписывание небольшого приложения, которое я сделал для iOS (я хочу портировать его на Android), и я использовал MutableTextures в версии Objective-C (оно работало). Я просто пытаюсь найти лучший способ использования RenderTexture, чтобы я мог динамически создавать фоновые изображения с помощью вызовов OpenGL.


РЕДАКТИРОВАТЬ (РЕШЕНИЕ)

Я написал свой собственный простой фрагментный шейдер, который «маскирует» видимые части текстуры (фон) на основе видимых частей другой текстуры (маски). У меня есть массив точек, которые определяют, где мои круги находятся на экране, и в методе update я рисую их в RenderTexture. Затем я беру сгенерированную текстуру и использую ее в качестве маски, которую передаю шейдеру.

Это мой шейдер:

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform sampler2D u_alphaMaskTexture;

void main() {

    float maskAlpha = texture2D(u_alphaMaskTexture, v_texCoord).a;
    float texAlpha = texture2D(u_texture, v_texCoord).a;
    float blendAlpha = (1.0 - maskAlpha) * texAlpha; // Show only where mask is not visible

    vec3 texColor = texture2D(u_texture, v_texCoord).rgb;

    gl_FragColor = vec4(texColor, blendAlpha);

    return;
}

метод инициализации:

bool HelloWorld::init() {

    // [...]

    Size visibleSize = Director::getInstance()->getVisibleSize();

    // Load and cache the custom shader
    this->loadCustomShader();

    // 'generateBackgroundSlice()' creates a new RenderTexture and fills it with a
    // color, nothing too complicated here so I won't copy-paste it in my edit
    m_background = Sprite::createWithTexture(this->generateBackgroundSprite()->getSprite()->getTexture());
    m_background->setPosition(visibleSize.width / 2, visibleSize.height / 2);
    this->addChild(m_background);

    m_background->setShaderProgram(ShaderCache::getInstance()->getProgram(Shader_AlphaMask_frag_key));
    GLProgram *shader = m_background->getShaderProgram();
    m_alphaMaskTextureUniformLocation = glGetUniformLocation(shader->getProgram(), "u_alphaMaskTexture");
    glUniform1i(m_alphaMaskTextureUniformLocation, 1);

    m_alphaMaskRender = RenderTexture::create(m_background->getContentSize().width,
                                              m_background->getContentSize().height);
    m_alphaMaskRender->retain();

    // [...]
}

метод loadCustomShader:

void HelloWorld::loadCustomShader() {

    // Load the content of the vertex and fragement shader
    FileUtils *fileUtils = FileUtils::getInstance();
    string vertexSource = ccPositionTextureA8Color_vert;
    string fragmentSource = fileUtils->getStringFromFile(
                            fileUtils->fullPathForFilename("Shader_AlphaMask_frag.fsh"));

    // Init a shader and add its attributes
    GLProgram *shader = new GLProgram;
    shader->initWithByteArrays(vertexSource.c_str(), fragmentSource.c_str());

    shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
    shader->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS);
    shader->link();
    shader->updateUniforms();

    ShaderCache::getInstance()->addProgram(shader, Shader_AlphaMask_frag_key);

    // Trace OpenGL errors if any
    CHECK_GL_ERROR_DEBUG();
}

метод обновления:

void HelloWorld::update(float dt) {

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // Create the mask texture from the points in the m_circlePos array
    GLProgram *shader = m_background->getShaderProgram();

    m_alphaMaskRender->beginWithClear(0, 0, 0, 0); // Begin with transparent mask

    for (vector<Point>::iterator it = m_circlePos.begin(); it != m_circlePos.end(); it++) {

        // draw a circle on the mask
        const float radius = 40;
        const int resolution = 20;
        Point circlePoints[resolution];

        Point center = *it;
        center = Director::getInstance()->convertToUI(center); // OpenGL has a weird coordinates system
        float angle = 0;
        for (int i = 0; i < resolution; i++) {

            float x = (radius * cosf(angle)) + center.x;
            float y = (radius * sinf(angle)) + center.y;
            angle += (2 * M_PI) / resolution;

            circlePoints[i] = Point(x, y);
        }

        DrawNode *circle = DrawNode::create();
        circle->retain();
        circle->drawPolygon(circlePoints, resolution, Color4F::BLACK, 0.0f, Color4F(0, 0, 0, 0));
        circle->setPosition(Point::ZERO);
        circle->visit();
        circle->release();
    }

    m_alphaMaskRender->end();

    Texture2D *alphaMaskTexture = m_alphaMaskRender->getSprite()->getTexture();
    alphaMaskTexture->setAliasTexParameters(); // Disable linear interpolation
    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    shader->use();
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, alphaMaskTexture->getName());
    glActiveTexture(GL_TEXTURE0);
}

person malloc19    schedule 28.05.2014    source источник


Ответы (1)


Возможно, вы захотите взглянуть на фреймбуферы, я не слишком знаком с мобильным API для OpenGL, но я уверен, что у вас должен быть доступ к фреймбуферам.

Идея того, что вы, возможно, захотите попробовать, состоит в том, чтобы сделать первый проход, где вы визуализируете круги, которые вы хотите установить в альфа-канале на вашем фоне, в новую текстуру фреймбуфера, затем вы можете использовать эту текстуру в качестве альфа-карты на своем проходе для визуализация вашего фона. Таким образом, в основном, когда вы визуализируете свой круг, вы можете установить значение в текстуре на 0,0 для альфа-канала, иначе на 1,0, при рендеринге вы можете установить альфа-канал фрагмента на то же значение, что и альфа текстуры первого прохода ' процесса рендеринга.

Вы можете думать об этом как о той же идее, что и маска. Но только используя другую текстуру.

Надеюсь это поможет :)

person Daniel    schedule 28.05.2014
comment
Итак, если я правильно понимаю, передайте один, чтобы обновить позиции/цвет кругов на альфа-карте, передайте два, я использую эту карту для создания фона? - person malloc19; 28.05.2014
comment
Почти да, вы не будете использовать альфа-карту для создания фона, а больше для того, чтобы определить, какие части фона будут альфа/прозрачными, а какие — нет. - person Daniel; 28.05.2014
comment
Определение похоже на итерацию по альфа-карте, и если значение равно 0, не рисовать пиксель фона? Как это можно сделать с OpenGL? - person malloc19; 28.05.2014
comment
Таким образом, если бы вы использовали пользовательский фрагментный шейдер, вы могли бы связать свою альфа-карту в качестве текстуры для вашего шейдера, а затем, когда ваш шейдер отрисовывал бы значения альфа-канала из текстуры, используя координаты текстуры объекта, который вы рендерите. Когда вы визуализируете свой круг в альфа-карту, вам нужно будет написать отдельный сквозной фрагментный шейдер для этого прохода. Я не уверен, как будут работать особенности создания и связывания пользовательских шейдерных программ в cocos2d, но это в значительной степени теория того, как вы могли бы это сделать, если бы вы писали только с использованием API openGL. - person Daniel; 28.05.2014
comment
Спасибо, я попробую и позже отредактирую ОП с обновлениями :) - person malloc19; 28.05.2014
comment
Круто, не забудьте поставить мне голос или принять ответ, если это помогло :) И удачи! - person Daniel; 28.05.2014