卡通渲染Cocos2d-x中的实现(描边与对物体表面颜色的色阶化)

卡通渲染Cocos2d-x中的实现

在一些类型的游戏中,使用卡通渲染能够将原有模型的一些细节剥离,使原本比较写实的模型变得卡通化。在这里,我向大家介绍简单介绍一下如何在Cocos2d-x中实现卡通渲染。


事实上,卡通渲染具体来说,可以分为两个部分:描边与对物体表面颜色的色阶化,这两部分,我们会分开具体来讲解:


(一)描边

思路:

首先我们来说一说如何对物体进行描边,讲述描边的代码可以在Sprite3Dtest.cpp下的Sprite3DWithSkinOutlineTest 以及Effect3DOutline 中找到,在这里对描边算法的实现非常简单,我们实际上是将一个物体绘制了两遍,第一遍,我们正常的绘制一个模型(开启背面裁剪);第二遍,我们开启正面裁剪,然后我们将模型的每个经过变换后的顶点,按照其被变换后的法线的位置向外延展一段距离(这个值可以自定义,代表描边的线宽),为什么我们这里要按照变换后的法线的方向来延展顶点的位置呢?原因很简单,因为我们延展顶点的位置是想达到一个轮廓的感觉,而轮廓这个概念本身是相对于相机或者说视图的,可以想象一下两个人在不同的角度看同一个物体,他们每个人的脑海里的轮廓的概念是不一致的,因为我们获得的法线是在世界坐标系的,将其变换后,正面向我们的顶点的法线是朝向我们的,而这部分的顶点属于正面,在开启了正面裁剪之后,这部分边缘将会不见,所以我们只会在游戏中看到相对于我们的(管注:顶点的法线是朝向我们的)视角的描边

实现:

好了,我们现在来看看Cocos2d-x中,这个是怎么做到的,首先在Effect3DOutline 中,我们通过virtual void setTarget(EffectSprite3D *sprite) override; 来得知我们需要描边的精灵是什么,并将其储存为成员变量,同时,我们通过传入的target的创建一个State来管理我们自定义的shader:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

_glProgramState = GLProgramState::create(glprogram);

_glProgramState->retain();

_glProgramState->setUniformVec3("OutLineColor", _outlineColor);

_glProgramState->setUniformFloat("OutlineWidth", _outlineWidth);

_sprite = sprite;

auto mesh = sprite->getMesh();

long offset = 0;

for (auto i = 0; i < mesh->getMeshVertexAttribCount(); i++)

{

     auto meshvertexattrib = mesh->getMeshVertexAttribute(i);

     _glProgramState->setVertexAttribPointer(s_attributeNames[meshvertexattrib.vertexAttrib],

                     meshvertexattrib.size,

                     meshvertexattrib.type,

                     GL_FALSE,

                     mesh->getVertexSizeInBytes(),

                    (void*)offset);

     offset += meshvertexattrib.attribSizeBytes;

}

这些准备工作做好了之后,我们为他写了一个Draw函数,在这里最重要的一点是我们一定要开启正面裁剪,否则整个模型有可能被“描边模型“给罩住:

1

2

glEnable(GL_CULL_FACE);

glCullFace(GL_FRONT);

然后我们拿在setTarget中获取的3D精灵以及着色器的State,进行自定义的渲染:

1

2

3

4

if(_sprite)

_glProgramState->apply(transform);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->getIndexBuffer());

glDrawElements(mesh->getPrimitiveType(), (GLsizei)mesh->getIndexCount(), mesh->getIndexFormat(), 0);

注意,千万不要忘记将背面裁剪给重新激活,否则,会影响到后续图元的绘制

CPU这端的方法就讲解完了,我们来看看shader是如何实现的:

1

2

3

vec4 normalproj = CC_MVPMatrix * vec4(a_normal, 0);

normalproj = normalize(normalproj);

pos.xy += normalproj.xy * (OutlineWidth * (pos.z * 0.5 + 0.5));

首先我们用MVP矩阵连乘,将其变换到投影后的发现,请注意法线是方向,而不是点,在补足齐次坐标的时候应该填上的是0,千万不能写成vec4(a_normal,1)。

然后我们将其进行规范化处理,因为在GL在规范化空间内坐标在(-1,1)之间的,我们显然不能让要描的边要比0还要小,所以我们将其规范化到0~1的范围内。

请注意,在这里,我们不对pos和nromalProj的xyz分量除以w,这是因为,我们在这里是直接让gl_Position直接赋值为pos的最终值,这一步还没有进行透视除法,所以下一个阶段一起除以W,不会影响结果,最后的效果为:

miaobian1.jpg

 
(二)色阶化

思路:

除了描边外,写实的物体与卡通物体的显著不同在于卡通物体外观颜色是有色阶化,这是因为卡通图片是人用手绘制的,由于材料和设备的限制,通常会有比较大的色块,色阶,不可能像用计算机渲染的效果那么写实,我们通过自定义的shader来模拟色阶化的效果,从而达到卡通渲染的目的。

实现:
所有的代码都在Sprite3Dtest.cpp下的 Sprite3DBasicToonShaderTest  找到,方法很简,要实现色阶化,我们先让一个方向光打在模型身上,然后根据Lambert算法,我们能得到光照照射的因子,我们就通过对这个因子离散化,来达到色阶的效果。

我们先创建一个自定义的state,并将精灵mesh的信息传递过去:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

auto teapot = Sprite3D::create("Sprite3DTest/teapot.c3b"); 

auto shader =GLProgram::createWithFilenames("Sprite3DTest/toon.vert","Sprite3DTest/toon.frag");

auto state = GLProgramState::create(shader);

teapot->setGLProgramState(state);

long offset = 0;

auto attributeCount = teapot->getMesh()->getMeshVertexAttribCount();

for (auto i = 0; i < attributeCount; i++) {

        auto meshattribute = teapot->getMesh()->getMeshVertexAttribute(i);

        state->setVertexAttribPointer(s_attributeNames[meshattribute.vertexAttrib],

            meshattribute.size,

            meshattribute.type,

            GL_FALSE,

            teapot->getMesh()->getVertexSizeInBytes(),

            (GLvoid*)offset);

  offset += meshattribute.attribSizeBytes;

}

我们接下来看看shader是如何写的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

vec3 light_direction = vec3(1,-1,0);

light_direction = normalize(light_direction);

vec3 light_color = vec3(1,1,1);

vec3 normal  = normalize(v_normal);

float diffuse_factor = dot(normal,-light_direction);

vec4 diffuse_color = texture2D(CC_Texture0,v_texture_coord);

if (diffuse_factor > 0.95)      

    diffuse_factor=1.0;

else if (diffuse_factor > 0.75) 

    diffuse_factor = 0.8;

else if (diffuse_factor > 0.50) 

    diffuse_factor = 0.6;

else                       

    diffuse_factor = 0.4;

我们先创建一个光的方向,然后将其与法线点乘,得到的就是夹角的余弦值,这个diffuse_factor我们将其离散化成{1.0,0.8,0.6,0.4}然后用这个diffuse_factor与纹理采样出来的颜色相乘,我们就可以得到色阶化的颜色,请看最终的效果:

sejiehua.jpg

只要大家按照上述过程做,就能实现比较好的卡通渲染的效果了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值