如何在cocos2d-x实现高效的mask

作者&投稿:万咬 (若有异议请与网页底部的电邮联系)
如何在cocos2d-x实现高效的mask~

Cocos2d-x3.0

//a static method
Sprite* HelloWorld::maskedSpriteWithSprite(Sprite* textureSprite, Sprite* maskSprite)
{
// 1
RenderTexture * rt = RenderTexture::create( maskSprite->getContentSize().width,
maskSprite->getContentSize().height );

// 2
maskSprite->setPosition(maskSprite->getContentSize().width/2,
maskSprite->getContentSize().height/2);
textureSprite->setPosition(textureSprite->getContentSize

我们将使用这张图片来给我们的日历图片添加一个边框,是那种带有波纹效果的边框,而不是四边形的。这张图片透明的部分,就是遮罩效果的部分,而白色区域则是日历图片会显示的区域。
为了实现这个效果,我们将使用OpenGL的混合模式。
如果你回过头去看《如何使用CCRenderTexture来动态创建纹理》这篇教程的话,我们在那里讨论过OpenGL的混合模式。我在那里提到过一个非常方便的在线工具可以用来可见化调节混合模式的效果。
为了完成我们想要的效果,我们需要采取下面的策略:
我们首先渲染mask精灵,把src color(就是mask精灵)设置为GL_ONE,并且把destination
color(一个空的buffer)设置为GL_ZERO。所以,效果就是简单的把mask图片显示来。
接下来,我们渲染日历图片精灵。把src
color(日历)设置为GL_DST_ALPHA。意思是,看看mask图片的当前alpha值是多少,如果是0(完全透明),那么就显示mask的。如果是1(完全不透明),那么就显示日历图片。(译者注:如果大家对此不明白,可以参考这个链接)。然后把dst
color(the mask)设计成GL_ZERO,这样的话,之前渲染上去的mask就消失了。
很酷吧!你可能会觉得我们只需要先把mask精灵渲染上去,然后再渲染日历精灵,并且指定这两个精灵的blendFunc就行了。可是,实际上这样是行不通的!
上面所提到的混合算法,当精灵下面还有一些精灵在渲染的时候就会出问题---比如背景图片上面有一个精灵。这是因为,这里作了一个假设,在上面做完1那个步骤之后,imgae
buffer里面只存在唯一一张图片,那就是mask。(这个假设当然是不正确的啦,因为你要切换日历图片对不对?)
因此,我们需要一种方式,可以建立一个干净的“黑板”,然后在那执行1,2步来制作一个遮罩纹理。很幸运的是,用RenderTexture非常方便。
Masking
and CCRenderTexture]
RenderTexture是一个这样的类,它可以让你在屏幕之外的buffer里面渲染。
它用起来非常方便,主要有以下原因---你可以使用它来给你的游戏截屏,可以高效地缓存用户渲染的内容,可以在运行时动态地创建sprite
sheet,或者,就像本教程中一样,可以制作一个mask sprite。
为了使用RenderTexture,你需要采取以下4步:
创建RenderTexture类,以像素为单位,指定你想要绘制的纹理的宽度和高度。
调用RenderTexture的begin方法来初始化渲染操作。
调用OpenGL函数来绘制实际的内容--但是,这些OpenGL调用最终都会绘制到屏幕之外去,而不会影响游戏中现在渲染的图像。
调用RenderTexture的end方法来结束绘制操作。一旦你完成之后,RenderTexture有一个sprite属性,你可以把它当用Sprite来用。
不要觉得第3步很奇怪---因为你正在使用Cocos2d,90%的情况你是不需要手动直接调用OpenGL函数的。但是,如果你想渲染一个节点的话,你可以直接调用某一个节点的visit方法,如sprite->visit,然后这个函数会自动为你发射一些OpenGL函数指针给图形硬件去显示。
这里有一点需要注意的就是坐标问题。(0,0)点是渲染的纹理的左下角位置,所以,你在使用RenderTexture的时候,一定要把坐标设置对。
好了,你可能听得有些烦了,程序员还是喜欢看代码的。好,让我们开始coding吧!
给精灵添加遮罩: 最终实现
打开HelloWorldScene.m,然后在init方法上面添加下面的方法,注意这个方法是静态的,在头文件声明时需要注意:
//a static method
Sprite* HelloWorld::maskedSpriteWithSprite(Sprite* textureSprite, Sprite* maskSprite)
{
// 1
RenderTexture * rt = RenderTexture::create( maskSprite->getContentSize().width,
maskSprite->getContentSize().height );
// 2
maskSprite->setPosition(maskSprite->getContentSize().width/2,
maskSprite->getContentSize().height/2);
textureSprite->setPosition(textureSprite->getContentSize().width/2,
textureSprite->getContentSize().height/2);
// 3
maskSprite->setBlendFunc( BlendFunc{GL_ONE, GL_ZERO} );
textureSprite->setBlendFunc( BlendFunc{GL_DST_ALPHA, GL_ZERO} );
// 4
rt->begin();
maskSprite->visit();
textureSprite->visit();
rt->end();
// 5
Sprite *retval = Sprite::createWithTexture(rt->getSprite()->getTexture());
retval->setFlippedY(true);
return retval;
}
让我们一步步来分解下面的操作:
使用mask精灵的大小来创建CCRenderTexture
重新设置mask精灵和texture精灵的位置,使它们的左下角是(0,0)
按照我们之前讨论的,设置每个精灵的blendFunc。
调用CCRenderTexture的begin方法来开始渲染操作,然后依次渲染mask和texture精灵,最后调用end方法。
基于CCRenderTexture的sprite属性的texture创建一个新的精灵,同时翻转y,因为纹理创建出来是倒的。
好了,接下来,我们可以使用上面的函数来制作遮罩的效果了:
//cal->setPosition(visibleSize.width/2, visibleSize.height/2);
Sprite * mask = Sprite::create("CalendarMask.png");
Sprite * maskedCal = maskedSpriteWithSprite(cal, mask);
maskedCal->setPosition(visibleSize.width/2, visibleSize.height/2);
//pRet->addChild(cal);
pRet->addChild(maskedCal);
编译并运行

cocos2d-x目前无法利用opengl es2.0的shader来实现mask效果,如果按照老外提供的renderTexture来实现性能就太差了。
遍寻网上后在cocoachina上找到一个深入了解OpenGL-模板测试,经过改造后终于可以在cocos2d-x中使用,与各位同仁分享一下。

先说下模板缓冲(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中找到的didFinishLaunchingWithOpti*****方法,将其中的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,性能一样无损,还可以支持渐变,但也有其局限性,且听下回分解。


对于入行五年多的AS3程序员,未来之路何在?转型是否明智?
然而,OC和Swift的局限性不容忽视,它们绑定于Apple平台,限制了你的职业发展。在众多路径中,JAVA的优先级可能高于它们。如果你的专长在页游,Unity和cocos2d-x是值得考虑的转型方向,但虚幻4的挑战,恐怕超出了你的现有基础。回顾过去,我在如何学习Flash?的回答中,分享了Flash的优势领域。即使在Adobe的...

柘城县15854644543: 如何在cocos2d - x实现高效的mask -
充之盐酸: Cocos2d-x3.0//a static methodSprite* HelloWorld::maskedSpriteWithSprite(Sprite* textureSprite, Sprite* maskSprite){ // 1 RenderTexture * rt = RenderTexture::create( maskSprite->getContentSize().width, maskSprite->getContentSize().height ); // 2 ...

柘城县15854644543: 如何在cocos2d - x实现高效的mask -
充之盐酸: 先说下模板缓冲(stencil buffer),这在05年还算是一个比较普及的技术.cocos2d-x现在的版本是不支持stencil buffer的,但opengl es是支持的.可以简单的动手改造一下:创建stencil buffer.在ES1Renderer.m文件中找到resizeFromLayer方法...

柘城县15854644543: 请求cocos2dx简单高效碰撞检测的方法,不断的在update中遍历所有子弹和敌人感觉好低效. -
充之盐酸: 可使用物理引擎来处理碰撞,官方的封装,会比你的效率高

柘城县15854644543: 如何使用Cocos2d - x完美呈现捕鱼3 -
充之盐酸: 捕鱼达人3与前两部相比,技术上有哪些突破和创新?捕鱼达人3作为前两代成功作品的续作,我们需要从玩法到技术,全面超越前代.所以在技术上,我们主要做了下面一些突破和创新:1)鱼的3D渲染. 跟2D渲染相比,3D渲染可以让鱼看起...

柘城县15854644543: 怎样实现cocos2d - x之文字渲染 -
充之盐酸: // 1、创建一段文本// create函数的三个参数分别为:文本内容、字体和字体大小 CCLabelTTF *font=CCLabelTTF::create("Hello World","微软雅黑",48);// 2、获取文本的尺寸// font->getContentSize()返回的是一个CCSize类型的值// 该值...

柘城县15854644543: cocos2d - x实现剧情对话的打字机效果,求思路,求教程,求指点 -
充之盐酸: 获取对话内容字符串的长度,然后为相应显示用的CCLabelBMFont run一个由CCDelayTime和CCCallFuncND组成的动画序列,回调函数里设置CCLabelBMFont 的显示内容,显示长度依次+1,直到最大值并播放音效就可以了.另外,和提这个需求的策划说一声:别用小时候玩游戏的意淫来祸害程序猿了.这种不实用的慢节奏东西根本不适合手机游戏,这年头有人看字幕么,就算有,有人看这种一个字一个字打出来的SB字幕么?

柘城县15854644543: cocos2d - x怎么实现跨平台 -
充之盐酸: 基本流程如下:1.下载Cocos2d-x库,分别在Windows平台和Mac平台上解压,并安装对应的向导.2.在Windows平台和Mac平台上创建同名工程.3.在Windows平台上开发代码,保证将代码放在Class目录和Resource目录.4.把Windows平台上开发的Class目录和Resource目录中的代码拷贝到Mac电脑上,覆盖同名目录.5.在Xcode中将刚添加的代码和资源加入工程.6.在Xcode中启动编译,在虚拟机和真机上测试,完工.

柘城县15854644543: 关于cocos2d - x几种画图方法的用法与思考 -
充之盐酸: CCRenderTexture自己的理解CCRenderTexture类似一张空白的画布,用户通过自定义笔刷(CCSprite*),在touch事件中把笔刷的移动痕迹记录起来,从而画出各种艺术效果.记录方法很简单,首先CCRenderTexture调用自己的begin()函数...

柘城县15854644543: 各位大神cocos2d - x如何实现一个下拉列表? 最好有代码附上!!感激不尽! -
充之盐酸: 如果你是在手机上做的话不建议使用下拉列表这种东西因为手机屏幕小下拉列表表现不够清晰,最好的解决方案是把下拉列表的内容尽量放到全屏然后用滚动条的方式来实现.具体可以参考下面这篇文章: http://codingnow.cn/cocos2d-x/1024.html

柘城县15854644543: 如何使用cocos2d - x实现经典射击游戏 -
充之盐酸: 本视频教程将使用cocos2d-x 2.0引擎实现射击游戏中游戏背景,飞机,子弹的逻辑,通过该实例了解CCLayer CCSprite CCScene和动画的使用,以及定时逻辑编程." data_time="1:51

本站内容来自于网友发表,不代表本站立场,仅表示其个人看法,不对其真实性、正确性、有效性作任何的担保
相关事宜请发邮件给我们
© 星空见康网