1. 程式人生 > >基於OpenGL編寫一個簡易的2D渲染框架-13 使用例子

基於OpenGL編寫一個簡易的2D渲染框架-13 使用例子

tom 進行 prim demo custom 第一個 manager sets mar

這是重構渲染器的最後一部分了,將會給出一個 demo,測試模板測試、裁剪測試、半透明排序等等:

技術分享

上圖是本次 demo 的效果圖,中間的綠色圖形展現的是模板測試。

模板測試

void init(Pass*& p1, Pass*& p2)
{
    p1 = new Pass;
    p2 = new Pass;

    Shader* s1 = new Shader("Shader/defaultGeometryShader.vs", "Shader/defaultGeometryShader.frag", CVA_V3F_C4F);
    Shader* s2 = new
Shader("Shader/defaultGeometryShader.vs", "Shader/defaultGeometryShader.frag", CVA_V3F_C4F); p1->enableBlend(true); p2->enableBlend(true); p1->setBlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA, BLEND_ONE, BLEND_ZERO); p2->setBlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA, BLEND_ONE, BLEND_ZERO); p1
->setShader(s1); p2->setShader(s2); p1->setPrimType(PT_TRIANGLES); p2->setPrimType(PT_TRIANGLES); p1->enableStencilTest(true); p1->setStencilMask(0xFF); p1->setStencilCompareFunc(COMPARE_ALWAYS); p1->setStencilRef(1); p1->setStencilOp(STENCIL_OP_KEEP, STENCIL_OP_KEEP, STENCIL_OP_REPLACE); p2
->enableStencilTest(true); p2->setStencilMask(0xFF); p2->setStencilCompareFunc(COMPARE_EQUAL); p2->setStencilRef(1); p2->setStencilOp(STENCIL_OP_KEEP, STENCIL_OP_KEEP, STENCIL_OP_REPLACE); }

使用模板測試需要兩個 Pass,第一個 Pass 繪制圓的時候,把圓範圍內的模板值設置為 1,。在時候 p1 繪制好圓後,再使用第二個 pass 繪制兩個波紋效果的圖形,這時的 pass 設置比較函數為等於,即只有模板值等於 1 的像素才不會被拋棄:

/* 模板測試 */
void stencilTest(GraphicsContext* gc, Canvas2D* canvas, Pass* p1, Pass* p2)
{
    static float d = 0;
    static float d1 = 0;
    static float h = 0;

    Vec2 vs1[23];
    Vec2 vs2[23];

    gc->render();
    canvas->setCustomPass(p1);
    canvas->fillCircle(Vec3(400, 300, 0), 100, 360, Color(1, 1, 1, 0));
    gc->render();

    canvas->setCustomPass(p2);

    float hz = 20;
    vs1[0].set(500, 200);
    vs1[1].set(300, 200);
    for ( int i = 0; i <= hz; i++ ) {
        float y = sinf(i / hz * PI_2 + d + i / hz * 2);

        vs1[i + 2].set(300 + i / hz * 200, y * 15 + 200 + h);
    }
    canvas->fillPath(vs1, 23, Color(0, 1, 0, 0.5));

    
    vs2[0].set(500, 200);
    vs2[1].set(300, 200);
    for ( int i = 0; i <= hz; i++ ) {
        float y = sinf(i / hz * PI_2 + d1 + i / hz * 3);

        vs2[i + 2].set(300 + i / hz * 200, y * (10 + i / hz * 10) + 200 + h);
    }
    canvas->fillPath(vs2, 23, Color(0, 1, 0, 0.7));

    gc->render();
    canvas->setCustomPass(nullptr);

    h += 0.08;
    if ( h > 200 ) {
        h = 0;
    }

    d += 0.01;
    if ( d >= PI_2 ) d = 0;
    d1 += 0.02;
    if ( d1 >= PI_2 ) d1 = 0;
}

最終的效果:

技術分享

和迅雷的懸浮球顯示下載進度的效果相差不多。

裁剪測試

在使用裁剪測試時,使用一種粒子效果作為測試對象。粒子會拖出一條長長的尾巴,碰到窗口邊緣時反彈。四條綠線圍成的矩形為裁剪區域,粒子在矩形區域外的部分不會被顯示出來。代碼實現:

    ParticleSystem* ball = new ParticleSystem();
    ball->initWithPlist("Particle/motion.plist");
    ball->setTexture("Particle/fire.png");
    ball->getEmitter()->setEmitPos(Vec2(400, 300));

    Pass* pass = ball->getPass();
    pass->enableScissor(true);
    pass->setScissorRect(100, 100, 600, 400);

    ParticleSystemManager manager;
    manager.appendParticleSystem(ball);

先創建一個粒子系統,實現拖尾的粒子效果。然後獲取粒子系統的 Pass 對象(每個粒子系統都會有一個 pass 對象),開啟裁剪測試,隨機給出一個裁剪區域,初始化到此結束。

/* 裁剪測試 */
void scissorTest(ParticleSystem* ps, Canvas2D* canvas)
{
    static float x = 400, y = 300;
    static int xdir = 1, ydir = 1;
    static float clipx1 = 0, clipy1 = 0, clipx2 = 800, clipy2 = 600;
    static int clipdx = 1, clipdy = 1;

    ps->getEmitter()->setEmitPos(Vec2(x, y));
    int speed = 2.5;
    x -= xdir * speed;
    y -= ydir * speed;
    if ( x < 0 ) {
        xdir = -1;
    }
    else if ( x > DEFAULT_WIN_W ) {
        xdir = 1;
    }
    if ( y < 0 ) {
        ydir = -1;
    }
    else if ( y > DEFAULT_WIN_H ) {
        ydir = 1;
    }

    clipx1 += clipdx * 0.5f;
    clipx2 -= clipdx * 0.5f;
    clipy1 += clipdy * 0.5f;
    clipy2 -= clipdy * 0.5f;
    if ( clipx1 >= 150 ) {
        clipdx = -1;
    }
    else if ( clipx1 <= 0 ) {
        clipdx = 1;
    }
    if ( clipy1 >= 150 ) {
        clipdy = -1;
    }
    else if ( clipy1 <= 0 ) {
        clipdy = 1;
    }
    canvas->drawLine(0, clipy1, DEFAULT_WIN_W, clipy1, Color(0, 1, 0, 1));
    canvas->drawLine(clipx1, 0, clipx1, DEFAULT_WIN_H, Color(0, 1, 0, 1));

    canvas->drawLine(0, clipy2, DEFAULT_WIN_W, clipy2, Color(0, 1, 0, 1));
    canvas->drawLine(clipx2, 0, clipx2, DEFAULT_WIN_H, Color(0, 1, 0, 1));
    ps->getPass()->setScissorRect(clipx1, clipy1, clipx2 - clipx1, clipy2 - clipy1);
}

每一幀,都移動四條綠線,實現裁剪區域的變化。在改變裁剪區域後,要更新 pass 的裁剪區域。作為對比,還加入了火焰的粒子效果(沒有對 pass 做任何的更改),火焰跟隨鼠標的位置移動。

半透明圖形排序

這個主要在繪制圖形的時候設置深度值即可:

        for ( int i = 0; i < 10; i++ ) {
            int x = 20 + i * 20;
            int y = 20 + i * 20;
            if ( i < 5 ) {
                canvas.fillRect(x, y, x + 100, y + 100, Color(0.2 * i, 0, 0, 0.1 + 0.1 * i), i);
            }
            else {
                canvas.fillRect(x, y, x + 100, y + 100, Color(0, 0, 0.2 * i, 0.1 * i), i);
            }
        }

fillRect 函數的最後一個參數就是深度值(渲染器中就是根據深度值 depth 進行排序的),數值小的先被繪制。

技術分享

基於OpenGL編寫一個簡易的2D渲染框架-13 使用例子