您的位置:首页 > 移动开发 > Cocos引擎

Cocos2d-x pgeRippleSprite OpenGL ES2.0 Shader版本

2014-01-25 13:08 776 查看
前几年移植过一篇水波纹的cocos2d-x实现, 但是是使用OpenGL ES2.0以前的版本实现的,计算纹理坐标是使用CPU计算的,当水波纹较多时fps较低。

而且由于当时不会openGL,所以一直没有改。最近将OpenGL比较系统的学习了一遍,现在移植了一个Shader版本,能够显著提高效率。

1. pgeRippleSiprite.h

//

// pgeRippleSprite.h

// test2dx

//

// Created by limeng on 14-1-16.

//

//

#ifndef __test2dx__pgeRippleSprite__

#define __test2dx__pgeRippleSprite__

#include <iostream>

#include <list>

//

// pgeRippleSprite.h

// rippleDemo

//

// Created by Lars Birkemose on 02/12/11.

// Copyright 2011 Protec Electronics. All rights reserved.

//

// --------------------------------------------------------------------------

// import headers

// porting to cplusplus by wanghong.li (wanghong.li1029@163.com)on 04/01/12

// All rights reserved

#include "cocos2d.h"

#include <list>

#include "ccTypes.h"

USING_NS_CC;

// --------------------------------------------------------------------------

// defines

#define RIPPLE_DEFAULT_QUAD_COUNT_X 100

#define RIPPLE_DEFAULT_QUAD_COUNT_Y 60

#define RIPPLE_BASE_GAIN 0.1f // an internal constant

#define RIPPLE_DEFAULT_RADIUS 400 // radius in pixels //半径

#define RIPPLE_DEFAULT_RIPPLE_CYCLE 0.4f // timing on ripple ( 1/frequenzy ) //帧频

#define RIPPLE_DEFAULT_LIFESPAN 4.0f // entire ripple lifespan //时长

#define RIPPLE_CHILD_MODIFIER 2.0f

// --------------------------------------------------------------------------

// typedefs

typedef enum {
RIPPLE_TYPE_RUBBER, // a soft rubber sheet
RIPPLE_TYPE_GEL, // high viscosity fluid
RIPPLE_TYPE_WATER, // low viscosity fluid
} RIPPLE_TYPE;

typedef enum {
RIPPLE_CHILD_LEFT,
RIPPLE_CHILD_TOP,
RIPPLE_CHILD_RIGHT,
RIPPLE_CHILD_BOTTOM,
RIPPLE_CHILD_COUNT
} RIPPLE_CHILD;

typedefstruct _rippleData {
bool parent; // ripple is a parent
bool childCreated[
4 ];
// child created ( in the 4 direction )
RIPPLE_TYPE rippleType; // type of ripple ( se update: )

cocos2d::CCPoint center; // ripple center ( but
you just knew that, didn't you? )
cocos2d::CCPoint centerCoordinate; // ripple center in texture
coordinates
float radius; // radius at which ripple has faded 100%
float strength; // ripple strength
float runtime; // current run time
float currentRadius; // current radius
float rippleCycle; // ripple cycle timing
float lifespan; // total life span
} rippleData;

// --------------------------------------------------------------------------

// pgeRippleSprite

typedefstd::list<rippleData*>::iterator
RIPPLE_DATA_LIST;
typedefstd::list<rippleData*>::reverse_iterator
REVERSE_RIPPLE_DATA_LIST;

class CCpgeRippleSprite :public
cocos2d::CCNode
{

private:

ccV3F_C4B_T2F_Quad m_sQuad;

void setTextureRect(constCCRect& rect,
bool rotated,const
CCSize& untrimmedSize);
void setTextureCoords(CCRect rect);
void setMyTexture(CCTexture2D *texture);
void setBatchNode(CCSpriteBatchNode *pobSpriteBatchNode);
CCPoint m_obOffsetPosition;

public:
CCpgeRippleSprite();
~CCpgeRippleSprite();

CC_SYNTHESIZE(cocos2d::CCTexture2D*,m_texture, Texture)
CC_SYNTHESIZE(int,m_quadCountX, QuadCountX)
CC_SYNTHESIZE(int,m_quadCountY, QuadCountY)
CC_SYNTHESIZE(int,m_VerticesPrStrip, VerticesPrStrip)
CC_SYNTHESIZE(int,m_bufferSize, BuffSize)

CC_SYNTHESIZE(cocos2d::CCPoint*,m_vertice, Vertice)

CC_SYNTHESIZE(cocos2d::CCPoint*,m_textureCoordinate,
TextureCoordinate)
CC_SYNTHESIZE_READONLY(float*,m_edgeVertice, EdgeVertice)

CC_SYNTHESIZE_READONLY_PASS_BY_REF(std::list<rippleData*>,m_rippleList,
RippleList)

public:
static
CCpgeRippleSprite* rippleSpriteWithFile(constchar* filename);

bool initWithFile(constchar* filename);
virtual
void draw();
void update(float dt);
void addRipple(cocos2d::CCPoint &pos,RIPPLE_TYPE
type, float strength);

protected:
bool initShader();
void tesselate();
void addRippleChild(rippleData* parent,RIPPLE_CHILD type);

protected:
int m_texture_max_idx;
int m_ripple_num_idx;
bool ripple_dirty_;
};

#endif /* defined(__test2dx__pgeRippleSprite__) */

pgeRippleSprite.cpp

//

// pgeRippleSprite.cpp

// test2dx

//

// Created by limeng on 14-1-16.

//

//

#include "pgeRippleSprite.h"

#include "CCGL.h"

using namespace
cocos2d;

#define RippleEdgeAttr 4

CCpgeRippleSprite*CCpgeRippleSprite::rippleSpriteWithFile(constchar*
filename)
{
CCpgeRippleSprite* pgeRippleSprite =
new CCpgeRippleSprite();
if(pgeRippleSprite && pgeRippleSprite->initWithFile(filename))
{
pgeRippleSprite->autorelease();
return pgeRippleSprite;
}

CC_SAFE_DELETE(pgeRippleSprite);

return
NULL;
}

CCpgeRippleSprite::CCpgeRippleSprite()

:m_texture(NULL),

m_vertice(NULL),

m_textureCoordinate(NULL),

m_edgeVertice(NULL),

m_texture_max_idx(0),

m_ripple_num_idx(0),

ripple_dirty_(false)
{

}

CCpgeRippleSprite::~CCpgeRippleSprite()
{

CC_SAFE_RELEASE(m_texture);
CC_SAFE_DELETE_ARRAY(m_vertice);
CC_SAFE_DELETE_ARRAY(m_textureCoordinate);
CC_SAFE_DELETE_ARRAY(m_edgeVertice);

RIPPLE_DATA_LIST iterBegin =m_rippleList.begin();

while (iterBegin !=
m_rippleList.end())
{
rippleData* date = *iterBegin;

CC_SAFE_DELETE(date);

iterBegin++;
}
m_rippleList.clear();
}

boolCCpgeRippleSprite::initWithFile(constchar*
filename)
{

#if 0
CCGLProgram * glShaderProgram = CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTexture);
this->setShaderProgram(glShaderProgram);

#endif

if (!initShader())
{

return
false;
}

m_texture =CCTextureCache::sharedTextureCache()->addImage(filename);

if (!m_texture)
{

return
false;
}
m_texture->retain();

m_vertice =
NULL;

m_textureCoordinate =NULL;
CC_SAFE_DELETE_ARRAY(m_vertice);
CC_SAFE_DELETE_ARRAY(m_textureCoordinate);
CC_SAFE_DELETE_ARRAY(m_edgeVertice);
m_quadCountX = RIPPLE_DEFAULT_QUAD_COUNT_X;
m_quadCountY = RIPPLE_DEFAULT_QUAD_COUNT_Y;

tesselate();

schedule(schedule_selector(CCpgeRippleSprite::update));

m_pShaderProgram->use();

GLfloat texture_max[] = {m_texture->getMaxS(),m_texture->getMaxT()};

m_pShaderProgram->setUniformLocationWith2fv(m_texture_max_idx, texture_max,1);

CHECK_GL_ERROR_DEBUG();

return
true;
}

bool CCpgeRippleSprite::initShader()
{
CCGLProgram* glProgram =
new CCGLProgram();

if (!glProgram->initWithVertexShaderFilename("shaders/ripple.vsh","shaders/ripple.fsh"))
{

return
false;
}

glProgram->addAttribute(kCCAttributeNamePosition,kCCVertexAttrib_Position);

glProgram->addAttribute(kCCAttributeNameTexCoord,kCCVertexAttrib_TexCoords);
glProgram->addAttribute("a_edge",RippleEdgeAttr);

glProgram->link();
glProgram->updateUniforms();

m_texture_max_idx = glProgram->getUniformLocationForName("texture_max");

m_ripple_num_idx = glProgram->getUniformLocationForName("ripple_num");

CHECK_GL_ERROR_DEBUG();

setShaderProgram(glProgram);

return
true;
}

void CCpgeRippleSprite::draw()
{

CC_NODE_DRAW_SETUP();

if (m_texture !=NULL)
{

ccGLBindTexture2D(m_texture->getName() );

ccGLEnableVertexAttribs (kCCVertexAttribFlag_Position |
kCCVertexAttribFlag_TexCoords );

glEnableVertexAttribArray(RippleEdgeAttr);
}

// vertex

glVertexAttribPointer(kCCVertexAttrib_Position,2,
GL_FLOAT,GL_FALSE,
0, m_vertice);

if (m_texture !=NULL)
{

// texCoods

//glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, ( m_rippleList.size() == 0 ) ? m_textureCoordinate : m_rippleCoordinate );

glVertexAttribPointer(kCCVertexAttrib_TexCoords,2,
GL_FLOAT,GL_FALSE,
0, m_textureCoordinate);

glVertexAttribPointer(RippleEdgeAttr,1,
GL_FLOAT,GL_FALSE,
0,m_edgeVertice);
}

// color

// glVertexAttribPointer(kCCVertexAttrib_Color, 2, GL_FLOAT, GL_FALSE, 0, m_vertice);

for (
int strip =0; strip <
m_quadCountY; strip ++ ) {

glDrawArrays(GL_TRIANGLE_STRIP, strip *m_VerticesPrStrip,
m_VerticesPrStrip );
}

// glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

CHECK_GL_ERROR_DEBUG();

CC_INCREMENT_GL_DRAWS(1);

CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite,"CCSprite - draw");
}

void CCpgeRippleSprite::tesselate()
{
int vertexPos =
0;
CCPoint normalized;

CC_SAFE_DELETE_ARRAY(m_vertice);

CC_SAFE_DELETE_ARRAY(m_textureCoordinate);

CC_SAFE_DELETE_ARRAY(m_edgeVertice);

m_VerticesPrStrip =2 * (m_quadCountX +1);

m_bufferSize =m_VerticesPrStrip *
m_quadCountY;

//allocate buffers

m_vertice =
new CCPoint[m_bufferSize];

m_textureCoordinate =new
CCPoint[m_bufferSize];

m_edgeVertice =new
float[m_bufferSize];

memset(m_edgeVertice,0,
sizeof(int) *m_bufferSize);

vertexPos =0;

CCLOG("循环开始");
for (int y =0; y <
m_quadCountY; y++)
{
for (int x =0; x < (m_quadCountX
+1); x++)
{
for (
int yy =0; yy <
2; yy ++ ) {

// first simply calculate a normalized position into rectangle
normalized.x = (float )x / (
float )m_quadCountX;
normalized.y = (float )( y + yy ) / (
float )m_quadCountY;

// calculate vertex by multiplying rectangle ( texture ) size
CCSize contentSize =
m_texture->getContentSize();
m_vertice[ vertexPos ] =
ccp( normalized.x * contentSize.width, normalized.y * contentSize.height);

// adjust texture coordinates according to texture size

// as a texture is always in the power of 2, maxS and maxT are the fragment of the size actually used

// invert y on texture coordinates
m_textureCoordinate[ vertexPos ] =
ccp( normalized.x * m_texture->getMaxS(),
m_texture->getMaxT()- ( normalized.y *m_texture->getMaxT() ) );

// check if vertice is an edge vertice, because edge vertices are never modified to keep outline consistent
if (( x ==
0 ) ||
( x ==m_quadCountX ) ||
( ( y ==0 ) && ( yy ==
0 ) ) ||
( ( y == (m_quadCountY -
1 ) ) && ( yy >0 ) ))
{
m_edgeVertice[vertexPos] =
1.0f;
}

// next buffer pos
vertexPos ++;
}
}
}
CCLOG("循环结束");
}

void CCpgeRippleSprite::addRipple(cocos2d::CCPoint &pos,RIPPLE_TYPE
type, float strength)
{
rippleData* newRipple;

// allocate new ripple
newRipple =new
rippleData;

// initialize ripple
newRipple->parent =true;
for (
int count =0; count <
4; count ++ ) newRipple->childCreated[ count ] =false;
newRipple->rippleType = type;
newRipple->center = pos;

CCSize contentSize =
m_texture->getContentSize();
newRipple->centerCoordinate =ccp( pos.x / contentSize.width
*m_texture->getMaxS(),m_texture->getMaxT() - ( pos.y / contentSize.height
*m_texture->getMaxT()) );

newRipple->radius =RIPPLE_DEFAULT_RADIUS;
// * strength;
newRipple->strength = strength;
newRipple->runtime =0;
newRipple->currentRadius =0;

newRipple->rippleCycle =RIPPLE_DEFAULT_RIPPLE_CYCLE;

newRipple->lifespan =RIPPLE_DEFAULT_LIFESPAN;

// add ripple to running list
m_rippleList.push_back(newRipple);

ripple_dirty_ =true;
}

void CCpgeRippleSprite::addRippleChild(rippleData* parent,RIPPLE_CHILD type)
{
rippleData* newRipple;
CCPoint pos;

// allocate new ripple
newRipple =new
rippleData;

// new ripple is pretty much a copy of its parent
memcpy( newRipple, parent,
sizeof( rippleData ) );

// not a parent
newRipple->parent =false;

CCSize winSize =
CCDirector::sharedDirector()->getWinSize();

// mirror position
switch ( type ) {

caseRIPPLE_CHILD_LEFT:
pos =ccp( -parent->center.x, parent->center.y );
break;

caseRIPPLE_CHILD_TOP:
pos =ccp( parent->center.x, winSize.height + ( winSize.height
- parent->center.y ) );
break;

caseRIPPLE_CHILD_RIGHT:
pos =ccp( winSize.width + ( winSize.width - parent->center.x
), parent->center.y );
break;

caseRIPPLE_CHILD_BOTTOM:
default:
pos =ccp( parent->center.x, -parent->center.y );
break;
}

newRipple->center = pos;

CCSize contentSize =
m_texture->getContentSize();

newRipple->centerCoordinate =ccp( pos.x / contentSize.width
*m_texture->getMaxS(),m_texture->getMaxT()- ( pos.y / contentSize.height
*m_texture->getMaxT()) );

newRipple->strength *=RIPPLE_CHILD_MODIFIER;

// indicate child used
parent->childCreated[ type ] =true;

// add ripple to running list
m_rippleList.push_back(newRipple);

ripple_dirty_ =true;
}

void CCpgeRippleSprite::update(float dt)
{
rippleData* ripple =
NULL;

// test if any ripples at all
if (
m_rippleList.size() ==0 )
return;

// scan through running ripples

// the scan is backwards, so that ripples can be removed on the fly

CCSize winSize =
CCDirector::sharedDirector()->getWinSize();

REVERSE_RIPPLE_DATA_LIST iterRipple =m_rippleList.rbegin();

while ( iterRipple !=
m_rippleList.rend())
{

// get ripple data
ripple = *iterRipple;

// calculate radius
ripple->currentRadius = ripple->radius * ripple->runtime / ripple->lifespan;

// check if ripple should expire
ripple->runtime += dt;
if ( ripple->runtime >= ripple->lifespan )
{

// free memory, and remove from list
CC_SAFE_DELETE( ripple );

RIPPLE_DATA_LIST it = --iterRipple.base() ;
RIPPLE_DATA_LIST it_after_del =
m_rippleList.erase(it);
iterRipple =std::list<rippleData*>::reverse_iterator(it_after_del);
ripple_dirty_ =
true;
}
else
{

// check for creation of child ripples
if ( ripple->parent ==true ) {

// left ripple
if ( ( ripple->childCreated[RIPPLE_CHILD_LEFT ] ==
false ) && ( ripple->currentRadius > ripple->center.x ) ) {
addRippleChild(ripple,
RIPPLE_CHILD_LEFT);
}

// top ripple
if ( ( ripple->childCreated[RIPPLE_CHILD_TOP ] ==
false ) && ( ripple->currentRadius > winSize.height - ripple->center.y ) ) {
addRippleChild(ripple,
RIPPLE_CHILD_TOP);
}

// right ripple
if ( ( ripple->childCreated[RIPPLE_CHILD_RIGHT ] ==
false ) && ( ripple->currentRadius > winSize.width - ripple->center.x ) ) {

addRippleChild(ripple,RIPPLE_CHILD_RIGHT);
}

// bottom ripple
if ( ( ripple->childCreated[RIPPLE_CHILD_BOTTOM ] ==
false ) && ( ripple->currentRadius > ripple->center.y ) ) {

addRippleChild(ripple,RIPPLE_CHILD_BOTTOM);
}
}
iterRipple++;
}
}

iterRipple =m_rippleList.rbegin();

int ripple_index =
0;

m_pShaderProgram->use();

m_pShaderProgram->setUniformLocationWith1i(m_ripple_num_idx, (int)(m_rippleList.size()));

CHECK_GL_ERROR_DEBUG();

while ( iterRipple !=
m_rippleList.rend())
{

// get ripple data
ripple = *iterRipple;

char ripple_attr_name[128] = {0};
sprintf(ripple_attr_name,
"ripples[%d].center", ripple_index);

GLint ripple_center =
m_pShaderProgram->getUniformLocationForName(ripple_attr_name);
m_pShaderProgram->setUniformLocationWith2fv(ripple_center, (GLfloat*)(&ripple->center),1);

CHECK_GL_ERROR_DEBUG();

sprintf(ripple_attr_name,
"ripples[%d].coor_center", ripple_index);
GLint ripple_coor_center =
m_pShaderProgram->getUniformLocationForName(ripple_attr_name);
m_pShaderProgram->setUniformLocationWith2fv(ripple_coor_center, (GLfloat*)(&ripple->centerCoordinate),1);

CHECK_GL_ERROR_DEBUG();

sprintf(ripple_attr_name,
"ripples[%d].ripple_type", ripple_index);
GLint rippe_type =
m_pShaderProgram->getUniformLocationForName(ripple_attr_name);
m_pShaderProgram->setUniformLocationWith1i(rippe_type, (GLint)ripple->rippleType);

CHECK_GL_ERROR_DEBUG();

sprintf(ripple_attr_name,
"ripples[%d].run_time", ripple_index);
GLint ripple_run_time =
m_pShaderProgram->getUniformLocationForName(ripple_attr_name);
m_pShaderProgram->setUniformLocationWith1f(ripple_run_time, (GLfloat)ripple->runtime);

CHECK_GL_ERROR_DEBUG();

sprintf(ripple_attr_name,
"ripples[%d].current_radius", ripple_index);
GLint ripple_radius =
m_pShaderProgram->getUniformLocationForName(ripple_attr_name);
m_pShaderProgram->setUniformLocationWith1f(ripple_radius, (GLfloat)ripple->currentRadius);

CHECK_GL_ERROR_DEBUG();

iterRipple++;
ripple_index++;
}
}

Shader文件我放在Resources/shaders文件夹下,代码中hard code指定的。所以如果要正确运行必须也放在这个文件夹下。

顶点着色器(ripple.vsh)

attribute vec4 a_position;

attribute vec2 a_texCoord;

attribute float a_edge;

struct RippleData

{

vec2 center;

vec2 coor_center;

int ripple_type;

float run_time;

float current_radius;

};

uniform vec2 texture_max;

uniform int ripple_num;

uniform RippleData ripples[100];

#ifdef GL_ES

varying mediump vec2 v_texCoord;

#else

varying vec2 v_texCoord;

#endif

void main()

{

float PI=3.1415927;

float ripple_cycle = 0.4;

float ripple_radius = 400.0;

float life_span = 4.0;

gl_Position = CC_MVPMatrix * a_position;

vec2 vertex_pos = a_position.xy;

if (ripple_num == 0 || a_edge == 1.0)

{

v_texCoord = a_texCoord;

}

else

{

v_texCoord = a_texCoord;

for (int i = 0; i < ripple_num; i++)

{

float ripple_distance = distance(ripples[i].center, vertex_pos);

float correction = 0.0;

if (ripple_distance < ripples[i].current_radius)

{

if (ripples[i].ripple_type == 0)

{

correction = sin(2.0 * PI * ripples[i].run_time / ripple_cycle);

}

else if (ripples[i].ripple_type == 1)

{

correction = sin(2.0 * PI * (ripples[i].current_radius - ripple_distance)/ ripple_radius * life_span / ripple_cycle);

}

else

{

correction = (ripple_radius * ripple_cycle / life_span)/(ripples[i].current_radius - ripple_distance);

if (correction > 1.0) correction = 1.0;

correction = correction * correction;

correction = sin(2.0 * PI * (ripples[i].current_radius - ripple_distance) / ripple_radius * life_span / ripple_cycle) * correction;

}

correction = correction * (1.0 - ripple_distance / ripples[i].current_radius);

correction = correction * (1.0 - ripples[i].run_time / life_span);

correction = correction * 0.1;

correction = correction * 2.0;

correction = correction / distance(ripples[i].coor_center, v_texCoord);

v_texCoord = v_texCoord + (v_texCoord - ripples[i].coor_center) * correction;

v_texCoord = clamp(v_texCoord, vec2(0.0, 0.0), texture_max);

}

}

}

}

片段着色器(ripple.fsh)

#ifdef GL_ES

precision lowp float;

#endif

varying vec2 v_texCoord;

uniform sampler2D CC_Texture0;

void main()

{

gl_FragColor = texture2D(CC_Texture0, v_texCoord);

}

客户端用法,在点击时调用addRipple即可。

cocos2d::CCTouch* pTouch = (cocos2d::CCTouch*)pTouches->anyObject();
cocos2d::CCPoint touchLocation = pTouch->getLocation();//
Get the touch position
touchLocation =m_rippleSprite->convertToNodeSpace(touchLocation);

m_rippleSprite->addRipple(touchLocation,RIPPLE_TYPE_WATER,
1.0);

PS:由于着色器的Uniform变量的个数有限,所以我设置了最多只能有100个Ripple。而且此处实现边界水波纹反弹是使用在沿对应的边界建立一个镜像Ripple的方式来实现的。所以一个水波纹在到边界处有可能会变成4个。所以如果产生的水波纹太多,并且水波纹的life_span很长,可能会出现问题。目前考虑到得是每秒点击12次,声明周期是2S, 24 * 4 < 100, 所以能满足需求。
另外,感谢右半边翅膀Vincent改写的2.0 版本draw函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: