[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier
Cocos2d-x2.1.1-ClippingNodeTest深入分析(二)
用冯巩的一句台词:“好久不见,我可想死你们啦!”。红孩儿五月份没有写东西,很对不起大家,红孩儿五月份一直在忙工具箱的事,经过短期发力,工具箱已经可以将动画和界面部分编辑并导出到Cocos2d-x中。另外红孩儿手上的项目进入测试期,就把博客给冷落了。不过大家放心,红孩儿一定会继续坚持把Cocos2d-x的教程进行到底!
好了,书归正传,接上一篇文ClippingNodeTest。我们把后面的部分继续讲完。
第八个实例:RawStencilBufferTest
截图:
说明:
循环设置模版缓冲各位的值并进行遮罩处理。
原理:
设置相应的模板缓冲位数据,并通过模板测试比较进行相应区域像素的刷色块与精灵显示。
类代码:
class RawStencilBufferTest : public BaseClippingNodeTest
{
public:
//析构
~RawStencilBufferTest();
//重载基类函数。
virtual std::string title();
virtual std::string subtitle();
virtual void setup();
virtual void draw();
//设置遮罩用的模版缓冲
virtual void setupStencilForClippingOnPlane(GLint plane);
//设置显示图像用的模板缓冲
virtual void setupStencilForDrawingOnPlane(GLint plane);
protected:
//精灵成员
CCSprite* m_pSprite;
};
对应CPP:
//模板位索引
static GLint _stencilBits = -1;
// ALPHA参考值
static const GLfloat _alphaThreshold = 0.05f;
//面板数量
static const int _planeCount = 8;
//面板颜色
static const ccColor4F _planeColor[] = {
{0, 0, 0, 0.65f},
{0.7f, 0, 0, 0.6f},
{0, 0.7f, 0, 0.55f},
{0, 0, 0.7f, 0.5f},
{0.7f, 0.7f, 0, 0.45f},
{0, 0.7f, 0.7f, 0.4f},
{0.7f, 0, 0.7f, 0.35f},
{0.7f, 0.7f, 0.7f, 0.3f},
};
//析构
RawStencilBufferTest::~RawStencilBufferTest()
{
CC_SAFE_RELEASE(m_pSprite);
}
//标题
std::string RawStencilBufferTest::title()
{
return "Raw Stencil Tests";
}
//副标题
std::string RawStencilBufferTest::subtitle()
{
return "1:Default";
}
//初始化
void RawStencilBufferTest::setup()
{
//OPENGL相应的接口函数取得程序运行在当前设备的模版缓冲区位数。
glGetIntegerv(GL_STENCIL_BITS, &_stencilBits);
//如果小于3,提示模版缓冲不满足当前的OPENGL视窗要求
if (_stencilBits < 3) {
CCLOGWARN("Stencil must be enabled for the current CCGLView.");
}
//创建精灵
m_pSprite = CCSprite::create(s_pPathGrossini);
//占用,对其引用计数器加一。
m_pSprite->retain();
//设置锚点横向居中,纵向靠下。就是设置脚部位置为锚点。
m_pSprite->setAnchorPoint( ccp(0.5, 0) );
//放大2.5倍
m_pSprite->setScale( 2.5f );
//设置开启ALPHA混合。为什么要开启ALPHA混合?好问题!放在后面回答。
CCDirector::sharedDirector()->setAlphaBlending(true);
}
//绘制函数
void RawStencilBufferTest::draw()
{
//取得屏幕右上角位置
CCPoint winPoint = ccpFromSize(CCDirector::sharedDirector()->getWinSize());
//根据面板数量取得每个面板的大小。
CCPoint planeSize = ccpMult(winPoint, 1.0 / _planeCount);
//开启模板测试。
glEnable(GL_STENCIL_TEST);
//检测是否有运行BUG
CHECK_GL_ERROR_DEBUG();
//遍历设置每个面板
for (int i = 0; i < _planeCount; i++) {
//计算出每个遮罩区域的右上角位置,这里计算的结果其实就是右边至上而下的平均分位置。
CCPoint stencilPoint = ccpMult(planeSize, _planeCount - i);
stencilPoint.x = winPoint.x;
//计算出精灵的位置。这里计算的结果为精灵在最下方至左向右的平均分位置。
CCPoint spritePoint = ccpMult(planeSize, i);
spritePoint.x += planeSize.x / 2;
spritePoint.y = 0;
m_pSprite->setPosition( spritePoint );
//设置当前面板的遮罩模板缓冲
this->setupStencilForClippingOnPlane(i);
//检测是否有运行BUG
CHECK_GL_ERROR_DEBUG();
//在屏幕上相应遮罩区域绘制色块,写入模板缓冲相应模板值。
ccDrawSolidRect(CCPointZero, stencilPoint, ccc4f(1, 1, 1, 1));
//绘制精灵。
//先保存当前状态下的矩阵。
kmGLPushMatrix();
//设置当前节点的矩阵
this->transform();
//调用精灵函数绘制精灵。
m_pSprite->visit();
//恢复之前状态下的矩阵。
kmGLPopMatrix();
//设置显示图形时的模板缓冲。
this->setupStencilForDrawingOnPlane(i);
//检测是否有运行BUG
CHECK_GL_ERROR_DEBUG();
//在屏幕上全屏绘制色块,因为有模版遮罩,所以只有遮罩区域才显示。
ccDrawSolidRect(CCPointZero, winPoint, _planeColor[i]);
//再绘制精灵一遍。
kmGLPushMatrix();
this->transform();
m_pSprite->visit();
kmGLPopMatrix();
}
//关闭模版测试
glDisable(GL_STENCIL_TEST);
//检测是否有运行BUG
CHECK_GL_ERROR_DEBUG();
}
//设置遮罩用的模版缓冲
void RawStencilBufferTest::setupStencilForClippingOnPlane(GLint plane)
{
//计算相应平面所在的模板位
GLint planeMask = 0x1 << plane;
//设置为模板缓冲掩码
glStencilMask(planeMask);
//先清空当前画面的模板缓冲
glClearStencil(0x0);
glClear(GL_STENCIL_BUFFER_BIT);
glFlush();
//设置永远不能通过测试。
glStencilFunc(GL_NEVER, planeMask, planeMask);
//如果测试未通过时将相应像素位置的模版缓冲位的值设为1。
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
}
//设置显示图像用的模板缓冲
void RawStencilBufferTest::setupStencilForDrawingOnPlane(GLint plane)
{
//计算相应平面所在的模板位
GLint planeMask = 0x1 << plane;
//取得对应相应模版位的测试参考值,因为之前的位在循环做遮罩时都设为1,故测试参考值应为相应位为1加后面所有位都为1。
GLint equalOrLessPlanesMask = planeMask | (planeMask - 1);
//设置只有像素对应模板位的模板指等于参考值通过测试。即只有缓冲位值为1的像素位置才会显示当前要绘制的精灵像素。
glStencilFunc(GL_EQUAL, equalOrLessPlanesMask, equalOrLessPlanesMask);
//如果测试未通过时保持模版缓冲位的值不变。
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
}
这是一个比较难于理解的实例,也许你看的云里雾里,不过放心,红孩儿的教程一定让你深入的理解这里面的玄机。
这个实例复杂之外其实就是多了一个精灵,为了更好的理解模版缓冲,咱们先把精灵干掉。然后来运行一下。
/*
kmGLPushMatrix();
this->transform();
m_pSprite->visit();
kmGLPopMatrix();
*/
this->setupStencilForDrawingOnPlane(i);
CHECK_GL_ERROR_DEBUG();
ccDrawSolidRect(CCPointZero, winPoint, _planeColor[i]);
/*
kmGLPushMatrix();
this->transform();
m_pSprite->visit();
kmGLPopMatrix();
*/
看,这就是通过循环至上而下设置每个遮罩区域后,对屏幕进行刷色块处理的显示效果。
再仔细,再仔细,我们以循环两次时的色块截屏来观察:
这样,我们可能很好的观察到色块是如何刷的。
我们在此基础上打开精灵的显示:
我们可以看到精灵被渲染了两次,我用黄线标示了精灵的实际遮罩大小,为什么会是这样的区域呢?在这里我们要重点说明一下,当使用ALPHABLEND和ALPHATEST的区别:
如果你使用ALPHABLEND,实际它的每个像素仍会写入模版缓冲,即使我们认为它是透明的像素。
而使用ALPHATEST,它被测试为透明镂空的像素在PS阶段完全被干掉了,不会再对背景缓冲起任何作用,包括颜色缓冲,深度缓冲与模版缓冲。
下面,我们开启ALPHATEST:
//CCDirector::sharedDirector()->setAlphaBlending(true);
//开启ALPHATEST
CCGLProgram *alphaTestShader = CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColorAlphaTest);
m_pSprite->setShaderProgram(alphaTestShader);
截图显示:
可以看到,与ALPHABLEND时的不同在于黄框中上方的部分区域是镂空的。好好的记住这一点,我们在做很多效果时会用到。
我们以原来的ALPHABLEND状态再增加一次循环来看一下效果:
可以看到,第三次循染的色块为绿色,精灵我用黄框标示,我们发现在黄框右上角出现一个小区块是没有被涂绿的,这是为什么呢?
在setupStencilForDrawingOnPlane函数中,我们判定可以通过模版测试的条件是:
当前像素的模板缓冲值 = 当前循环次数所在缓冲位的数值 | 之前位都为1。
那右小角的小块呢?它的模板缓冲值是肯定不符合测试条件的。
它的模板缓冲值为0x100,只是精灵写入了一次。而黄框中其它区域的模板缓冲值为多少呢?0x110.因为与上次循环写入的模板缓冲值0x010做了“或”运算。
如果我们稍稍改动一下:
把setupStencilForDrawingOnPlane函数中的这句
GLintequalOrLessPlanesMask = planeMask | (planeMask - 1);
改为
GLintequalOrLessPlanesMask = planeMask;
再运行看看:
右上角就被填充了,因为黄框中所有模板缓冲值为0x100,而测试条件只测第三位,所以与0x110运算是可以通过测试的。
瞧,这么一个小程序,里面也是处处玄机。你是否领悟了真正的引擎开发:的乐趣呢?
后面的RawStencilBufferTest2~ RawStencilBufferTest6是一堆由此实例派生的各种测试实例,分别代表的意义为:
RawStencilBufferTest2:关闭写入深度缓冲。
RawStencilBufferTest3:关闭写入深度缓冲,并关闭深度测试。这样会按照渲染的先后顺序来做Z排序,而非深度缓冲。
RawStencilBufferTest4: RawStencilBufferTest2基础上使用ALPHATEST而非混合。
RawStencilBufferTest5: RawStencilBufferTest3基础上使用ALPHATEST而非混合。
RawStencilBufferTest6:这个是取像素的模版缓冲。简单说一下:
//取得幅标题。
std::string RawStencilBufferTest6::subtitle()
{
return "6:ManualClear,AlphaTest:ENABLE";
}
//初始化
void RawStencilBufferTest6::setup()
{
//判断所处平台支持
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
//取得窗口大小
CCPoint winPoint = ccpFromSize(CCDirector::sharedDirector()->getWinSize());
//清空模板缓冲值为0
unsigned char bits = 0;
glStencilMask(~0);
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
glFlush();
//取得0,0点的像素的模板缓冲值
glReadPixels(0, 0, 1, 1, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, &bits);
//创建文字标签来显示模板缓冲值。
CCLabelTTF *clearToZeroLabel = CCLabelTTF::create(CCString::createWithFormat("00=%02x", bits)->getCString(), "Arial", 20);
clearToZeroLabel->setPosition( ccp((winPoint.x / 3) * 1, winPoint.y - 10) );
this->addChild(clearToZeroLabel);
//清空模板缓冲值为0xAA
glStencilMask(0x0F);
glClearStencil(0xAA);
glClear(GL_STENCIL_BUFFER_BIT);
glFlush();
//取得0,0点的像素的模板缓冲值
glReadPixels(0, 0, 1, 1, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, &bits);
//创建文字标签来显示模板缓冲值。
CCLabelTTF *clearToMaskLabel = CCLabelTTF::create(CCString::createWithFormat("0a=%02x", bits)->getCString(), "Arial", 20);
clearToMaskLabel->setPosition( ccp((winPoint.x / 3) * 2, winPoint.y - 10) );
this->addChild(clearToMaskLabel);
#endif
//设置遮罩掩码为0xfffffffff
glStencilMask(~0);
//调用基类的相应函数。
RawStencilBufferTest::setup();
}
//设置遮罩用的模版缓冲
void RawStencilBufferTest6::setupStencilForClippingOnPlane(GLint plane)
{
//取得相应循环对应的位
GLint planeMask = 0x1 << plane;
//设置模版缓冲的掩码值。
glStencilMask(planeMask);
//设置永远不通过测试
glStencilFunc(GL_NEVER, 0, planeMask);
//设置如果不通过测试,就设置相应位的值为1.
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
//全屏绘制白色,写入模板缓冲值。
ccDrawSolidRect(CCPointZero, ccpFromSize(CCDirector::sharedDirector()->getWinSize()), ccc4f(1, 1, 1, 1));
//设置永远不通过测试
glStencilFunc(GL_NEVER, planeMask, planeMask);
//设置如果不通过测试,就设置相应位的值为1.
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
//关闭深度测试。
glDisable(GL_DEPTH_TEST);
//关闭写入深度。
glDepthMask(GL_FALSE);
//开始ALPHA测试。
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, _alphaThreshold);
#else
CCGLProgram *program = CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColorAlphaTest);
GLint alphaValueLocation = glGetUniformLocation(program->getProgram(), kCCUniformAlphaTestValue);
program->setUniformLocationWith1f(alphaValueLocation, _alphaThreshold);
m_pSprite->setShaderProgram(program);
#endif
//立即刷新缓冲。
glFlush();
}
//设置显示图像用的模板缓冲
void RawStencilBufferTest6::setupStencilForDrawingOnPlane(GLint plane)
{
//关闭ALPHATEST
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
glDisable(GL_ALPHA_TEST);
#endif
//打开写入深度缓冲。
glDepthMask(GL_TRUE);
//打开深度测试。
glEnable(GL_DEPTH_TEST);
//调用基类的相应函数。
RawStencilBufferTest::setupStencilForDrawingOnPlane(plane);
//立即刷新缓冲。
glFlush();
}
运行截图:
讲了这么多,那倒底用遮罩能做什么效果呢?为了更好的理解,我用“红孩儿工具箱”来展示遮罩的功能:
一.红孩儿工具箱之遮罩动画1-【遮罩显示】
http://v.qq.com/boke/page/t/h/v/t0407iwbmhv.html
这个演示是使用模板缓冲来实现图片相应区域的显示。
二.红孩儿工具箱之遮罩动画2-【遮罩隐藏】
http://v.qq.com/boke/page/y/c/m/y0407t6szcm.html
这个演示是使用模板缓冲来实现图片相应区域的隐藏。
我只使用了简单的三张图,就实现了需要大量序列帧才能实现的效果,大大的节省了游戏对内存的需求。所以,这种技法很有用。
衷心的希望大家能有所收获,把游戏做的更漂亮。下节课再见!