cocos2d-x 精靈遮罩


轉自:http://bbs.9ria.com/thread-220210-1-4.html

首先得理解一些東西。

1.理解顏色混合。精靈有個成員函數:setBlendFunc(),這個函數以一個ccBlendFunc類型的變量為參數。這個ccBlendFunc是個結構體。這個結構體中有兩個變量:src 和 dest. 舉個例子:

代碼:

ccBlendFunc  spriteBlend;

spriteBlend.src = GL_ONE;

spriteBlend.dst = GL_ZERO;

pSprite->setBlendFunc(spriteBlend);

假設精靈pSprite是源顏色.則setBlendFunc的作用就是把精靈pSprite的各個像素的R,G,B,A分量和源顏色因子1.0(src = GL_ONE)相乘.  如果精靈pSprite是目標顏色,則setBlendFunc的作用就是把精靈pSprite的各個像素的R,G,B,A分量和目標顏色因子(dst = GL_ZERO)相乘.

 

如何界定pSprite是源顏色還是目標顏色呢?

如果這個時候還存在一個精靈pSpriteOther.如果pSprite先調用visit(), 然后pSpriteOther后調用visit()(visit()的作用是遞歸的渲染精靈和他的孩子節點)。。。則先調用visit()的為目標顏色,后調用visit的為源顏色。即:pSprite是目標顏色 ,pSpriteOther為源顏色。

 

2.做精靈的遮罩效果為什么要用CCRenderTexture這個類。

你可能會覺得我們只需要先把mask(遮罩)精靈渲染上去,然后再渲染被遮罩的精靈,並且指定這兩個精靈的blendFunc就行了。可是,實際上這樣是行不通的!

因為被渲染上去的mask精靈下面如果還有其他的精靈。這樣的話被渲染到mask精靈之上的精靈在做顏色混合的時候會出現意想不到的結果。達不到我們做遮罩的效果。

這樣的話,我們需要一個比較干凈的畫板,這個干凈的畫板只有兩個精靈在做顏色混合。這樣的話這兩個精靈在做顏色混合的時候就能達到我們想要的結果。不會受到不干凈的背景造成的混合誤差。這個背景就是CCRenderTexture.

 

當然如果我們的layer上只有精靈做混合的話就用不着CCRenderTexture了。但是實際項目中基本上是不能的。

 

OK。看看我們的Code.

    CCSize size = CCDirector::sharedDirector()->getWinSize();
    //創建干凈的畫板
    CCRenderTexture *pRt = CCRenderTexture::create(size.width,size.height);
     CCAssert(pRt, "RenderTexture is invalid");
     addChild(pRt);
     pRt->setPosition(size.width/2,size.height/2);
    //創建遮罩圖片
    CCSprite *pMask = CCSprite::create("CalendarMask.png");
    CCAssert(pMask,"mask sprite is invalid");
    pMask->setPosition(CCPointMake(pMask->getContentSize().width/2, pMask->getContentSize().height/2));
    //創建被遮罩圖片
    CCSprite *pFlower = CCSprite::create("Calendar1.png");
    CCAssert(pFlower, "Flower sprite is invalid");
    pFlower->setPosition(CCPointMake(pFlower->getContentSize().width/2, pFlower->getContentSize().height/2));
    
    //先設置好 遮罩精靈 和 被遮罩精靈 在被渲染的時候采用什么樣的顏色混合法則
    ccBlendFunc maskBlend = {GL_ONE, GL_ZERO};
    ccBlendFunc flowerBlend = {GL_DST_ALPHA, GL_ZERO};
    pMask->setBlendFunc(maskBlend);
    pFlower->setBlendFunc(flowerBlend);

    //開始把各種精靈渲染到畫板上
    pRt->begin();
    //先渲染遮罩精靈。但是因為有個畫板先被渲染。所以pMask是第二個被渲染的,即后被渲染。
    //所以在這一刻pMask是源顏色。調用pMask->visit()的時候吧精靈pMask上的每個像素的RGBA分量和1.0相乘。
    //所以遮罩圖片被元模原樣的渲染出來.
    pMask->visit();
    //再渲染被遮罩的精靈.在這一刻,之前先有pMask被渲染。所以pFlower后被渲染。pFlower就是源顏色。之前的pMask就是目標顏色。
     //調用pFlower->visit()的時候,精靈pFlower上的對應像素的RGBA分量和pMask上的對應像素的A分量相乘.因為前面設置了GL_DST_ALPHA。
    pFlower->visit();
    //停止渲染到畫板
    pRt->end();

上面看注釋就懂了。

先看遮罩圖片(PNG)目標顏色

這個遮罩圖片是個不規則的邊緣的圖片,其本事是個矩形。除了白色區域有像素外,其他區域沒像素,是全透明的。以上圖片中顯淺藍色的區域是我截取的時候故意這樣做的 。實際上這一區域是全透明的。

再看被遮擋圖片(源顏色)

采用GL_DST_ALPHA把遮擋圖片對應像素的RGBA分量和 被遮擋圖片的A分量相乘.這樣的話,遮擋圖片中透明的區域在被遮擋圖片上對應的區域就全透明了。

效果如下圖。

黑色的區域是layer的背景.

話中貌似用CCRenderTexture的方式效率很低下,但是本人也沒深究過。

2、高效率遮罩


先說下模板緩沖(stencil buffer),這在05年還算是一個比較普及的技術。cocos2d-x現在的版本是不支持stencil buffer的,但opengl es是支持的。
可以簡單的動手改造一下:
創建stencil buffer。在ES1Renderer.m文件中找到resizeFromLayer方法,將if (depthFormat_){}大括號中的代碼替換成以下內容:

if (depthFormat_) 
{
   if( ! depthBuffer_ )
       glGenRenderbuffersOES(1, &depthBuffer_);
        
   glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthBuffer_);
   if( multiSampling_ )
       glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, samplesToUse_, depthFormat_,backingWidth_, backingHeight_);
   else
       glRenderbufferStorageOES(GL_RENDERBUFFER_OES, depthFormat_, backingWidth_, backingHeight_);
   glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthBuffer_);
   // add by frankyang at 2012/5/8
   glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthBuffer_);
   // bind color buffer
   glBindRenderbufferOES(GL_RENDERBUFFER_OES, colorRenderbuffer_);
}

設置stencil buffer格式。在AppController.mm中找到的didFinishLaunchingWithOptions方法,將其中的depthFormat參數改為GL_DEPTH24_STENCIL8_OES,如下:  
 

 // Add the view controller's view to the window and display.
window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
EAGLView *__glView = [EAGLView viewWithFrame: [window bounds]
  pixelFormat: kEAGLColorFormatRGBA8
  //depthFormat: GL_DEPTH_COMPONENT16_OES
  depthFormat:GL_DEPTH24_STENCIL8_OES
  preserveBackbuffer: NO
  sharegroup:nil
  multiSampling:NO
  numberOfSamples:0];

設置每幀渲染開始時清除stencil buffer。在CCDirector.cpp中找到drawScene方法,將其中
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
改成
        glClear(GL_COLOR_BUFFER_BIT | GL_COLOR_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
這樣就可以正確清除stencil buffer。
啟動模板測試,設置模板函數。這里要用到三個函數:

glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 0x1, 0x1);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

第一個是啟用模板測試,第二個是設置模板測試函數,第三個是設置模板緩沖操作方式。
模板測試簡單來說就是先往模板緩沖中寫入模板值,然后渲染時根據模板測試結果來決定像素是否寫入color buffer。
具體解釋大家可以看這個帖子深入了解OpenGL-模板測試
為了靈活的寫入模板值,我借鑒了Quaz2D中maskLayer的概念,在要渲染的Layer前后插入MaskBeginLayer和MaskEndLayer。
用MaskBeginLayer來填充模板緩沖,並設定好之后需要的模板測試函數;用MaskEndLayer來恢復模板測試狀態。

void MaskBeginLayer::visit()
{
  if (getChildrenCount() != 0) {
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_GREATER, 0.0);

    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0x1, 0x1);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

    CCLayer::visit();

    glDisable(GL_ALPHA_TEST);
    glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
  }
}
void MaskEndLayer::visit()
{
  glDisable(GL_STENCIL_TEST);

  CCLayer::visit();
}

這里要注意透明像素也會寫入stencil buffer,所有特別用了alphatest。
經過真機測試,這樣實現mask性能是無損的。由於不影響alpha blend,使用起來比較靈活。唯一不好的是mask不支持漸變,要么全透,要么全部透。
現在我在研究直接用alpha blend操作實現mask,性能一樣無損,還可以支持漸變,但也有其局限性,且聽下回分解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM