【问题标题】:Draw transparent holes in a texture/plain color以纹理/纯色绘制透明孔
【发布时间】:2014-05-28 12:30:40
【问题描述】:

我遇到了一个问题,我不知道什么是最好的做法。我有一个向上移动的背景,实际上是一起移动的“切片”,就好像屏幕被水平分成了 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;
}

初始化方法:

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

【问题讨论】:

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


    【解决方案1】:

    您可能想查看的是帧缓冲区,我对 OpenGL 的移动 API 不太熟悉,但我相信您应该可以访问帧缓冲区。

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

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

    希望这会有所帮助:)

    【讨论】:

    • 所以如果我理解正确,通过一个我更新 alpha 地图中的圆圈位置/颜色,通过两个我使用这个地图生成背景?
    • 几乎是的,您不会使用 alpha 贴图来生成背景,而是更多地确定背景的哪些部分是 alpha/透明的,哪些部分不是。
    • 确定像迭代alpha图,如果值为0不绘制背景像素?用 OpenGL 怎么能做到这一点?
    • 因此,如果您使用自定义片段着色器,您将能够将 alpha 贴图绑定为着色器的纹理,然后在着色器绘制时,您将从纹理中采样 alpha 值使用您正在渲染的对象的纹理坐标。当您将圆圈渲染到 Alpha 贴图中时,您需要为该通道编写一个单独的直通片段着色器。我不确定在 cocos2d 中创建和绑定自定义着色器程序的具体细节是如何工作的,但这几乎是如果你只使用 openGL API 编写的话,你会如何做到这一点的理论。
    • 谢谢,我会试一试,稍后将通过更新编辑 OP :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 1970-01-01
    相关资源
    最近更新 更多