前几年移植过一篇水波纹的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 ([email protected])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
// 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函数。