首页 > 代码库 > cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译)

cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译)

cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译)

 技术分享(2012-12-24 13:22:17)
技术分享转载
标签: 

it

 

cocos2d

 

opengl

 

着色器

 

渲染


翻译:弹涂鱼

PS:欢迎加入开发群:285275050

本文翻译自:http://www.raywenderlich.com/10862/how-to-create-cool-effects-with-custom-shaders-in-opengl-es-2-0-and-cocos2d-2-x#

第一次翻译这么长的教程,翻译中间部分时有些不耐烦,可能大家在看时不会太明了。后面及时调整了心态。以后尽量每周翻译一篇外文资料。

自从3d游戏的引入,着色器很可能是计算机图形学的一大进步。它使编程人员可以创建全新的特效,并且能控制屏幕上所显示的内容。如果你还没有用过着色器,读完此篇文章后你将会用到。

Cocos2D是当下最好ios游戏开发框架,值得庆幸的是cocos2D现在已经支持openg-es 2.0 和着色器。本教程中,在Cocos2D的帮助下你将学会如何去创建和使用着色器。你将学习以下内容:

l   GLSL基础(opengl shader language

l   Cocos2D里如何创建和使用着色器

l   三个着色器实例

1.     在游戏中通过使用斜坡纹理(ramp textures)来操作颜色

2.     创建浮雕特效

3.     通过几行代码创建波浪起伏的青草

 

这是一篇高级教程,所以需要一些先决条件。为了能知道本篇在讲什么,你需要以下基础知识:

l   ocios. other tutorials

l   Cocos2D  Cocos2D tutorials

l   Opengl es 2.0  OpenGL ES tutorials

 

最后,我将使用最新的xcode,请确保更新到最新版。

 

前言

在探索和使用着色器前,你需要cocos2d 2.x和确保xcode集成了cocos2d模板。然后创建一个cocos2d项目,步骤如下

1.     下载最新cocos2d 2.x

2.     解压到你喜欢的文件夹

3.     通过控制台导航到cocos2d文件夹,运行以下命令来安装模板./install-templates.sh -u -f

4.     运行xcode执行:File\New\Project…

5.     选择cocos2d模板点击下一步

6.     命名项目为CocosShaderEffects,选择iphone设备作为目标设备,点击下一步

7.     选择工程创建的路径,点击创建

 

接下在你的项目中启用arc,尽管cocos2d没有使用arc,你可以在余下的项目中去启用,这可以节约代码同时能减少内存泄露。

 

在菜单中选择Edit\Refactor\Convert to Objective-C ARC…。在代开的对话框中选中select main.m, AppDelegate.m and HelloWorldLayer.m (如果未找到点击CocosShaderEffects.app旁边的小三角展开),在接下来的一系列对话框总点击checkNextSave。在转换前你会被建议自动启用快照。本工程中你可以不启用,但是启用快照,在紧要关头可能会救了你。比如保留快照可以使你因为修改过多而而导致错误时进行回复。

 

很简单吧?现在你可以在使用arc的同时感受cocos2d的力量。

 

技术分享

 

编译运行,你会看到:

技术分享

 

由于本教程中你不会用到高清图片,如果你的程序运行在高清显示屏上,你可能想禁用高清图片加载。找到AppDelegate.m把下面代码注释掉:

if( ! [director_ enableRetinaDisplay:YES] )

现在,准备工作完成,可以你的Cocos2D和着色器旅程了。

 

什么是着色器?

着色器是一个简单的类C程序,用于执行渲染特效。举例来说,正如名字所暗示的,着色器最基本的功能可能是为一个对象添加不同的色彩(或者是对象的一部分).

着色器程序在GPU(图形处理器)执行。对于移动设备,共有两类着色器: 

1.     顶点着色器:顶点着色器作用在每个正在被渲染的顶点上,所以在渲染一个简单的精灵时,着色器会执行四次用于计算精灵四个顶点的颜色和其他属性。 

2.     片元着色器:这类着色器作用在屏幕显示的每一个像素上,这意味着当在iphone上渲染一个全屏的方形时,片源着色器会被执行320*480次。

上述两类着色器不能单独使用:它们必须同时出现。两类着色器组合在一起成为一个程序。它们这样来运行:

1.     顶点着色器首先定义每个即将在屏幕上显示顶点的属性

2.     然后,每个顶点依次被打散(break down in turn)成一系列像素(我理解为像素化或栅格话),这些像素通过片元着色器去执行

3.     最后的像素被渲染到屏幕。

 

在引入着色器以前,当你渲染特效时,你只能去调用已知的固定功能管道线。固定功能管道线允许你渲染那些差不多是硬件编码的特效。你可以去更改参数(修改颜色,位置等),这些也是你唯一能做的。

 

固定功能管道线的种种限制促进了着色器的诞生,着色器可以使你通过编程的方式去创建新的渲染特效。通常,着色器可以实现你能想到的所有特效:着色器可以使你去编写屏幕每次绘制时调用的程序。

 

Opengl-ES 2 着色器使用OpenGL Shading Language, GLSL来编写的,它的语法很像oc,如果你有编程经验,尤其是你读过本网站的一些文章时,你不应该会遇到问题。不用担心去阅读说明文档,我会带领你从基础开始的。

 

(老外写的太细致了)

 

如何构建在Cocos2D中工作的着色器?

 

Cocos2d 2.x 使用Opengl-es 2.0 进行图形渲染,这样,即使是最简单的渲染你都需要着色器。所以每一个CCNode节点都有一个shaderProgram实例变量,这个实例变量保留了shader program的指针,当节点绘制时会被调用。

 

Cocos2D 也提供了自己的CCShaderCache允许你调用默认的着色器程序,或者你也可以缓存自己的着色器程序,这样就能避免多次重复加载问题,你可以在libs\cocos2D\CCGLProgram.h找到访问预定义着色器的常量定义关键字。你可以在libs\cocos2d\ccShader_xxx.h.找到cocos2d使用的着色器。

 

下面这段代码是一个示例着色器程序:

技术分享

 

在开始讲解这段代码之前,我们先来对shader有个大致了解,和它实现的目标。

每个着色器都需要输入值,并产生输出值。对于上面的着色器。

 

l   需要每个顶点的position作为输入值,对于精灵来说就是他的四个角所对应的顶点。它也需要纹理的坐标(coordinate)作为输入值用于显示在每个顶点上(会映射纹理到四个顶点),另外还需要一个几何变换(transform)用于应用position/scale/rotate到精灵上。Cocos2d会在着色器执行前传入这些值。

l   输出决定着顶点在屏幕上的最终显示坐标(输入的位置经过几何变换以后),和顶点的最终纹理坐标,片元着色器会利用这些输出变量,这些我在后面介绍。

 

现在你已经有了大致了解,接下来进行更详细的分步介绍:

1.     定义输入顶点的数据结构,关键字attribute告诉编译器这是个包含了每个顶点数据结构的输入变量。类型vec2vec4说明数据是由浮点数组成的向量。Vec可以有2-4个组成部分,这里定义了两个,一个对应位置,另一个对应纹理坐标。

2.     当你需要从你的源码中传递扩展变量到着色器时,需要用关键字uniform定义。Mat4定义了有4*4float组成的矩阵。如果你对先行代数不了解,记住矩阵是数学上表示位置,旋转,缩放的方式。

3.     顶点着色器需要传递一些值到片源着色器,为了标识从顶点着色器传到片源着色器变量,使用varying作为关键字。这些变量可以通过插值去改变,很酷吧。这是一个有趣的描述方式,如果你给一个变换的坐标顶点A设置为0,变换的坐标顶点B设置为1.0,当你到达片元着色器渲染一个像素时,opengl会自动将变量值设置为0.5。每一个片元都会为组成这个片元顶点进行插值计算。在这里可以看到精度标识符mediump的详细介绍。还有两类标识符:highplowp,它们定义计算数据存储空间质量(They define the importance of the quality of calculations/data storage)。Highp表示更精确的数据类型将会使用,当然计算速度相对较慢。

4.     每一个着色器需要有一个main方法,就像oc

5.     顶点着色器需要变换顶点坐标来填充内建的变量gl_Positon,这个着色器被由cocos2d自动传入的ModelViewProjection matrix乘以了传入的Position

6.     通过给变量赋值,直接传递坐标位置到片元着色器

 

下面是上述顶点着色器的片元着色器:

技术分享

 

记住当执行到片元着色器时,opengl会为组成精灵的每一个像素时调用一次这个程序。这个程序的目的是为了表示出如何去为每一个像素着色。对这个默认着色器的回答很简单,仅仅是挑选出纹理中正确的点去匹配对应像素。

 

下面是分步介绍:

1.     强制设置片元着色器顶部的基础精度为浮点型,这里设置为中等精度。

2.     所有顶点着色器的输出变量,在这里设置为输入变量。顶点着色器输出了纹理坐标坐标,所以在这里纹理坐标变为输入。

3.     片元着色器也可以有uniform变量,这里定义了一个uniform型的sampler2D变量,代表正常纹理。

4.     gl_FragColor是内建变量,需要为它填充最后的像素颜色。这里从从uniform类型的纹理和从顶点着色器传过来的纹理坐标中获取像素。

 

使你明白如何和把他们组合在一起前还有许多步要走,这些纹理正好在CCGrid.m中用到了,好的,打开文件看看如何使用。

 

首先,在init方法中shadercache中加载

技术分享

如果你感到好奇,妮可以在CCShaderCache中查看shaders是如何被编译和存储的。

接下来在blit方法中,向着色器材传入变量并运行。 

技术分享

你可能想知道纹理是在那里传入的呢?这里有个捷径如果你不设置变量,他的默认值是0,另外第一个纹理的单元也是0,所以在afterDraw中绑定纹理,并且不用再次传入。

技术分享

现在你已经学习了一个在cocos2d中使用纹理的实例,你可能想去深入挖掘CCSprite是如何渲染它自己的。

当你去做那件事时,充分分析。

是时候创建属于你自己的纹理了。

 

如何创建自己的着色器

大多数2D游戏有精灵组成,每个精灵有四个顶点。仅仅有这些还是不够,大多数2D特效是由片元着色器创建的。你即将使用cocos2d默认的顶点着色器来添加一个新的自定义片元着色器。

 

这里的目的是创建一个简单效果,在这里你将使用第二纹理去修改原始纹理颜色。你将使用“ramp”纹理(“ramp”texture)来操纵原始纹理。这是一个可以用与创建皮肤或卡通着色的效果。

 

下面是我们将要用到的纹理:

技术分享

我们将处理颜色的三部分(rgb),它们被表述成0-1的百分比,如果你在使用0-255的范围表示,不要忘记除以255.

 

注意图片是如何从白(RGB 0.0, 0.0, 0.0) 变到黑的(RGB 1.0, 1.0, 1.0)。对于原始图片中的rgb值,我们去寻找这个颜色遮罩的相关入口,有效的反转原始图片的颜色值。比如:

(下面的不翻译了,相信你明白他在说什么)

技术分享

所以,由于上图只有64像素宽,相对于原始图像它有较小的颜色变化范围,这导致了带状果。

 

好吧,让我们来试一下。你可能刚刚运行程序时就已经注意到,现在你有两个菜单选项--Achievements 和Leaderboards。由于不需要Achievements 和 Leaderboards来处理纹理,所以你需要修改一下程序菜单,使它导航到着色器测试界面。

 

创建一个继承自CCLayer类,命名为CSEColorRamp

打开HelloWorldLayer.m引入CSEColorRamp的头文件

 

#import "CSEColorRamp.h"

 

替换HelloWorldLayerinit方法为:

技术分享

 

编译运行,你将看到

技术分享

 

点击 Color Ramp将导航到一个黑色界面,我们来让它变得有趣。

打开CSEColorRamp替换@implementation 只有这一行为

技术分享

我们只是添加了一些私有变量。

1.  你想改变颜色的精灵

2.  为了传递数据给着色器中的uniform变量,我们需要创建一个变量来跟踪unifromd的位置

3.  你的ramp纹理

 

替换init方法为:

技术分享

 

你都做了些什么?

1.     加载精灵,使他可以铺满屏幕

2.     强制精灵使用你自己的着色器程序。它将使用自己默认的顶点着色器ccPositionTextureA8Color_vert和自定义的片元着色器CSEColorRamp.fsh,然后标示你的顶点将包含位置和纹理坐标属性。然后链接着色器并cocos2Duiniforms

3.     着色器连接完成后,你可以询问openglu_colorRampTexture标识的第二纹理的硬件位置。然后初始化这个位置为1.(我们会在slot 1 存储纹理,slot 0 是精灵的纹理)

4.  加载你的渐变纹理(ramp texture)并禁用线性插值,这里我们保留原始值。

5.  绑定纹理,设置第二纹理为你的ramp texture(注意在实际项目中,在精灵的每一次绘制你都要设置一下这些值,因为其他渲染可能会改变这些值),这样就绑定你的纹理到u_colorRampTexture上了。

 

现在需要片元着色器来使这个层来工作。

创建一个新的空文件iOS\Other\Empty template,命名为CSEColorRamp.fsh,选择文件位置,将CocosShaderEffectstargets列表中移除点击create.移除的原因是它不是源码文件。而是资源(Xcode by default would add it to the Compile Sources phase).

 

现在你需要将新添加的片元着色器添加到Copy Bundle Resources build phase(省略详细)

 

技术分享

 

修改CSEColorRamp.fsh如下:

 

技术分享

注意:为增加上述代码可读性,在菜单中选择Editor/Syntax Coloring/GLSL,将会开始着色器语法颜色显示。

 

上述代码做了些什么?

1.  此着色器需要从顶点着色器传来的纹理坐标,和从程序中传来的两个纹理

2.  获取纹理原始值

3.  使用真实的通道值作为地址去获取ramp texture修改后的颜色。

4.  创建基于简便的片元颜色,并使他不透明。

运行程序你会看到:

技术分享

 

Cocos2d只是改变了他的皮肤颜色。

 

你可以尝试改变绑定的颜色图片来达到不同效果。减小图片宽度,将会有较小的颜色范围,这可以用于卡通着色。

 

创建浮雕着色器

 

是时候创建新的着色器了。

 

1.        创建新类CSEEmboss(同上)

2.        helloworldLayer中修改init方法

技术分享

3.        添加一个着色器CSEEmboss.fsh,添加到bundle中(同上)

修该CSEEmboss.fsh如下

技术分享


上述代码实现了什么:

1.    此着色器需要从顶点着色器传来的纹理坐标,和从程序中传来的两个纹理

2.    定义纹理坐标空间所关联纹理的每个像素的尺寸

3.    复制纹理坐标(在下一节中你会知道原因)

4.    定义基础颜色,为白色的一般(which will be half the power of whitepower为幂,在这不知如何翻译合适),然后减去每个像素斜向运动的颜色,并添加相反的斜向运动位移。这会使像素处于不同的颜色边框并站立。相同颜色的像素会与基础颜色更接近。

5.    对颜色平均,这样你会的到在原始纹理中关注于不同颜色的灰度图。

运行程序:


 技术分享


 

添加动作

那确实很cool,那么我们给这个浮雕效果添加一些动作怎么样?

打开CSEEmboss.m,移除与ramp texture有关的两个变量,

技术分享

你需要uniform location,添加以下几行到init方法中(替换当前的3-5

技术分享

上述代码获取了时间值的uniform location,并且加入到cocos2dupdate循环(每帧需要调用的函数)

技术分享

 

上述代码每次更新时增加totalTime的值,并把它传给你自定义的着色器。调用时使用glUniform1f,因为你只传递了一个float值。

 

现在来修改一下你的CSEEmboss.fsh着色器,以便使用传入的uiniform类型的u_time。为了使用这个值来进行移动,你可以使用的最简单函数是基于u_time sine/cosine。替换#3部分的代码为:

技术分享

新的代码通过改变由texCoords所标识的坐标偏移来创建一个在-6+6变化的波浪动作。

 

编译运行,选择Emboss menu你会看到一个浮雕化的cocos2d图标在移动。

简单动态的草

 

在最后,你将使用着色器来创建一些简单动态的草,你可以把它用到你的cocos2d 2.x游戏中。

使用iOS\cocos2d v2.x\CCNode模板类创建一个新的文件,使其继承CCLayer并命名为CSEGrass.m(如果你也复制@implementation 这一行,不要忘记更改类名),在init方法中改变加载的片元着色器文件名为CSEGrass.fsh

 

替换#1部分的代码为:

技术分享

我们把代码改为使用草的纹理,由于草的纹理已经是横向状态,所以我们把rotation去掉。

 

接下来打开HelloWorldLayer.m 并引入 CSEGrass.h 头文件:

#import "CSEGrass.h"

添加一个新的菜单项:

技术分享

添加一个新的片元着色器CSEGrass.fsh,添加步骤同上。

 

修改片元着色器为:


技术分享

 

 

新的着色器是这样工作的以使草向一方弯曲,然后向另一方弯曲。这个弯曲影响操的顶部,而不是整个纹理。

以下是每一步的说明:

1.        定义一些常量以便于修改特效。

2.        基于高度去计算纹理的偏移。纹理在顶部从0开始,所以你需要反转一下。

3.        你不希望使用高度值去影响线性弯曲,正如你所希望纹理底部保持直立。弯曲程度应该随着草的高度而增加,所以这里最合适的方法是基于高度的指数去计算弯曲。

4.        提供弯曲的最简单方法是调用sine函数。将频率乘以速度常量,弯曲范围随sine对应的值与bendFactor的乘积而改变

5.        offset偏移量加到texCoord.x 上,函数fract可以确保当你获取的值小于0或大于1时,纹理会重复。然后只用偏移颜色作为你最终的片元颜色。

 

编译运行程序:

技术分享

 

翻译到此为止吧,后面还有一点无关紧要的,有兴趣的可以自己看英文的。

cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译)