热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(译)如何使用cocos2d2.0来给一个Sprite添加遮罩

免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢

  免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!

原文链接地址:http://www.raywenderlich.com/4428/how-to-mask-a-sprite-with-cocos2d-2-0

教程截图:

  在上一篇教程中,我们向大家展示了如何使用cocos2d 1.0来给精灵增加mask。

  那种方法很不错,但是,也有一些缺点----它会使纹理占用内存增加,同时在渲染的时候还有性能问题。

  但是,通过使用Cocos2D 2.0和OpenGL ES 2.0,我们可以用shader来更高效地实现精灵的mask。

  这篇教程是我们第一次尝试使用cocos2d 2.0分支。你将从这里学习到,如何下载cocos2d 2.0分支,如何为一个CCNode写自己的shader程序。

  为了更好地理解本教程里的内容,你需要有一些基本的OpenGL ES 2.0的知识。所以,如果你对OpenGL ES 2.0还很陌生的话,你可以先看看本博客翻译的其它OpenGL ES 2.0教程。

  话不多说,让我们使用shader来制作mask吧。

 

Introducing Cocos2D 2.0 Branch

  Cocos2D 2.0是一個全新的版本,它使用的是OpenGL ES 2.0,而之前的版本使用的是OpenGL ES 1.0. 这意味着,那些比较老的设备,如果不支持OpenGL ES 2.0的话,那么就不能再跑使用Cocos2D 2.0制作的程序了。但是,目前,大部分设备都是支持opengles 2.0的,因此,转向opengles 2.0是大势所趋。

  因为,即使一些老的设备不支持opengles 2.0,但是,玩一玩opengles 2.0的shader程序还是很有意思的!而且,你会看到,使用shader效率会更高。

  Cocos2d 2.0分支还处在开发阶段,但是,现在,我们可以下载抢鲜版来玩一玩。

  获得cocos2d 2.0的唯一方式,就是通过git仓库,因此,如果你还没安装git的话,那么可以从这里下载。(译者:关于git,它是和svn同类型的源码管理工具,但是,比svn更好用,更强大,支持分布式源码管理。之前博客园的首页有人发表过文章,现在,如果你还没用上git的,你都不好意思说自己是程序员。)

  然后,打开Applications\Utilities目录,打开Terminal程序并打开。然后在终端里面输入下面的git命令:

git clone https://github.com/cocos2d/cocos2d-iphone.git

  一旦你完成下载,然后就可以跳转到cocos2d-iphone的目录了,然后可以把cocos2d 2.0的会支checkout出来。具体命令如下:(这个下载过程刚开始有点长,因为它要把远程的代码仓库下载到本地,以后cocos2d有什么更新,你只需要git pull 就可以了。比你每次下载新的版本要快得多。如果大家对git还不熟悉的话,可以google一下,progit,去这个网站学习一下吧,绝对值得!)  

cd cocos2d-iphone
git checkout gles20

  接下来,要安装cocos2d 2.0的模板。这样的话,会把之前你安装的cocos2d 1.0的模板覆盖掉,那是没关系的---你只需要再choutout回原来的1.0版本,然后再运行一次install-template.sh就可以了。你也可以保存两个cocos2d版本,先把cocos2d 2.0的版本复制到另外的地方,然后checkout到1.0。  这样你想安装1.0就安装1.0,想安装2.0就安装2.0)  

./install-templates.sh -f -u

  然后回到xcode,选择File\New\New Project,再选择 iOS\cocos2d\cocos2d,点Next。把工程命名为MaskedCal2,再点Next,选择一个文件夹来保存工程,然后点Create。

    如果你现在编译并运行,你会看到一个HelloWorld!(译者:Ray写本文的时候模板有点问题,运行之后是黑屏,不过现在的版本已经解决这个问题了。Ray给出的解决方案是把下载的cocos2d 目录下的shaders全部copy进工程,不过,对于我们来说,现在不必要了。不过我还是在这里说明一下。如果没有出现“Hello World”,那么就把shader copy进来吧)。


创建一个简单的cocos2d 2.0工程

  首先,和之前一样,让我们先在屏幕上面显示一张日历图片。

  和之前一样,你把下载好的资源文件拖到Resource目录中,然后确保“Copy items into destination group’s folder (if needed)”被选中,而且 “Create groups for any added folders”也要被选中,然后点击Finish。

  然后打开AppDelegate.m,并作如下修改:

// Add to top of file
#import "SimpleAudioEngine.h"

// At end of applicationDidFinishLaunching, replace last line with the following 2 lines:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"TeaRoots.mp3" loop:YES];
[[CCDirector sharedDirector] runWithScene: [HelloWorldLayer sceneWithLastCalendar:
0]];

  这里和上一篇教程中一样。  

      然后打开RootViewController.m,找到shouldAutorotateToInterfaceOrientation方法,然后返回下面的语句:

return ( UIInterfaceOrientationIsLandscape( interfaceOrientation ) );

    这个设置是使设备仅支持横版(landscape)模式。

    接下来,打开HelloWorldLayer.h,然后作如下修改:  

// Add new instance variable
int calendarNum;

// Replace the +(CCScene*) scene declaration at the bottom with the following:
+ (CCScene *) sceneWithLastCalendar:(int)lastCalendar;
- (id)initWithLastCalendar:(int)lastCalendar;

    这里也和上一篇教程中的一样。

    最后,我们在 HelloWorldLayer.m里面作如下修改:

// Replace +(CCScene *) scene with the following
+(CCScene *) sceneWithLastCalendar:(int)lastCalendar // new
{
CCScene
*scene = [CCScene node];
HelloWorldLayer
*layer = [[[HelloWorldLayer alloc]
initWithLastCalendar:lastCalendar] autorelease];
// new
[scene addChild: layer];
return scene;
}

// Replace init with the following
-(id) initWithLastCalendar:(int)lastCalendar
{
if( (self=[super init])) {

CGSize winSize
= [CCDirector sharedDirector].winSize;

do {
calendarNum
= arc4random() %3+1;
}
while (calendarNum == lastCalendar);

NSString
* spriteName = [NSString
stringWithFormat:
@"Calendar%d.png", calendarNum];

// BEGINTEMP
CCSprite * cal = [CCSprite spriteWithFile:spriteName];
cal.position
= ccp(winSize.width/2, winSize.height/2);
[self addChild:cal];
// ENDTEMP

self.isTouchEnabled
= YES;
}
return self;
}

// Add new methods
- (void)registerWithTouchDispatcher {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self
priority:
0 swallowsTouches:YES];
}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CCScene
*scene = [HelloWorldLayer sceneWithLastCalendar:calendarNum];
[[CCDirector sharedDirector] replaceScene:
[CCTransitionJumpZoom transitionWithDuration:
1.0 scene:scene]];
return TRUE;
}

    还是和上一篇教程一样,我们等下要把(begintemp和endtemp之间的代码替换掉)。因此,你可以看到,其实cocos2d 2.0的api和1.0的api非常类似--当然,接下来你会看到2.0的一些非常酷的特性。  

    编译并运行,然后你可以点击模拟器,那么每次都会显示一张不同的日历,这和之前使用cocos2d 1.0没有什么不同。但是,接下来,我们将使用OpenGL ES 2.0和cocos2d 2.0来制作mask,准备好了吗?

Shaders and Cocos2D 2.0

    cocos2d 2.0有许多内置的shader程序。

    让我们先看一看,一个普通的CCSprite渲染的shader程序是怎么样的吧。他们其实非常简单,打开Shaders\PositionTextureColor.vsh,如下图所示:

 
 

attribute vec4 a_position;

 
 

attribute vec2 a_texCoord;

 
 

attributevec4 a_color;

 
 

uniformmat4 u_MVPMatrix;

 
 

varying vec4 v_fragmentColor;

 
 

varying vec2 v_texCoord;

 
 

void main()

 
 

{

 
 

    gl_Position = u_MVPMatrix * a_position;

 
 

    v_fragmentColor = a_color;

 
 

    v_texCoord = a_texCoord;

 
 

}

 

    这里把传进来的顶点坐标乘以投影矩阵和模型视图矩阵(即u_MVPMatrix矩阵,它是projection matrix 和model/view matrix的乘积),然后把输入的片断颜色和纹理坐标传递给相应的输出参数。

    接下来,让我们看看Shaders\PositionTextureColor.fsh: 


#ifdef GL_ES
precision lowp
float;
#endif

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform sampler2D u_texture;

void main()
{
gl_FragColor
= v_fragmentColor * texture2D(u_texture, v_texCoord);
}

    这里把输出颜色等于纹理颜色乘以顶点颜色。  

    cocos2d 2.0里面,你可以为每个node编写定制的shader程序来渲染它。接下来,让我们为sprite类写一段mask shader吧。

    回到Xcode,选择File\New\New file,再选择 iOS\Other\Empty,点击Next。然后命名为Mask.fsh并点击Save。

    然后,点工程,选择MaskedCal2 target(如下图红色圈圈所示),然后选择 Build Phase标签,在”Compile Sources“里面找到Mask.fsh,并把它拖到”Copy Bundle Resources“里面。这样会把shader程序拷贝到你的应用程序bundle里面,而不会直接参与源文件的编译。

  (顺便提一下,如果谁有更简单的方法,请留言告诉我)

    然后把Mask.fsh里面的内容替换成下面的形式:

#ifdef GL_ES
precision lowp
float;
#endif

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform sampler2D u_mask;

void main()
{
vec4 texColor
= texture2D(u_texture, v_texCoord);
vec4 maskColor
= texture2D(u_mask, v_texCoord);
vec4 finalColor
= vec4(texColor.r, texColor.g, texColor.b, maskColor.a * texColor.a);
gl_FragColor
= v_fragmentColor * finalColor;
}

 这里,我们为mask纹理建立了一个新的uniform,然后把日历和mask纹理里的像素读取到uniform变量中来。

 然后,我们通过日历的RGB值来构造最终的颜色值,但是把alpha通道乘以mask的alpha通道。这样,就可以制作出遮罩效果了来啦。

   好了,现在我们有shader程序啦,让我们来使用它们吧!

在cocos2d 2.0里面使用定制的shader

    为了使用定制的shader,你需要创建一个CCNode的子类,设置它的shaderProgram为你的定制的shader,而且还需要覆盖其draw方法来给shader程序传递合适的参数。

    我们接下来将创建一个CCSprite的子类(因为CCSprite也是继承的CCNode),把这个类命名为MaskedSprite。

   选择File\New\New File,然后选择iOS\Cocoa Touch\Objective-C class,再点击Next,然后输入CCSprite为subclass,接着,点Next,把新的文件命名为MaskedSprite.m,最后点击Save。

    然后把MaskedSprite.h替换成下面的内容:

#import "cocos2d.h"

@interface MaskedSprite : CCSprite {
CCTexture2D
* _maskTexture;
GLuint _textureLocation;
GLuint _maskLocation;
}

@end

    这里我们定义了一个实例变量来追踪mask纹理,还有两个实例变量用来追踪纹理的uniform位置,和mask 的uniform位置。  

    接下,我们回到MaskedSprite.m,然后把里面的内容替换如下:

#import "MaskedSprite.h"



@implementation MaskedSprite

- (id)initWithFile:(NSString *)file
{
self
= [super initWithFile:file];
if (self) {

// 1
_maskTexture = [[[CCTextureCache sharedTextureCache] addImage:@"CalendarMask.png"] retain];

// 2
self.shaderProgram =
[[[GLProgram alloc]
initWithVertexShaderFilename:
@"PositionTextureColor.vsh"
fragmentShaderFilename:
@"Mask.fsh"] autorelease];

CHECK_GL_ERROR_DEBUG();

// 3
[shaderProgram_ addAttribute:kCCAttributeNamePosition index:kCCVertexAttrib_Position];
[shaderProgram_ addAttribute:kCCAttributeNameColor index:kCCVertexAttrib_Color];
[shaderProgram_ addAttribute:kCCAttributeNameTexCoord index:kCCVertexAttrib_TexCoords];

CHECK_GL_ERROR_DEBUG();

// 4
[shaderProgram_ link];

CHECK_GL_ERROR_DEBUG();

// 5
[shaderProgram_ updateUniforms];

CHECK_GL_ERROR_DEBUG();

// 6
_textureLocation = glGetUniformLocation( shaderProgram_->program_, "u_texture");
_maskLocation
= glGetUniformLocation( shaderProgram_->program_, "u_mask");

CHECK_GL_ERROR_DEBUG();

}

return self;
}
@end

  这里有许多内容有待讨论,让我们一步步来看吧。

  1. 获得日历mask纹理的引用.
  2. 给CCNode内置的shaderProgram属性赋值,这样的话,你就可以使用自己的顶点和片断shader了。我们使用内置的PositionTextureColor顶点shader(因为并不需要改oyc什么),然后使用新的Mask.fsh来作为片断shader。注意,这里的GLProgram类和Jeff LaMarche的blog是一样的。
  3. 在链接之前,为每一个属性设置index。在OpenGL ES 2.0里面,你可以像本文中的一样,手动指定每个属性的index。也可以像之前我们在《Opengl es 2.0 iphone开发指引》中所使用的方法。
  4. 调用shaderProgram来链接到编译器和shaders。
  5. 调用shaderProgram的updateUniforms 方法,这是cocos2d 2.0特有的一个非常重要的方法。回想一下,还记得shaders里面的那些projection和model/view uiniform 吗?这个方法的调用,可以把那些uiniform都存到一个字典里面,因此,cocos2d就可以自动地基于当前的节点来设置postion和变换(transform)。
  6. 获得纹理和mask的uniform,我们后面将用到。

    接下来,我们将不得不覆盖些类的draw方法,用以给shader传递合适的参数。所以,在init方法后面加上下面的代码:

-(void) draw {    

// 1
ccGLBlendFunc( blendFunc_.src, blendFunc_.dst );
ccGLUseProgram( shaderProgram_
->program_ );

// ccGLUniformProjectionMatrix( shaderProgram_ );
// ccGLUniformModelViewMatrix( shaderProgram_ );
ccGLUniformModelViewProjectionMatrix(shaderProgram_);

// 2
glActiveTexture(GL_TEXTURE0);
glBindTexture( GL_TEXTURE_2D, [texture_ name] );
glUniform1i(_textureLocation,
0);

glActiveTexture(GL_TEXTURE1);
glBindTexture( GL_TEXTURE_2D, [_maskTexture name] );
glUniform1i(_maskLocation,
1);

// 3
#define kQuadSize sizeof(quad_.bl)
long offset = (long)&quad_;

// vertex
NSInteger diff = offsetof( ccV3F_C4B_T2F, vertices);
glVertexAttribPointer(kCCVertexAttrib_Position,
3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));

// texCoods
diff = offsetof( ccV3F_C4B_T2F, texCoords);
glVertexAttribPointer(kCCVertexAttrib_TexCoords,
2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));

// color
diff = offsetof( ccV3F_C4B_T2F, colors);
glVertexAttribPointer(kCCVertexAttrib_Color,
4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));

// 4
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glActiveTexture(GL_TEXTURE0);
}

  让我们一步步看这些代码是如何工作的:

  1. 这是一段万金油代码。它设置此node的blend函数,使用shader 程序,并且设置projection和model/view uniform。
  2. 这里把日历纹理绑定到1号纹理单元,把mask纹理绑定到2号纹理单元。在《Opengl Es 2.0 iphone开发指引:第二部分,纹理贴图》有讨论。
  3. CCSprite已经包含代码来设置顶点,颜色和映射纹理坐标了--它把这些东西存储在一个特殊的结构里面,叫做quad。这里使用quad计算偏移,然后设置相应的顶点,颜色和纹理坐标。
  4. 最后,我们通过调用GL_TRIANGLE_STRIP 来渲染精灵,并且重新激活0号texture单元。(否则的话,1号纹理单元就会处于激活状态,而cocos2d假设的是0号纹理单元处于激活状态)。

    就快完成啦!回到HelloWorldLayer.m,然后作如下修改:

// Add to top of file
#import "MaskedSprite.h"

// Replace code between BEGINTEMP and ENDTEMP with the following
MaskedSprite * maskedCal = [MaskedSprite spriteWithFile:spriteName];
maskedCal.position
= ccp(winSize.width/2, winSize.height/2);
[self addChild:maskedCal];

    就这么多!编译并运行,现在你可以看到使用cocos2d 2.0和opengles 2.0制作的mask效果啦!  

    这里最牛叉的地方就再于,我们并没有创建任何额外的纹理,而且shader的效率也非常高!

何去何从?

    这里有本教程的完整源代码。(大家如果下载源代码,会发现代码和我这里给出来的不一样。为什么呢?因为cocos2d 2.0在不断地发展之中,Ray写作这篇教程时的代码,用于新的cocos2d 2.0已经不适合了。但是,如果你下载Ray提供的源代码还是能运行的。)

    至此,本系列教程就全部结束了。你可以使用cocos2d 1.0和2.0来给sprite增加mask,it's up to you!

   如果你想学习更多有关cocos2d 2.0的知识,你可以玩一玩里面的shaderTest。

   如果你想学习更多关于shader的内容,推荐看Philip Rideout’s iPhone 3D Programming--Ray也是看这本书学习的。

  如果大家实践本教程的过程中遇到任何问题,请留言!


 

著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

 


推荐阅读
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • CentOS7.8下编译muduo库找不到Boost库报错的解决方法
    本文介绍了在CentOS7.8下编译muduo库时出现找不到Boost库报错的问题,并提供了解决方法。文章详细介绍了从Github上下载muduo和muduo-tutorial源代码的步骤,并指导如何编译muduo库。最后,作者提供了陈硕老师的Github链接和muduo库的简介。 ... [详细]
  • imnewtotheswiftandxcodeworld,soimhavingaproblemtryingtointegrateapackagetomypro ... [详细]
  • 从零开始系列之vue全家桶(2)安装调试插件vue Devtools
    小白安装前提是会用git,会从github上找东西。第一步:我们可以先从github上找到vue-devtools的项目,下载到本地。下载vue-devtools链接。克隆方法:git ... [详细]
  • 安卓端开源移动浏览器开源项目
    进入android开发以来,webview用的比较多,后来又看了一些基于webview的浏览器代码,慢慢的将积累的东西做成了一个安卓移动浏 ... [详细]
  • Git GitHub多人协作
    在学校做一个小项目需要多人协作,就用到了gitHub,百度了一下多数写得乱七八糟或者支离破碎,于是总结了一下自己的步骤如下,第一次使用GitHUb,哪里不对望大神指出一.前期准备: ... [详细]
  • 1找现有的镜像dockerhub上搜索到一个,其在github上公布了镜像构建的dockerfile,并且支持http和svn两种协议,就这个吧。https:github.come ... [详细]
  • 在应用开发中少不了界面元素的布局、适配、自适应。之前苹果已经为我们提供了 AutoLayout 和 SizeClasses,方便我们实现页面的自适应弹性布局。但对于一些复杂的交互界 ... [详细]
  • 删除iPad或iPhone目录privatevarkeychainskeychain-2.db文件,重启iPad或iPhone。1.如何卸载机内自带软件? ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 这篇文章给大家介绍怎么从源码启动和编译IoTSharp ,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。IoTSharp项目是 ... [详细]
  • UMTS基础知识汇总
    协议框架23G接口UMTS实体EntityNameDescriptionAuCAuthenticationCenterCBCCellBroadcastCenterC-RNCCon ... [详细]
author-avatar
小鱼2502907687
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有