以纹理/纯色绘制透明孔

时间:2014-05-28 12:30:40

标签: c++ opengl drawing cocos2d-x cocos2d-x-3.0

我遇到了一个问题,我不知道它的最佳做法是什么。我有一个向上移动的背景,实际上是“切片”移动到另一个,好像屏幕被水平分割成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),我在Objective-C版本中使用了MutableTextures(它正在运行)。我只是想看看是否有更好的方法使用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;
}

init方法:

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

1 个答案:

答案 0 :(得分:2)

你可能想看的是帧缓冲器,我不太熟悉OpenGL的移动API,但我确信你应该可以访问framebuffers。

你可能想要尝试的一个想法是做第一遍,你将你想要在背景上设置为alpha的圆圈渲染成一个新的帧缓冲纹理,然后你可以使用这个纹理作为alpha贴图你传递你的背景。所以基本上当渲染圆圈时,你可以将纹理中的值设置为0.0,对于alpha通道,否则为1.0,渲染时可以将片段的alpha通道设置为与第一遍的纹理alpha相同的值'渲染过程。

您可以将其视为与面具相同的想法。但只是使用另一种纹理。

希望这会有所帮助:)