cocos2d-x 紋理深入研究


轉自:http://blog.csdn.net/qq51931373/article/details/9152227

1.紋理控制。

   看此代碼:

  

CCSprite *pSprite = CCSprite::create("ship.png");

ccTexParams params = {GL_NEAREST,GL_NEAREST,GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE};

pSprite->getTexture()->setTexParameters(&params);

上面最重要的函數就是setTexParameters();他就是根據ccTexParameters來控制紋理圖像的紋理如何對應到屏幕的像素。根據什么來實現紋理的縮放和紋理的重復(重復紋理)等效果。

typedef struct _ccTexParams {
    GLuint    minFilter;
    GLuint    magFilter;
    GLuint    wrapS;
    GLuint    wrapT;
} ccTexParams;

先看看ccTexParams這個結構體,他的前兩個成員變量叫做基本過濾器。用來指定按照什么規則來控制紋理的縮放效果。

兩個基本的紋理過濾規則:

(1)GL_LINEAR:“當顯示紋理時,顯示的大小大於或者小於原紋理的尺寸時,使用鄰近像素點來插值補點”

     特點:圖像拉伸或者縮小后,看起來失真了,但是效果比GL_NEAREST好,看起來沒有人工操作后的痕跡.

(2)GL_NEAREST:(最鄰近過濾)最簡單最快捷的過濾方式。

    特點:當紋理拉伸到特別大的時候,會出現大片斑駁狀像素。

ccTexParameters后面的兩個成員變量叫做紋理環繞模式.是用來控制當繪制紋理邊界范圍之外的像素點的時候應該怎么去處理。openGL紋理的坐標范圍是(0.0f, 1.0f).意思就是當紋理坐標大於1.0f或者紋理坐標小於0.0f的時候怎么去處理紋理。

兩個常用的紋理環繞模式:

(1)GL_REPEAT:在紋理超過1.0f的方向上對紋理進行重復。如果你想顯示紋理邊界之外的像素點時,把它旁邊的紋理像素點平鋪過去 .

    看代碼:

pSprite = CCSprite::create("RepeatSprite.png");
pSprite->retain();
CCSize size = CCDirector::sharedDirector()->getWinSize();
pSprite->setPosition(CCPointMake(size.width/2, size.height/2));
addChild(pSprite);
ccTexParams tp ;
tp.minFilter = GL_LINEAR;
tp.magFilter = GL_LINEAR;
tp.wrapS = GL_REPEAT;
tp.wrapT = GL_REPEAT;
pSprite->getTexture()->setTexParameters(&tp);
//不斷的修改pSprite的紋理矩形區域,通過修改紋理的x坐標,來繪制紋理區域之外的部分。
schedule(schedule_selector(HelloWorld::UpdateTexture),0.05f);

在UpdateTexture()中:

void HelloWorld::UpdateTexture(float dt)
{
    int nLen = 40;
    static int nOffset = 0;
    nOffset += nLen*dt;
    pSprite->setTextureRect(CCRectMake(nOffset, 0, pSprite->getTexture()->getPixelsWide(),pSprite->getTexture()->getPixelsHigh()));
}

上面的代碼通過不斷增加nOffset來繪制精靈的紋理邊界之外的區域。由於之前設置了環繞模式為GL_REPEAT。則繪制精靈之外的區域的時候把旁邊的紋理像素進行平鋪。形成重復紋理的效果。

 

程序中這個精靈的紋理不斷的會從右向左滾動。注意:精靈的位置不會發生變化.這就用GL_REPEAT來實現了重復紋理的效果.

還需要注意的是RepeatSprite.png的寬和高必須是2的N次方大小。否則的話當你用GL_REPEAT來作為環繞模式的時候會出現警告提示:GL_REPEAT mode must be POT.意思就是用GL_REPEAT模式圖片大小必須是2的指數冪(POT).警告提示完了之后就是崩潰.

(2)GL_CLAMP:作用就是紋理的繪制坐標大於1.0則就設置成1.0, 小於0.0則設置成0.0;

效果見圖;

如果繪制的x坐標大於了紋理寬度,則就用紋理最右邊的一個像素來繪制后面的區域.

 

2.紋理緩沖區.(CCTextureCache)

   CCSprite::create()這個函數里面有句代碼:CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addImage(pszFilename);

   精靈就是用這句代碼來根據提供的圖片生成精靈的紋理對象。所以,不難看出用圖片創建了精靈對象時,圖片數據被加載到內存中,這塊內存就叫做紋理緩沖區(TextureCache),只要不手動就從紋理緩沖區中把圖片數據刪除則圖片數據永遠占着內存不釋放。所以這就帶出了針對紋理緩沖區的一些議題。

 

cocos2d中存放被加載的圖片數據的緩沖區(內存)分兩類:存放由多個小圖拼接成的大圖的區域   和  存放單個圖片的區域。具體分別對應於CCSpriteFrameCache和 CCTextureCache這兩個類。CCSpriteFrameCache主要是做動畫用的,請參考我的這篇文章:http://blog.csdn.net/qq51931373/article/details/9151363

 

為什么會存在這兩個類呢?從名字就知道他們是緩沖區,存在的唯一理由是下次如果需要同一個圖片數據的時候直接從緩沖區去取,而不需要再次把圖片文件載入到內存中去。這樣的話節省了內存還提高了渲染速度。

 

假設有一張圖片example.png被加載到緩沖區中去,如果這張圖片是第一次被加載到內存,則它首先被加載到緩沖區中去,然后再從緩沖區中讀取出來,用於創建紋理對象。

如果這個圖片的數據在內存中沒有被刪除,則在隨后程序員又想用圖片example.png來創建紋理對象的時候,cocos2d首先從紋理緩沖區中查找一下看是否存在這個圖片的數據。如果存在就直接從緩沖區中讀取。就不需要再次從硬盤載入到內存。多么方便啦!難道不是嗎?這樣為你節省了內存和提高了效率。

 

釋放:

如果游戲有很多場景,在切換場景的時候可以把前一個場景的內存全部釋放,防止總內存過高.

CCTextureCache::sharedTextureCache()->removeAllTextures();   //釋放到目前為止所有加載的圖片
CCTextureCache::sharedTextureCache()->removeUnusedTextures();  //將引用計數為1的圖片釋放掉
CCTextureCache::sharedTextureCache()->removeTexture();  //單獨釋放某個圖片

CCSpriteFrameCache 與 CCTextureCache 釋放的方法差不多。

值得注意的是釋放的時機,一般在切換場景的時候釋放資源,如果從A場景切換到B場景,調用的函數順序為B::init()---->A::exit()---->B::onEnter() 可如果使用了切換效果,比如CTransitionJumpZoom::transitionWithDuration這樣的函數,則函數的調用順序變為B::init()---->B::onEnter()---->A::exit() 而且第二種方式會有一瞬間將兩個場景的資源疊加在一起,如果不采取過度,很可能會因為內存吃緊而崩潰。

有時強制釋放全部資源時,會使某個正在執行的動畫失去引用而彈出異常,可以調用CCActionManager::sharedManager()->removeAllActions();來解決。

 

下面用簡短的代碼來證實2中這些闡述的事實.

我就用cocos2d-x中的例子Texture2dTest中的 CCTextureCache:remove 和 CCTexture2D:drawAtPoit來進行試驗,這兩個例子的場景是挨着的,CCTextureCache:remove下一個例子就是CCTexture2D:drawAtPoit,為什么要選這兩個,看下面的代碼。

代碼有點長,但是很簡單。不信你看:

void TextureCache1::onEnter()
{
    TextureDemo::onEnter();

    CCSize s = CCDirector::sharedDirector()->getWinSize();
    
    CCSprite *sprite;

    sprite = CCSprite::create("Images/grossinis_sister1.png");
    sprite->setPosition(ccp(s.width/5*1, s.height/2));
    sprite->getTexture()->setAliasTexParameters();
    sprite->setScale(2);
    addChild(sprite);

    //從紋理緩沖區中移除grossinis_sister1.png的圖片數據
    CCTextureCache::sharedTextureCache()->removeTexture(sprite->getTexture());

    
    sprite = CCSprite::create("Images/grossinis_sister1.png");
    sprite->setPosition(ccp(s.width/5*2, s.height/2));
    sprite->getTexture()->setAntiAliasTexParameters();
    sprite->setScale(2);
    addChild(sprite);

    //從紋理緩沖區中移除grossinis_sister1.png的圖片數據
     CCTextureCache::sharedTextureCache()->removeTexture(sprite->getTexture());
    // 2nd set of sprites
    
    sprite = CCSprite::create("Images/grossinis_sister2.png");
    sprite->setPosition(ccp(s.width/5*3, s.height/2));
    sprite->getTexture()->setAliasTexParameters();
    sprite->setScale(2);
    addChild(sprite);
    
    //從紋理緩沖區中移除grossinis_sister2.png的圖片數據
    CCTextureCache::sharedTextureCache()->removeTextureForKey("Images/grossinis_sister2.png");

    sprite = CCSprite::create("Images/grossinis_sister2.png");
    sprite->setPosition(ccp(s.width/5*4, s.height/2));
    sprite->getTexture()->setAntiAliasTexParameters();
    sprite->setScale(2);
    addChild(sprite);

    //從紋理緩沖區中移除grossinis_sister2.png的圖片數據
    CCTextureCache::sharedTextureCache()->removeTextureForKey("Images/grossinis_sister2.png");
}

上面的代碼就是例子:CCTextureCache:remove中的代碼.上面在加載了圖片到紋理緩沖區后就立即從紋理緩沖區中把圖片數據刪除了。

在這個例子場景中我們可以看到四個精靈(四個妹子)。

 

然后再看下一個場景CCTexture2D:drawAtPoit中的代碼:

void TextureDrawAtPoint::onEnter()
{
    TextureDemo::onEnter();

    DWORD dwBegin = GetTickCount();
    m_pTex1 = CCTextureCache::sharedTextureCache()->addImage("Images/grossinis_sister1.png");
    m_pTex2 = CCTextureCache::sharedTextureCache()->addImage("Images/grossinis_sister2.png");
    DWORD dwEnd = GetTickCount()-dwBegin;
    m_pTex1->retain();
    m_pTex2->retain();
}

在這個場景的例子代碼中用到了上一個場景中的兩個圖片文件:grossinis_sister1.png和 grossinis_sister2.png.由於在前一個場景中這兩個圖片文件的數據從紋理緩沖區中刪除了。所以我打斷點測試dwEnd的值是16毫秒。內存多出了240k左右.如果我把上面removeTexture()和removeTextureForKey()屏蔽,則dwEnd的值是0毫秒。內存少了240k,這是多么大的效率提升啊。

 

當然切換場景的時候還是要把前一個場景中用到的圖片數據從紋理緩沖區中刪除,方式內存占用過高。系統直接kill掉你的游戲。

個人建議每一個場景的圖片單獨放在一個文件夾中。切換場景的時候加載場景對應的文件夾中的圖片。這樣的話思路就很清晰。多個場景中公用圖片也放在一個單獨的場景中.


免責聲明!

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



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