[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier
红孩儿Cocos2d-X学习园地QQ群:249941957
Cocos2d-x 2.0 渲染到纹理深入分析
另:本章所用Cocos2d-x版本为:
cocos2d-2.0-x-2.0.2@ Aug 30 2012
http://cn.cocos2d-x.org/download
前几天被邀请到车库咖啡参加了国内Cocos2d-x网上知名讲师的一个小聚会。心里是倍儿感荣幸,回来后越发喜爱Cocos2d-x了,越发喜爱写博客了,不过看着越来越庞大的Cocos2d-x工程和越来越少的时间表,心里也越来越着急了,除了写博客,还有工具箱也需要完善,没办法,只有多熬夜了。
PS:刚刚上传了几个新的工具箱的视频到微博,是制做关键帧动画,导出PLIST并在Cocos2d-x中加载播放。
好,继续今天开学。
在游戏引擎开发中,常常会遇到RenderToTexture,简称RTT,即“渲染到纹理”技术。此项技术有什么用呢?当然有用,比如你要开发一个游戏人物的实时的3D头像,可以放一个摄像机在人物头部的相应位置,并创建一个RenderTexture或RenderTarget,之后将摄相机看到的画面输出到这个RenderTexture或RenderTarget中。然后使用它绘制到一个UI界面中,就可以实时的看到人物头部的动画,表情了。在Cocos2d-x中,RenderTexture则多用来做一些特殊效果,比如在之前讲网格动画一章时,令人赞叹的各种场景画面切割效果,如果没有RTT的支持是无法做到的,将场景画面先RTT到一张纹理上,然后在网格上贴上纹理,网格顶点做各种运动就实现了场景画面被切割的效果。
现在我们将重新认识一下RenderTexture。在TestCpp中它做为独立的演示场景存在,名称为RenderTextureTest。我们先来看一下最重要的类CCRenderTexture。它是用于管理渲染目标纹理的类,与纹理不同的是,它必须是属于
打开CCRenderTexture.h:
//图像格式枚举,可以保存为JPG和PNG两种格式 typedef enum eImageFormat { kCCImageFormatJPEG = 0, kCCImageFormatPNG = 1, } tCCImageFormat; //由结点派生 class CC_DLL CCRenderTexture : public CCNode { //精灵成员变量及存取接口 CC_PROPERTY(CCSprite*, m_pSprite, Sprite) public: //构造 CCRenderTexture(); //析构 virtual ~CCRenderTexture(); //创建一个渲染目标纹理。参数指定大小,像素格式和深度模板缓冲格式。内部调用create实现。 CC_DEPRECATED_ATTRIBUTE static CCRenderTexture * renderTextureWithWidthAndHeight(int w ,int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat); //创建一个渲染目标纹理。参数指定大小,像素格式。内部调用create实现。 CC_DEPRECATED_ATTRIBUTE static CCRenderTexture * renderTextureWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat); //创建一个渲染目标纹理。参数指定大小.。内部调用create实现。 CC_DEPRECATED_ATTRIBUTE static CCRenderTexture * renderTextureWithWidthAndHeight(int w, int h); //第一个函数的create实现。 static CCRenderTexture * create(int w ,int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat); //第二个函数的create实现。 static CCRenderTexture * create(int w, int h, CCTexture2DPixelFormat eFormat); //第三个函数的create实现。 static CCRenderTexture * create(int w, int h); //初始化,参数为大小和像素格式。 bool initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat); //初始化,参数为大小和像素格式,深度模板缓冲格式。 bool initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat); //开始渲染到当前目标纹理。 void begin(); //清空颜色缓冲的值为指定值。 void beginWithClear(float r, float g, float b, float a); //清空颜色缓冲和深度的值为指定值。 void beginWithClear(float r, float g, float b, float a, float depthValue); //清空颜色缓冲和深度,模版值缓冲的值为指定值。 void beginWithClear(float r, float g, float b, float a, float depthValue, int stencilValue); //LUA中调用的结束函数。 inline void endToLua(){ end();}; //结束渲染到当前目标纹理。 void end(); //清空目标纹理的颜色为指定色 void clear(float r, float g, float b, float a); //清空目标纹理的深度值 void clearDepth(float depthValue); //清空目标纹理的模板缓冲值 void clearStencil(int stencilValue); //由目标纹理的数据产生出CCImage实例。 CCImage* newCCImage(); //保存目标纹理到相应图片文件。 bool saveToFile(const char *szFilePath); //保存目标纹理到相应图片文件,指定图像格式。 bool saveToFile(const char *name, tCCImageFormat format); //监听消息,保存目标纹理。 void listenToBackground(CCObject *obj); protected: //FBO对象,即帧缓冲区,一帧中像素数据保存的缓冲区域。可参看http://www.cnblogs.com/aokman/archive/2010/11/14/1876987.html GLuint m_uFBO; //深度缓冲。 GLuint m_uDepthRenderBufffer; //保存旧的FBO对象。 GLint m_nOldFBO; //使用的纹理。 CCTexture2D* m_pTexture; //用于保存当前纹理数据的可变纹理对象。 CCImage* m_pUITextureImage; //像素格式 GLenum m_ePixelFormat; };
然后是CPP:
//构造 CCRenderTexture::CCRenderTexture() : m_pSprite(NULL) , m_uFBO(0) , m_uDepthRenderBufffer(0) , m_nOldFBO(0) , m_pTexture(0) , m_pUITextureImage(NULL) , m_ePixelFormat(kCCTexture2DPixelFormat_RGBA8888) { //设置监听EVENT_COME_TO_BACKGROUND事件,如果响应调用CCRenderTexture::listenToBackground函数。 CCNotificationCenter::sharedNotificationCenter()->addObserver(this, callfuncO_selector(CCRenderTexture::listenToBackground), EVENT_COME_TO_BACKGROUND, NULL); } //析构 CCRenderTexture::~CCRenderTexture() { //释放FBO glDeleteFramebuffers(1, &m_uFBO); //释放深度缓冲 if (m_uDepthRenderBufffer) { glDeleteRenderbuffers(1, &m_uDepthRenderBufffer); } //释放 CC_SAFE_DELETE(m_pUITextureImage); //移除监听响应函数。 CCNotificationCenter::sharedNotificationCenter()->removeObserver(this, EVENT_COME_TO_BACKGROUND); } //监听消息,保存目标纹理。 void CCRenderTexture::listenToBackground(cocos2d::CCObject *obj) { //如果使用可变纹理。 #if CC_ENABLE_CACHE_TEXTURE_DATA //释放上一个m_pUITextureImage CC_SAFE_DELETE(m_pUITextureImage); // 产生当前渲染目标的CCImage m_pUITextureImage = newCCImage(); //如果成功则将纹理m_pTexture中数据填充到可变纹理。 if (m_pUITextureImage) { const CCSize& s = m_pTexture->getContentSizeInPixels(); VolatileTexture::addDataTexture(m_pTexture, m_pUITextureImage->getData(), kTexture2DPixelFormat_RGBA8888, s); } else { CCLOG("Cache rendertexture failed!"); } #endif } //取得精灵成员 CCSprite * CCRenderTexture::getSprite() { return m_pSprite; } //设置精灵成员。 void CCRenderTexture::setSprite(CCSprite* var) { m_pSprite = var; } //创建一个渲染目标纹理。参数指定大小,像素格式。内部调用create实现。 CCRenderTexture * CCRenderTexture::renderTextureWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat) { return CCRenderTexture::create(w, h, eFormat); } //上面的create实现。 CCRenderTexture * CCRenderTexture::create(int w, int h, CCTexture2DPixelFormat eFormat) { //创建一个渲染目标纹理。 CCRenderTexture *pRet = new CCRenderTexture(); //调用相应的初始化函数。 if(pRet && pRet->initWithWidthAndHeight(w, h, eFormat)) { //成功后交由内存管理器进行管理。 pRet->autorelease(); return pRet; } //不成功则释放置空返回NULL。 CC_SAFE_DELETE(pRet); return NULL; } //创建一个渲染目标纹理。参数指定大小,像素格式和深度模板缓冲格式。内部调用create实现。 CCRenderTexture * CCRenderTexture::renderTextureWithWidthAndHeight(int w ,int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat) { return CCRenderTexture::create(w, h, eFormat, uDepthStencilFormat); } //上面的create实现。 CCRenderTexture * CCRenderTexture::create(int w ,int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat) { //创建一个渲染目标纹理。 CCRenderTexture *pRet = new CCRenderTexture(); //调用相应的初始化函数。 if(pRet && pRet->initWithWidthAndHeight(w, h, eFormat, uDepthStencilFormat)) { pRet->autorelease(); return pRet; } CC_SAFE_DELETE(pRet); return NULL; } //创建一个渲染目标纹理。参数指定大小。内部调用create实现。 CCRenderTexture * CCRenderTexture::renderTextureWithWidthAndHeight(int w, int h) { return CCRenderTexture::create(w, h); } //上面的create实现。 CCRenderTexture * CCRenderTexture::create(int w, int h) { //创建一个渲染目标纹理。 CCRenderTexture *pRet = new CCRenderTexture(); //调用相应的初始化函数。 if(pRet && pRet->initWithWidthAndHeight(w, h, kCCTexture2DPixelFormat_RGBA8888, 0)) { pRet->autorelease(); return pRet; } CC_SAFE_DELETE(pRet); return NULL; } //初始化,参数为大小和像素格式格式。 bool CCRenderTexture::initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat) { return initWithWidthAndHeight(w, h, eFormat, 0); } //初始化,参数为大小和像素格式,深度模板缓冲格式。 bool CCRenderTexture::initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat, GLuint uDepthStencilFormat) { //有效性判断。 CCAssert(m_ePixelFormat != kCCTexture2DPixelFormat_A8, "only RGB and RGBA formats are valid for a render texture"); // bool bRet = false; //使用do –while结构来保证出错及时中断退出 do { //宽高要乘以缩放值。 w *= (int)CC_CONTENT_SCALE_FACTOR(); h *= (int)CC_CONTENT_SCALE_FACTOR(); //保存当前的FBO对象。 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO); //创建临时变量保存纹理的真实大小。 unsigned int powW = 0; unsigned int powH = 0; //看是否支持纹理的非2幂次方,做相应的处理。 if( CCConfiguration::sharedConfiguration()->supportsNPOT() ) { //如果支持就用w,h做为纹理大小。 powW = w; powH = h; } else { //如果不支持要转换为2的幂次方大小。 powW = ccNextPOT(w); powH = ccNextPOT(h); } //为像素申请内存。每像素4字节。 void *data = malloc((int)(powW * powH * 4)); //内存申请失败则中断退出。 CC_BREAK_IF(! data); //清空内存为0。 memset(data, 0, (int)(powW * powH * 4)); //保存像素格式。 m_ePixelFormat = eFormat; //创建一个新的纹理。 m_pTexture = new CCTexture2D(); //由上面的像素数据和大小,像素格式信息填充纹理。 if (m_pTexture) { m_pTexture->initWithData(data, (CCTexture2DPixelFormat)m_ePixelFormat, powW, powH, CCSizeMake((float)w, (float)h)); free( data ); } else { free( data ); // ScopeGuard (a simulated Finally clause) would be more elegant. break; } //取得当前的FBO对象。 GLint oldRBO; glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldRBO); // 创建新的FBO并绑定。 glGenFramebuffers(1, &m_uFBO); glBindFramebuffer(GL_FRAMEBUFFER, m_uFBO); //设置将帧缓冲区颜色数据输出到纹理。 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTexture->getName(), 0); //如果有使用深度缓冲。 if (m_uDepthRenderBufffer != 0) { //创建一个渲染目标深度缓冲区并绑定。 glGenRenderbuffers(1, &m_uDepthRenderBufffer); glBindRenderbuffer(GL_RENDERBUFFER, m_uDepthRenderBufffer); //设置当前渲染目标缓冲氏的像素格式和大小。 glRenderbufferStorage(GL_RENDERBUFFER, uDepthStencilFormat, (GLsizei)powW, (GLsizei)powH); //设置将帧缓冲区深度数据输出到这个新创建的深度缓冲区。 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_uDepthRenderBufffer); //如果深度缓冲格式是24位深度,8位模版缓冲,则设置将帧缓冲区的模版数据输出到这个新创建的深度缓冲区中。 if (uDepthStencilFormat == CC_GL_DEPTH24_STENCIL8) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_uDepthRenderBufffer); } // 检查上面操作是否正常执行。 CCAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Could not attach texture to framebuffer"); //设置纹理不使用抗距齿模糊。 m_pTexture->setAliasTexParameters(); //由纹理创建精灵成员。 m_pSprite = CCSprite::createWithTexture(m_pTexture); //释放纹理。 m_pTexture->release(); //精灵设置Y镜像并放入到当前结点下。 m_pSprite->setScaleY(-1); this->addChild(m_pSprite); //设置ALPHA混合方案。 ccBlendFunc tBlendFunc = {GL_ONE, GL_ONE_MINUS_SRC_ALPHA }; m_pSprite->setBlendFunc(tBlendFunc); //还原所用的 glBindRenderbuffer(GL_RENDERBUFFER, oldRBO); glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); bRet = true; } while (0); return bRet; } //开始渲染到当前目标纹理。 void CCRenderTexture::begin() { // 保存当前矩阵 kmGLPushMatrix(); //取得纹理的大小。 const CCSize& texSize = m_pTexture->getContentSizeInPixels(); // 取得设备的窗口大小,计算出视口和投影矩阵 CCDirector *director = CCDirector::sharedDirector(); CCSize size = director->getWinSizeInPixels(); float widthRatio = size.width / texSize.width; float heightRatio = size.height / texSize.height; glViewport(0, 0, (GLsizei)texSize.width, (GLsizei)texSize.height); kmMat4 orthoMatrix; kmMat4OrthographicProjection(&orthoMatrix, (float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1,1 ); kmGLMultMatrix(&orthoMatrix); //取得当前的FBO glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_nOldFBO); glBindFramebuffer(GL_FRAMEBUFFER, m_uFBO); } //清空颜色缓冲。 void CCRenderTexture::beginWithClear(float r, float g, float b, float a) { //开始渲染到目标纹理。 this->begin(); //临时变量,用于保存当前帧的色彩缓冲的清空值数据。 GLfloat clearColor[4]; glGetFloatv(GL_COLOR_CLEAR_VALUE,clearColor); //按指定清空值数值清空色彩缓冲区。 glClearColor(r, g, b, a); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //恢复色彩缓冲的清空值数据。 glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); } //清空颜色缓冲和深度缓冲的值为指定值。 void CCRenderTexture::beginWithClear(float r, float g, float b, float a, float depthValue) { //开始渲染到目标纹理。 this->begin(); //临时变量,用于保存当前帧的各缓冲的清空值数据。 GLfloat clearColor[4]; GLfloat depthClearValue; glGetFloatv(GL_COLOR_CLEAR_VALUE,clearColor); glGetFloatv(GL_DEPTH_CLEAR_VALUE, &depthClearValue); //按指定清空值数值清空各绘冲区。 glClearColor(r, g, b, a); glClearDepth(depthValue); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //恢复各缓冲的清空值数据。 glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); glClearDepth(depthClearValue); } //清空颜色缓冲和深度,模版值缓冲的值为指定值。 void CCRenderTexture::beginWithClear(float r, float g, float b, float a, float depthValue, int stencilValue) { //开始渲染到目标纹理。 this->begin(); //临时变量,用于保存当前帧的各缓冲的清空值数据。 GLfloat clearColor[4]; GLfloat depthClearValue; int stencilClearValue; glGetFloatv(GL_COLOR_CLEAR_VALUE,clearColor); glGetFloatv(GL_DEPTH_CLEAR_VALUE, &depthClearValue); glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &stencilClearValue); //按指定清空值数值清空各绘冲区。 glClearColor(r, g, b, a); glClearDepth(depthValue); glClearStencil(stencilValue); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // 恢复各缓冲的清空值数据。 glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); glClearDepth(depthClearValue); glClearStencil(stencilClearValue); } //结束渲染到当前目标纹理。 void CCRenderTexture::end() { //还原旧的FBO glBindFramebuffer(GL_FRAMEBUFFER, m_nOldFBO); //恢复矩阵 kmGLPopMatrix(); //取得设备。 CCDirector *director = CCDirector::sharedDirector(); //取得窗口大小。 CCSize size = director->getWinSizeInPixels(); // 还原视口及投影矩阵。 glViewport(0, 0, GLsizei(size.width * CC_CONTENT_SCALE_FACTOR()), GLsizei(size.height * CC_CONTENT_SCALE_FACTOR())); if ( director->getProjection() == kCCDirectorProjection3D && CC_CONTENT_SCALE_FACTOR() != 1 ) { glViewport((GLsizei)(-size.width/2), (GLsizei)(-size.height/2), (GLsizei)(size.width * CC_CONTENT_SCALE_FACTOR()), (GLsizei)(size.height * CC_CONTENT_SCALE_FACTOR())); } director->setProjection(director->getProjection()); } //清空目标纹理的颜色为指定色 void CCRenderTexture::clear(float r, float g, float b, float a) { //开始渲染到目标纹理并清空目标纹理的颜色为指定色。 this->beginWithClear(r, g, b, a); //结束渲染到目标纹理。 this->end(); } //清空目标纹理的深度值为指定值。 void CCRenderTexture::clearDepth(float depthValue) { //开始渲染到目标纹理。 this->begin(); // 取得当前深度缓冲清空值。 GLfloat depthClearValue; glGetFloatv(GL_DEPTH_CLEAR_VALUE, &depthClearValue); //设置新深度清空值并清空模板缓冲。 glClearDepth(depthValue); glClear(GL_DEPTH_BUFFER_BIT); //还用深度缓冲清空值。 glClearDepth(depthClearValue); //结束渲染到目标纹理。 this->end(); } //清空目标纹理的模版缓冲值为指定色 void CCRenderTexture::clearStencil(int stencilValue) { // 取得当前模板缓冲清空值。 int stencilClearValue; glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &stencilClearValue); //设置新模板清空值并清空模板缓冲。 glClearStencil(stencilValue); glClear(GL_STENCIL_BUFFER_BIT); //还原模板缓冲清空值。 glClearStencil(stencilClearValue); } //保存目标纹理到相应图片文件。 bool CCRenderTexture::saveToFile(const char *szFilePath) { bool bRet = false; //取得当前渲染目标纹理产生的CCImage。 CCImage *pImage = newCCImage(); if (pImage) { //保存到图片。 bRet = pImage->saveToFile(szFilePath, kCCImageFormatJPEG); } //释放pImage CC_SAFE_DELETE(pImage); return bRet; } //保存目标纹理到相应图片文件,可以指定像素格式。 bool CCRenderTexture::saveToFile(const char *fileName, tCCImageFormat format) { bool bRet = false; //只允许JPG和PNG格式 CCAssert(format == kCCImageFormatJPEG || format == kCCImageFormatPNG, "the image can only be saved as JPG or PNG format"); //取得当前渲染目标纹理产生的CCImage。 CCImage *pImage = newCCImage(); if (pImage) { //取得路径。 std::string fullpath = CCFileUtils::sharedFileUtils()->getWriteablePath() + fileName; //保存到图片。 bRet = pImage->saveToFile(fullpath.c_str(), true); } //释放pImage CC_SAFE_DELETE(pImage); //返回成败。 return bRet; } //由目标纹理的数据产生出CCImage实例。/ CCImage* CCRenderTexture::newCCImage() { //有效性判断。 CCAssert(m_ePixelFormat == kCCTexture2DPixelFormat_RGBA8888, "only RGBA8888 can be saved as image"); if (NULL == m_pTexture) { return NULL; } //取得纹理的大小。 const CCSize& s = m_pTexture->getContentSizeInPixels(); //取得宽高。 int nSavedBufferWidth = (int)s.width; int nSavedBufferHeight = (int)s.height; //定义临时指针变量。 GLubyte *pBuffer = NULL; GLubyte *pTempData = NULL; CCImage *pImage = new CCImage(); //do-while流程结构保证出错及时退出。 do { //创建最终结果的像素数据数组的内存。 CC_BREAK_IF(! (pBuffer = new GLubyte[nSavedBufferWidth * nSavedBufferHeight * 4])); //申请临时像素数据数组的内存。 if(! (pTempData = new GLubyte[nSavedBufferWidth * nSavedBufferHeight * 4])) { delete[] pBuffer; pBuffer = NULL; break; } //开始渲染到目标纹理。 this->begin(); //设置像素按字节对齐。 glPixelStorei(GL_PACK_ALIGNMENT, 1); //将像素写入到pTempData中。 glReadPixels(0,0,nSavedBufferWidth, nSavedBufferHeight,GL_RGBA,GL_UNSIGNED_BYTE, pTempData); this->end(); //将pTempData从下往上拷到pBuffer中,为什么呢?因为前面initWithWidthAndHeight中精灵成员设置了Y镜像,所以图像是上下反的。 for (int i = 0; i < nSavedBufferHeight; ++i) { memcpy(&pBuffer[i * nSavedBufferWidth * 4], &pTempData[(nSavedBufferHeight - i - 1) * nSavedBufferWidth * 4], nSavedBufferWidth * 4); } //填充pImage pImage->initWithImageData(pBuffer, nSavedBufferWidth * nSavedBufferHeight * 4, CCImage::kFmtRawData, nSavedBufferWidth, nSavedBufferHeight, 8); } while (0); //释放申请的内存 CC_SAFE_DELETE_ARRAY(pBuffer); CC_SAFE_DELETE_ARRAY(pTempData); return pImage; }
这个渲染目标纹理的核心功能类解析完之后,我们就没有什么还不可以理解的了。现在我们来打开RenderTextureTest.h:
//演示用的层,做为基类使用 class RenderTextureTest : public CCLayer { public: //加载当前层时的处理 virtual void onEnter(); //取得标题。 virtual std::string title(); //取得副标题。 virtual std::string subtitle(); //重新启动当前演示 void restartCallback(CCObject* pSender); //下一个演示 void nextCallback(CCObject* pSender); //上一个演示 void backCallback(CCObject* pSender); };
对应CPP:
//场景索引 static int sceneIdx = -1; //最大的演示层数量,四个 #define MAX_LAYER 4 //创建相应的演示层 CCLayer* createTestCase(int nIndex) { //根据索引创建相应的演示层实例。 switch(nIndex) { case 0: return new RenderTextureSave(); case 1: return new RenderTextureIssue937(); case 2: return new RenderTextureZbuffer(); case 3: return new RenderTextureTestDepthStencil(); } return NULL; } //下一个演示实例 CCLayer* nextTestCase() { sceneIdx++; sceneIdx = sceneIdx % MAX_LAYER; CCLayer* pLayer = createTestCase(sceneIdx); pLayer->autorelease(); return pLayer; } //上一个演示实例 CCLayer* backTestCase() { sceneIdx--; int total = MAX_LAYER; if( sceneIdx < 0 ) sceneIdx += total; CCLayer* pLayer = createTestCase(sceneIdx); pLayer->autorelease(); return pLayer; } //重新运行当前的演示实例。 CCLayer* restartTestCase() { CCLayer* pLayer = createTestCase(sceneIdx); pLayer->autorelease(); return pLayer; } //加载当前层时的处理。 void RenderTextureTest::onEnter() { //先调用基类的相应函数。 CCLayer::onEnter(); //取得屏幕大小 CCSize s = CCDirector::sharedDirector()->getWinSize(); //创建相应的文字标签。 CCLabelTTF* label = CCLabelTTF::create(title().c_str(), "Arial", 26); //将文字标签放到当前层下。 addChild(label, 1); //将文字标签放置在屏幕*上部。 label->setPosition( ccp(s.width/2, s.height-50) ); //取得副标题。 std::string strSubtitle = subtitle(); if( ! strSubtitle.empty() ) { //如果副标题文字有效,创建相应的文字标签显示副标题。 CCLabelTTF* l = CCLabelTTF::create(strSubtitle.c_str(), "Thonburi", 16); //将副标题放入当前层下并放在主标题之下位置。 addChild(l, 1); l->setPosition( ccp(s.width/2, s.height-80) ); } //创建用于控制动画演示的菜单项 //用于点击返回上一个动画演示的菜单项。 CCMenuItemImage *item1 = CCMenuItemImage::create("Images/b1.png", "Images/b2.png", this, menu_selector(RenderTextureTest::backCallback) ); //用于点击重新运行当前动画演示的菜单项。 CCMenuItemImage *item2 = CCMenuItemImage::create("Images/r1.png","Images/r2.png", this, menu_selector(RenderTextureTest::restartCallback) ); //用于点击重新运行下一个动画演示的菜单项。 CCMenuItemImage *item3 = CCMenuItemImage::create("Images/f1.png", "Images/f2.png", this, menu_selector(RenderTextureTest::nextCallback) ); //由上面三个菜单项创建菜单。 CCMenu *menu = CCMenu::create(item1, item2, item3, NULL); //设置菜单和各菜单项的位置。 menu->setPosition( CCPointZero ); item1->setPosition( ccp( s.width/2 - item2->getContentSize().width*2, item2->getContentSize().height/2) ); item2->setPosition( ccp( s.width/2, item2->getContentSize().height/2) ); item3->setPosition( ccp( s.width/2 + item2->getContentSize().width*2, item2->getContentSize().height/2) ); //将菜单放入到当前层下。 addChild(menu, 1); } //响应重启当前演示的函数。 void RenderTextureTest::restartCallback(CCObject* pSender) { //创建一个新的场景,创建一个新的当前演示层并放入到这个场景中。 CCScene* s = new RenderTextureScene(); s->addChild(restartTestCase()); //运行创建的场景。 CCDirector::sharedDirector()->replaceScene(s); s->release(); } //响应进行下一个演示的函数。 void RenderTextureTest::nextCallback(CCObject* pSender) { //创建一个新的场景,创建下一个演示层并放入到这个场景中。 CCScene* s = new RenderTextureScene(); s->addChild( nextTestCase() ); //运行创建的场景。 CCDirector::sharedDirector()->replaceScene(s); s->release(); } //响应进行上一个演示的函数。 void RenderTextureTest::backCallback(CCObject* pSender) { //创建一个新的场景,创建上一个演示层并放入到这个场景中。 CCScene* s = new RenderTextureScene(); s->addChild( backTestCase() ); //运行创建的场景。 CCDirector::sharedDirector()->replaceScene(s); s->release(); } //取得标题。 std::string RenderTextureTest::title() { return "No title"; } //取得副标题。 std::string RenderTextureTest::subtitle() { return ""; }
第一个演示:保存渲染目标纹理。
说明:在屏幕上触屏移动,可以不断的使用随机顶点色的火球点图片精灵来做为刷子绘制线条。点击右边的“Clear”可以将屏幕清为随机色。而这些图像都是输出到纹理的。点击“SaveImage”可以保存这个纹理为PNG或JPG。
截图:
代码:
//由上面的类派生出的类,可以将目标纹理保存成为图片。 class RenderTextureSave : public RenderTextureTest { public: //构造 RenderTextureSave(); //析构 ~RenderTextureSave(); //取得标题 virtual std::string title(); //取得副标题。 virtual std::string subtitle(); //响应触屏时移动事件 virtual void ccTouchesMoved(CCSet* touches, CCEvent* event); //清除图片图像 void clearImage(CCObject *pSender); //保存纹理到图片 void saveImage(CCObject *pSender); private: //目标纹理 CCRenderTexture *m_pTarget; //代表刷子的精灵 CCSprite *m_pBrush; };
对应CPP:
//构选 RenderTextureSave::RenderTextureSave() { //取得屏幕大小 CCSize s = CCDirector::sharedDirector()->getWinSize(); //创建一个目标纹理,用于把场景渲染到其中。 m_pTarget = CCRenderTexture::create(s.width, s.height, kCCTexture2DPixelFormat_RGBA8888); //占用它,对其引用计数器加一。 m_pTarget->retain(); //设置目标纹理的位置放在屏幕*。 m_pTarget->setPosition(ccp(s.width / 2, s.height / 2)); //将目标纹理放入到当前层下。 this->addChild(m_pTarget, -1); //创建一个刷子的精灵,载入的是火球的图片。 m_pBrush = CCSprite::create("Images/fire.png"); //占用它,对其引用计数器加一。 m_pBrush->retain(); //设置精灵为红色 m_pBrush->setColor(ccRED); //设置透明度为20% m_pBrush->setOpacity(20); //设置当前层响应触屏事件。 this->setTouchEnabled(true); //创建一个菜单项文字的字体,大小为16。 CCMenuItemFont::setFontSize(16); //创建两个菜单项文字,分别为“保存图像”和“清空图像”。 CCMenuItem *item1 = CCMenuItemFont::create("Save Image", this, menu_selector(RenderTextureSave::saveImage)); CCMenuItem *item2 = CCMenuItemFont::create("Clear", this, menu_selector(RenderTextureSave::clearImage)); //由这两个菜单项创建菜单,并放入当前层下。 CCMenu *menu = CCMenu::create(item1, item2, NULL); this->addChild(menu); //设置菜单项按纵向排列。 menu->alignItemsVertically(); //设置菜单的位置。 menu->setPosition(ccp(s.width - 80, s.height - 30)); } //取得标题。 string RenderTextureSave::title() { return "Touch the screen"; } //取得副标题。 string RenderTextureSave::subtitle() { return "Press 'Save Image' to create an snapshot of the render texture"; } //清除图片图像。 void RenderTextureSave::clearImage(cocos2d::CCObject *pSender) { //对目标纹理调用clear即可以将其清空为相应的颜色。 //这里对,R,G,B,A的色彩分量做了0~1间的随机,产生出随机的色彩。 m_pTarget->clear(CCRANDOM_0_1(), CCRANDOM_0_1(), CCRANDOM_0_1(), CCRANDOM_0_1()); } //保存图像。 void RenderTextureSave::saveImage(cocos2d::CCObject *pSender) { //创建一个计数器,用来统计保存的次数。 static int counter = 0; //要保存文件的名称。 char png[20]; sprintf(png, "image-%d.png", counter); char jpg[20]; sprintf(jpg, "image-%d.jpg", counter); //对目标纹理调用saveToFile将纹理上的图像保存为PNG和JPG两种格式的图片。 m_pTarget->saveToFile(png, kCCImageFormatPNG); m_pTarget->saveToFile(jpg, kCCImageFormatJPEG); //取得纹理对应的CCImage实例指针 CCImage *pImage = m_pTarget->newCCImage(); //将PNG图片和CCImage实例中的信息加载到纹理管理器中。 CCTexture2D *tex = CCTextureCache::sharedTextureCache()->addUIImage(pImage, png); //删除pImage CC_SAFE_DELETE(pImage); //由纹理tex创建精灵sprite。 CCSprite *sprite = CCSprite::createWithTexture(tex); //设置缩放值为原来0.3倍 sprite->setScale(0.3f); //将精灵放到当前层下。 addChild(sprite); //设置精灵位置。 sprite->setPosition(ccp(40, 40)); sprite->setRotation(counter * 3); //打印日志 CCLOG("Image saved %s and %s", png, jpg); //计数器加一 counter++; } //析构 RenderTextureSave::~RenderTextureSave() { //对占用的目标纹得和刷子精灵的引用计数减一。 m_pBrush->release(); m_pTarget->release(); //纹理管理器释放不用的纹理。 CCTextureCache::sharedTextureCache()->removeUnusedTextures(); } //响应触屏时移动事件 void RenderTextureSave::ccTouchesMoved(CCSet* touches, CCEvent* event) { //取得触点 CCTouch *touch = (CCTouch *)touches->anyObject(); //取得触点的当前位置。 CCPoint start = touch->getLocation(); //取得触点的上一个位置。 CCPoint end = touch->getPreviousLocation(); // 开始渲染到目标纹理 m_pTarget->begin(); //取得当前位置和上一个位置的距离。 float distance = ccpDistance(start, end); //如果距离大于1像素。 if (distance > 1) { int d = (int)distance; //则在上一个位置和当前位置间连一条线,每个像素渲染一下刷子精灵。 for (int i = 0; i < d; i++) { //计算X和Y方向的偏移。 float difx = end.x - start.x; float dify = end.y - start.y; //通过偏移计算斜率。 float delta = (float)i / distance; //通过直线公式计数出连像上第i个位置的像素位置设置给精灵。 m_pBrush->setPosition(ccp(start.x + (difx * delta), start.y + (dify * delta))); //设置一个随机的旋转值给精灵。 m_pBrush->setRotation(rand() % 360); //在给一个限定范围的随机缩放大小给精灵。 float r = (float)(rand() % 50 / 50.f) + 0.25f; m_pBrush->setScale(r); /*m_pBrush->setColor(ccc3(CCRANDOM_0_1() * 127 + 128, 255, 255));*/ // Use CCRANDOM_0_1() will cause error when loading libtests.so on android, I don't know why. //设置一个随机的颜色给精灵。 m_pBrush->setColor(ccc3(rand() % 127 + 128, 255, 255)); // Call visit to draw the brush, don't call draw.. //将精灵绘制出来。 m_pBrush->visit(); } } //结束渲染到纹理,则由精灵构成的连线图像就被渲染到纹理中了。 m_pTarget->end(); }
第二个演示:渲染小球到目标纹理。
说明:将纵向排列的两个小球的精灵渲染到目标纹理上,在场景中左边一列显示两个小球,右边一列显示目标纹理。
截图:
代码:
//由上面的类派生出的类,。 class RenderTextureIssue937 : public RenderTextureTest { public: //构造 RenderTextureIssue937(); //取得标题 virtual std::string title(); //取得副 标题 virtual std::string subtitle(); };
对应CPP:
//构造 RenderTextureIssue937::RenderTextureIssue937() { //创建一个色层做为背景放入到当前层下。 CCLayerColor *background = CCLayerColor::create(ccc4(200,200,200,255)); addChild(background); //创建第一个精灵 CCSprite *spr_premulti = CCSprite::create("Images/fire.png"); spr_premulti->setPosition(ccp(16,48)); //创建第二个精灵。 CCSprite *spr_nonpremulti = CCSprite::create("Images/fire.png"); spr_nonpremulti->setPosition(ccp(16,16)); //创建一个渲染目标纹理。 CCRenderTexture *rend = CCRenderTexture::create(32, 64, kCCTexture2DPixelFormat_RGBA8888); //如果失败则返回 if (NULL == rend) { return; } //开始渲染图像到目标纹理。 rend->begin(); //先渲染第一个精灵。 spr_premulti->visit(); //再渲染第二个精灵。 spr_nonpremulti->visit(); //结束渲染到纹理。 rend->end(); //取得屏幕大小。 CCSize s = CCDirector::sharedDirector()->getWinSize(); //设置第一个精灵的位置。 spr_premulti->setPosition(ccp(s.width/2-16, s.height/2+16)); //设置第二个精灵的位置。 spr_nonpremulti->setPosition(ccp(s.width/2-16, s.height/2-16)); //设置目标纹理的位置。 rend->setPosition(ccp(s.width/2+16, s.height/2)); //将上面三个结点都放到当前层之下。 addChild(spr_nonpremulti); addChild(spr_premulti); addChild(rend); } //取得标题。 std::string RenderTextureIssue937::title() { return "Testing issue #937"; } //取得副标题。 std::string RenderTextureIssue937::subtitle() { return "All images should be equal..."; }
第三个演示:渲染不同Z值的精灵到目标纹理。
说明:在屏幕上创建了一系列不同Z值的文字标签和精灵,当触屏按下松开后将屏幕画面绘制到目标纹理,创建一个精灵使用这个目标纹理并在屏幕上运行一个显示到渐隐的动画。
截图:
代码:
//由上面的类派生出的类,。 class RenderTextureZbuffer : public RenderTextureTest { public: //构造 RenderTextureZbuffer(); //响应触屏的一些事件 //按下并移动事件的响应 virtual void ccTouchesMoved(CCSet* touches, CCEvent* event); //按下事件的响应 virtual void ccTouchesBegan(CCSet* touches, CCEvent* event); //抬起事件的响应 virtual void ccTouchesEnded(CCSet* touches, CCEvent* event); //取得标题。 virtual std::string title(); //取得副标题。 virtual std::string subtitle(); //渲染屏幕快照 void renderScreenShot(); private: //使用的精灵批次优化处理结点。 cocos2d::CCSpriteBatchNode *mgr;; //用到的若干精灵。 cocos2d::CCSprite *sp1; cocos2d::CCSprite *sp2; cocos2d::CCSprite *sp3; cocos2d::CCSprite *sp4; cocos2d::CCSprite *sp5; cocos2d::CCSprite *sp6; cocos2d::CCSprite *sp7; cocos2d::CCSprite *sp8; cocos2d::CCSprite *sp9; };
对应CPP:
//构造 RenderTextureZbuffer::RenderTextureZbuffer() { //设置当前层响应触屏事悠扬。 this->setTouchEnabled(true); //取得屏幕大小。 CCSize size = CCDirector::sharedDirector()->getWinSize(); //创建文字标签。 CCLabelTTF *label = CCLabelTTF::create("vertexZ = 50", "Marker Felt", 64); //设置文字标签的位置并放入到当前层下。 label->setPosition(ccp(size.width / 2, size.height * 0.25f)); this->addChild(label); //创建第二个文字标签。 CCLabelTTF *label2 = CCLabelTTF::create("vertexZ = 0", "Marker Felt", 64); //设置文字标签的位置并放入到当前层下。 label2->setPosition(ccp(size.width / 2, size.height * 0.5f)); this->addChild(label2); //创建第三个文字标签。 CCLabelTTF *label3 = CCLabelTTF::create("vertexZ = -50", "Marker Felt", 64); //设置文字标签的位置并放入到当前层下。 label3->setPosition(ccp(size.width / 2, size.height * 0.75f)); this->addChild(label3); //分别对三个文字标签设置不同的顶点Z值。 label->setVertexZ(50); label2->setVertexZ(0); label3->setVertexZ(-50); //由circle.plist CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Images/bugs/circle.plist"); //创建相应的精灵批次优化结点。 mgr = CCSpriteBatchNode::create("Images/bugs/circle.png", 9); this->addChild(mgr); //创建9个精灵 sp1 = CCSprite::createWithSpriteFrameName("circle.png"); sp2 = CCSprite::createWithSpriteFrameName("circle.png"); sp3 = CCSprite::createWithSpriteFrameName("circle.png"); sp4 = CCSprite::createWithSpriteFrameName("circle.png"); sp5 = CCSprite::createWithSpriteFrameName("circle.png"); sp6 = CCSprite::createWithSpriteFrameName("circle.png"); sp7 = CCSprite::createWithSpriteFrameName("circle.png"); sp8 = CCSprite::createWithSpriteFrameName("circle.png"); sp9 = CCSprite::createWithSpriteFrameName("circle.png"); //将其加入到批次优化结点中,给予不同的Z值。 mgr->addChild(sp1, 9); mgr->addChild(sp2, 8); mgr->addChild(sp3, 7); mgr->addChild(sp4, 6); mgr->addChild(sp5, 5); mgr->addChild(sp6, 4); mgr->addChild(sp7, 3); mgr->addChild(sp8, 2); mgr->addChild(sp9, 1); //对这九个精灵设置不同的顶点Z值。 sp1->setVertexZ(400); sp2->setVertexZ(300); sp3->setVertexZ(200); sp4->setVertexZ(100); sp5->setVertexZ(0); sp6->setVertexZ(-100); sp7->setVertexZ(-200); sp8->setVertexZ(-300); sp9->setVertexZ(-400); //设置第九个放大2倍,顶点色为黄色。 sp9->setScale(2); sp9->setColor(ccYELLOW); } //取得标题。 string RenderTextureZbuffer::title() { return "Testing Z Buffer in Render Texture"; } //取得副标题。 string RenderTextureZbuffer::subtitle() { return "Touch screen. It should be green"; } //按下事件的响应 void RenderTextureZbuffer::ccTouchesBegan(cocos2d::CCSet *touches, cocos2d::CCEvent *event) { //遍历所有的触点。 CCSetIterator iter; CCTouch *touch; for (iter = touches->begin(); iter != touches->end(); ++iter) { //将九个精灵放在触点位置。 touch = (CCTouch *)(*iter); CCPoint location = touch->getLocation(); sp1->setPosition(location); sp2->setPosition(location); sp3->setPosition(location); sp4->setPosition(location); sp5->setPosition(location); sp6->setPosition(location); sp7->setPosition(location); sp8->setPosition(location); sp9->setPosition(location); } } //按下并移动事件的响应。 void RenderTextureZbuffer::ccTouchesMoved(CCSet* touches, CCEvent* event) { //遍历所有的触点。 CCSetIterator iter; CCTouch *touch; for (iter = touches->begin(); iter != touches->end(); ++iter) {//将九个精灵放在触点位置。 touch = (CCTouch *)(*iter); CCPoint location = touch->getLocation(); sp1->setPosition(location); sp2->setPosition(location); sp3->setPosition(location); sp4->setPosition(location); sp5->setPosition(location); sp6->setPosition(location); sp7->setPosition(location); sp8->setPosition(location); sp9->setPosition(location); } } //抬起事件的响应 void RenderTextureZbuffer::ccTouchesEnded(CCSet* touches, CCEvent* event) { //渲染屏幕快照 this->renderScreenShot(); } //渲染屏幕快照 void RenderTextureZbuffer::renderScreenShot() { //创建一个512,512大小的渲染目标纹理。 CCRenderTexture *texture = CCRenderTexture::create(512, 512); //如果无效直接返回。 if (NULL == texture) { return; } //设置锚点为左下角。 texture->setAnchorPoint(ccp(0, 0)); //开始渲染到目标纹理。 texture->begin(); //将当前层渲染一遍。 this->visit(); //结束渲染到目标纹理。 texture->end(); //创建一个精灵。 CCSprite *sprite = CCSprite::createWithTexture(texture->getSprite()->getTexture()); //设置精灵的位置,透明度,Y方向翻转。 sprite->setPosition(ccp(256, 256)); sprite->setOpacity(182); sprite->setFlipY(1); //将精灵放入到当前层的最前面。 this->addChild(sprite, 999999); //设置为绿色。 sprite->setColor(ccGREEN); //让精灵运行一个动画序列,表现为渐渐消失。 sprite->runAction(CCSequence::create(CCFadeTo::create(2, 0), CCHide::create(), NULL)); }
第四个演示:测试模版缓冲目标纹理。
说明:
关于模版缓冲可以先看一下博文:
http://www.cnblogs.com/aokman/archive/2010/12/13/1904723.html
本例是开启模版测试,然后绘制球精灵,但只写A通过模版测试,对通过的像素位置设置模版值为1,之后向右上移动1/4大小球的位置,对模版值为0的区域设置通过来绘制球精灵,这样的效果约会有1/4缺失。
注意:不幸的是,经测试所有版本似乎都无效,我不知道是不是哪里存在BUG,还是我的错误。
截图:
现实是这样的:
按代码的做法我用Photoshop做了一个结果图,本应是这样:
代码:
//由上面的基类派生出的类,。 class RenderTextureTestDepthStencil : public RenderTextureTest { public: //构造 RenderTextureTestDepthStencil(); //取得标题。 virtual std::string title(); //取得副标题。 virtual std::string subtitle(); };
对应CPP:
//构造 RenderTextureTestDepthStencil::RenderTextureTestDepthStencil() { //取得屏幕大小。 CCSize s = CCDirector::sharedDirector()->getWinSize(); //创建一个精灵,设置其位置,10倍放大。 CCSprite *sprite = CCSprite::create("Images/fire.png"); sprite->setPosition(ccp(s.width * 0.25f, 0)); sprite->setScale(10); //创建一个目标纹理,用于存储深度缓冲。 CCRenderTexture *rend = CCRenderTexture::create(s.width, s.height, kCCTexture2DPixelFormat_RGBA4444, CC_GL_DEPTH24_STENCIL8); //设置模版缓冲的掩码值。 glStencilMask(0xFF); //清空目标纹理的深度缓冲和模板缓冲为0。 rend->beginWithClear(0, 0, 0, 0, 0, 0); //开启模版测试。 glEnable(GL_STENCIL_TEST); //设置绘制图像的像素部分总是可以通过模版测试 glStencilFunc(GL_ALWAYS, 1, 0xFF); //设置更新操作为: //1,模板测试失败时保持当前的模板值不变。 //2,模板测试通过,深度测试失败时保持当前的模板值不变。 //3,模板测试通过,深度测试通过时将当前的模板值设置为参考值。 glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); //设置对颜色缓冲区的R,G,B,A位是否执行写入操作。这里只写A。 glColorMask(0, 0, 0, 1); //将精灵渲染一遍。 sprite->visit(); //设置精灵位置向右上偏移一些 sprite->setPosition(ccpAdd(sprite->getPosition(), ccpMult(ccp(sprite->getContentSize().width * sprite->getScale(), sprite->getContentSize().height * sprite->getScale()), 0.5))); //设置比较条件,如果像素参考值不等于模板值通过测试。 glStencilFunc(GL_NOTEQUAL, 1, 0xFF); //设置对颜色缓冲区的R,G,B,A位执行写入操作。 glColorMask(1, 1, 1, 1); //精灵再渲染一遍。 sprite->visit(); //结束渲染到目标纹理。 rend->end(); //关闭模版缓冲测试。 glDisable(GL_STENCIL_TEST); //设置目标纹理的位置。 rend->setPosition(ccp(s.width * 0.5f, s.height * 0.5f)); //将目标纹理放到当前层下。 this->addChild(rend); } //标题。 std::string RenderTextureTestDepthStencil::title() { return "Testing depthStencil attachment"; } //取得副标题。 std::string RenderTextureTestDepthStencil::subtitle() { return "Circle should be missing 1/4 of its region"; }
上面这个例子如果真存在问题,希望Cocos2d-x开发组能有看到并做下修改。最后代码余下的就是场景了:
//演示用的场景 class RenderTextureScene : public TestScene { public: //运行场景时的处理 virtual void runThisTest(); };
对应CPP:
//运行演示场景。 void RenderTextureScene::runThisTest() { //创建下一个演示层放入到当前的场景中。 CCLayer* pLayer = nextTestCase(); addChild(pLayer); //运行当前场景。 CCDirector::sharedDirector()->replaceScene(this); }
渲染到纹理,就是这么个东西了。很有用的技术,希望各位好好掌握。周末愉快,下课!