Cocos2d-x 3.3 的3D開發功能介紹
今天下載了Cocos2d-x 3.3,3D功能果然強大
主要有以下功能:
1. 基本的Sprite3D使用,載入靜態模型和動態模型,看 Sprite3DBasicTest
2.Sprite3D物件的旋轉,縮放等Action操作
3.Sprite3D中使用Shader特效,實現outLine
4.Animate3D來建立3D動畫
5.動態增加3D骨骼,實現怪物新增手持武器功能
6,動態修改骨骼面板實現換裝功能Sprite3DReskinTest
7.通過包圍盒實現3D模型碰撞,Sprite3DWithOBBPerfromanceTest
8.實現水平映象3D模型,Sprite3DMirrorTest
下面介紹一下Sprite3DTest裡面的原始碼
#include "Sprite3DTest.h"
#include "3d/CCAnimation3D.h"
#include "3d/CCAnimate3D.h"
#include "3d/CCAttachNode.h"
#include "3d/CCRay.h"
#include "3d/CCSprite3D.h"
#include "renderer/CCVertexIndexBuffer.h"
#include "DrawNode3D.h"
1.在Scene中新增3D模型
void Sprite3DBasicTest::addNewSpriteWithCoords(Vec2
{
//這裡的obj可以使用3dmax直接匯出
// //option 1: load a obj that contain the texture in it 第一種方法是在模型檔案中包含了紋理
// auto sprite = Sprite3D::create("sprite3dTest/scene01.obj");
//option 2: load obj and assign the texture 第二種方法是在模型檔案中不包含紋理
auto sprite =Sprite3D::create("Sprite3DTest/boss1.obj");
sprite->setScale
sprite->setTexture("Sprite3DTest/boss.png");
//在Sprite3D中包含了一些基本的Shader特效,下面是讓3D模型實現outline
//sprite->setEffect(cocos2d::EFFECT_OUTLINE);
//add to scene
addChild( sprite );
sprite->setPosition( Vec2( p.x, p.y) );
ActionInterval* action;
float random = CCRANDOM_0_1();
if( random < 0.20 )
action = ScaleBy::create(3,2);
else if(random <0.40)
action = RotateBy::create(3,360);
else if( random <0.60)
action = Blink::create(1,3);
else if( random <0.8 )
action = TintBy::create(2,0, -255, -255);
else
action = FadeOut::create(2);
auto action_back = action->reverse();
auto seq = Sequence::create( action, action_back,nullptr );
sprite->runAction( RepeatForever::create(seq) );
//以上大家看到Sprite3D起始和Sprite類似都可以實現各種Action
}
void Sprite3DBasicTest::onTouchesEnded(conststd::vector<Touch*>& touches,Event* event)
{
for (auto touch: touches)
{
auto location = touch->getLocation();
//觸控式螢幕幕新增3D模型
addNewSpriteWithCoords( location );
}
}
2.透過觸控式螢幕幕拖動模型移動
Sprite3DHitTest::Sprite3DHitTest()
{
auto s =Director::getInstance()->getWinSize();
auto sprite1 =Sprite3D::create("Sprite3DTest/boss1.obj");
sprite1->setScale(4.f);
sprite1->setTexture("Sprite3DTest/boss.png");
sprite1->setPosition( Vec2(s.width/2, s.height/2) );
//add to scene
addChild( sprite1 );
sprite1->runAction(RepeatForever::create(RotateBy::create(3,360)));
auto sprite2 =Sprite3D::create("Sprite3DTest/boss1.obj");
sprite2->setScale(4.f);
sprite2->setTexture("Sprite3DTest/boss.png");
sprite2->setPosition( Vec2(s.width/2, s.height/2) );
sprite2->setAnchorPoint(Vec2(0.5,0.5));
//add to scene
addChild( sprite2 );
sprite2->runAction(RepeatForever::create(RotateBy::create(3, -360)));
// Make sprite1 touchable
auto listener1 =EventListenerTouchOneByOne::create();
listener1->setSwallowTouches(true);
listener1->onTouchBegan = [](Touch* touch,Event* event){
auto target = static_cast<Sprite3D*>(event->getCurrentTarget());
Rect rect = target->getBoundingBox();
if (rect.containsPoint(touch->getLocation()))
{
log("sprite3d began... x = %f, y = %f", touch->getLocation().x, touch->getLocation().y);
target->setOpacity(100);
return true;
}
return false;
};
listener1->onTouchMoved = [](Touch* touch,Event* event){
auto target = static_cast<Sprite3D*>(event->getCurrentTarget());
target->setPosition(target->getPosition() + touch->getDelta());
};
listener1->onTouchEnded = [=](Touch* touch,Event* event){
auto target = static_cast<Sprite3D*>(event->getCurrentTarget());
log("sprite3d onTouchesEnded.. ");
target->setOpacity(255);
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2);
}
3.在一個Sprite3D上使用Shader
Effect3D繼承REF封裝了Shader的處理
Effect3DOutline繼承了Effect3D,封裝了Outline型別的Shader
EffectSprite3D繼承了Sprite3D封裝了對Effect3DOutline的呼叫
class Effect3D : publicRef
{
public:
virtual void draw(constMat4 &transform) =0;
virtual void setTarget(EffectSprite3D *sprite) =0;
protected:
Effect3D() : _glProgramState(nullptr) {}
virtual ~Effect3D()
{
CC_SAFE_RELEASE(_glProgramState);
}
protected:
GLProgramState* _glProgramState;
};
class Effect3DOutline: publicEffect3D
{
public:
static Effect3DOutline* create();
void setOutlineColor(constVec3& color);
void setOutlineWidth(float width);
virtual void draw(constMat4 &transform) override;
virtual void setTarget(EffectSprite3D *sprite) override;
protected:
Effect3DOutline();
virtual ~Effect3DOutline();
bool init();
Vec3 _outlineColor;
float _outlineWidth;
//weak reference
EffectSprite3D* _sprite;
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
EventListenerCustom* _backToForegroundListener;
#endif
protected:
static const std::string _vertShaderFile;
static const std::string _fragShaderFile;
static const std::string _keyInGLProgramCache;
static const std::string _vertSkinnedShaderFile;
static const std::string _fragSkinnedShaderFile;
static const std::string _keySkinnedInGLProgramCache;
static GLProgram* getOrCreateProgram(bool isSkinned =false);
};
class EffectSprite3D : publicSprite3D
{
public:
static EffectSprite3D* createFromObjFileAndTexture(conststd::string& objFilePath,const std::string& textureFilePath);
static EffectSprite3D* create(conststd::string& path);
void setEffect3D(Effect3D* effect);
void addEffect(Effect3DOutline* effect,ssize_t order);
virtual void draw(Renderer *renderer,constMat4 &transform, uint32_t flags) override;
protected:
EffectSprite3D();
virtual ~EffectSprite3D();
std::vector<std::tuple<ssize_t,Effect3D*,CustomCommand>> _effects;
Effect3D* _defaultEffect;
CustomCommand _command;
};
class Sprite3DEffectTest : public Sprite3DTestDemo
{
public:
CREATE_FUNC(Sprite3DEffectTest);
Sprite3DEffectTest();
virtual std::string title()const override;
virtual std::string subtitle()const override;
void addNewSpriteWithCoords(Vec2 p);
void onTouchesEnded(conststd::vector<Touch*>& touches,Event* event);
};
const std::stringEffect3DOutline::_vertShaderFile ="Shaders3D/OutLine.vert";
const std::stringEffect3DOutline::_fragShaderFile ="Shaders3D/OutLine.frag";
const std::stringEffect3DOutline::_keyInGLProgramCache ="Effect3DLibrary_Outline";
const std::stringEffect3DOutline::_vertSkinnedShaderFile ="Shaders3D/SkinnedOutline.vert";
const std::stringEffect3DOutline::_fragSkinnedShaderFile ="Shaders3D/OutLine.frag";
const std::stringEffect3DOutline::_keySkinnedInGLProgramCache ="Effect3DLibrary_Outline";
GLProgram* Effect3DOutline::getOrCreateProgram(bool isSkinned/* = false */ )
{
if(isSkinned)
{
auto program = GLProgramCache::getInstance()->getGLProgram(_keySkinnedInGLProgramCache);
if(program == nullptr)
{
program = GLProgram::createWithFilenames(_vertSkinnedShaderFile,_fragSkinnedShaderFile);
GLProgramCache::getInstance()->addGLProgram(program,_keySkinnedInGLProgramCache);
}
return program;
}
else
{
auto program = GLProgramCache::getInstance()->getGLProgram(_keyInGLProgramCache);
if(program == nullptr)
{
program = GLProgram::createWithFilenames(_vertShaderFile,_fragShaderFile);
GLProgramCache::getInstance()->addGLProgram(program,_keyInGLProgramCache);
}
return program;
}
}
Effect3DOutline* Effect3DOutline::create()
{
Effect3DOutline* effect =new (std::nothrow)Effect3DOutline();
if(effect && effect->init())
{
effect->autorelease();
return effect;
}
else
{
CC_SAFE_DELETE(effect);
returnnullptr;
}
}
boolEffect3DOutline::init()
{
returntrue;
}
Effect3DOutline::Effect3DOutline()
: _outlineWidth(1.0f)
, _outlineColor(1,1,1)
, _sprite(nullptr)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
_backToForegroundListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED,
[this](EventCustom*)
{
auto glProgram = _glProgramState->getGLProgram();
glProgram->reset();
glProgram->initWithFilenames(_vertShaderFile, _fragShaderFile);
glProgram->link();
glProgram->updateUniforms();
}
);
Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_backToForegroundListener, -1);
#endif
}
Effect3DOutline::~Effect3DOutline()
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
Director::getInstance()->getEventDispatcher()->removeEventListener(_backToForegroundListener);
#endif
}
void Effect3DOutline::setOutlineColor(constVec3& color)
{
if(_outlineColor != color)
{
_outlineColor = color;
if(_glProgramState)
_glProgramState->setUniformVec3("OutLineColor",_outlineColor);
}
}
void Effect3DOutline::setOutlineWidth(float width)
{
if(_outlineWidth != width)
{
_outlineWidth = width;
if(_glProgramState)
_glProgramState->setUniformFloat("OutlineWidth",_outlineWidth);
}
}
voidEffect3DOutline::setTarget(EffectSprite3D *sprite)
{
CCASSERT(nullptr != sprite &&nullptr != sprite->getMesh(),"Error: Setting a null pointer or a null mesh EffectSprite3D to Effect3D");
if(sprite != _sprite)
{
GLProgram* glprogram;
if(!sprite->getMesh()->getSkin())
glprogram = GLProgram::createWithFilenames(_vertShaderFile,_fragShaderFile);
else
glprogram = GLProgram::createWithFilenames(_vertSkinnedShaderFile,_fragSkinnedShaderFile);
_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;
}
Color4F color(_sprite->getDisplayedColor());
color.a = _sprite->getDisplayedOpacity() /255.0f;
_glProgramState->setUniformVec4("u_color",Vec4(color.r, color.g, color.b, color.a));
}
}
static void MatrixPalleteCallBack(GLProgram* glProgram,Uniform* uniform,int paletteSize,const float* palette)
{
glUniform4fv( uniform->location, (GLsizei)paletteSize, (constfloat*)palette );
}
void Effect3DOutline::draw(constMat4 &transform)
{
//draw
Color4F color(_sprite->getDisplayedColor());
color.a = _sprite->getDisplayedOpacity() /255.0f;
_glProgramState->setUniformVec4("u_color",Vec4(color.r, color.g, color.b, color.a));
if(_sprite && _sprite->getMesh())
{
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
glEnable(GL_DEPTH_TEST);
auto mesh = _sprite->getMesh();
glBindBuffer(GL_ARRAY_BUFFER, mesh->getVertexBuffer());
auto skin = _sprite->getMesh()->getSkin();
if(_sprite && skin)
{
auto function = std::bind(MatrixPalleteCallBack,std::placeholders::_1,std::placeholders::_2,
skin->getMatrixPaletteSize(), (float*)skin->getMatrixPalette());
_glProgramState->setUniformCallback("u_matrixPalette", function);
}
if(_sprite)
_glProgramState->apply(transform);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->getIndexBuffer());
glDrawElements(mesh->getPrimitiveType(), mesh->getIndexCount(), mesh->getIndexFormat(),0);
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, mesh->getIndexCount());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
glBindBuffer(GL_ARRAY_BUFFER,0);
glDisable(GL_DEPTH_TEST);
glCullFace(GL_BACK);
glDisable(GL_CULL_FACE);
}
}
void EffectSprite3D::draw(cocos2d::Renderer *renderer,constcocos2d::Mat4 &transform,uint32_t flags)
{
for(auto &effect : _effects)
{
if(std::get<0>(effect) >=0)
break;
CustomCommand &cc = std::get<2>(effect);
cc.func = CC_CALLBACK_0(Effect3D::draw,std::get<1>(effect),transform);
renderer->addCommand(&cc);
}
if(!_defaultEffect)
{
Sprite3D::draw(renderer, transform, flags);
}
else
{
_command.init(_globalZOrder);
_command.func =CC_CALLBACK_0(Effect3D::draw,_defaultEffect, transform);
renderer->addCommand(&_command);
}
for(auto &effect : _effects)
{
if(std::get<0>(effect) <=0)
continue;
CustomCommand &cc = std::get<2>(effect);
cc.func = CC_CALLBACK_0(Effect3D::draw,std::get<1>(effect),transform);
renderer->addCommand(&cc);
}
}
//Sprite3DEffectTest中實現了對Sprite3DEffect的載入
Sprite3DEffectTest::Sprite3DEffectTest()
{
auto s =Director::getInstance()->getWinSize();
addNewSpriteWithCoords(Vec2(s.width/2, s.height/2) );
auto listener =EventListenerTouchAllAtOnce::create();
listener->onTouchesEnded = CC_CALLBACK_2(Sprite3DEffectTest::onTouchesEnded,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
}
void Sprite3DEffectTest::addNewSpriteWithCoords(Vec2 p)
{
//option 2: load obj and assign the texture
auto sprite =EffectSprite3D::createFromObjFileAndTexture("Sprite3DTest/boss1.obj","Sprite3DTest/boss.png");
Effect3DOutline* effect =Effect3DOutline::create();
sprite->addEffect(effect, -1);
effect->setOutlineColor(Vec3(1,0,0));
effect->setOutlineWidth(0.01f);
Effect3DOutline* effect2 =Effect3DOutline::create();
sprite->addEffect(effect2, -2);
effect2->setOutlineWidth(0.02f);
effect2->setOutlineColor(Vec3(1,1,0));
//sprite->setEffect3D(effect);
sprite->setScale(6.f);
//add to scene
addChild( sprite );
sprite->setPosition( Vec2( p.x, p.y) );
ActionInterval* action;
float random = CCRANDOM_0_1();
if( random < 0.20 )
action = ScaleBy::create(3,2);
else if(random <0.40)
action = RotateBy::create(3,360);
else if( random <0.60)
action = Blink::create(1,3);
else if( random <0.8 )
action = TintBy::create(2,0, -255, -255);
else
action = FadeOut::create(2);
auto action_back = action->reverse();
auto seq = Sequence::create( action, action_back,nullptr );
sprite->runAction( RepeatForever::create(seq) );
}
void Sprite3DEffectTest::onTouchesEnded(conststd::vector<Touch*>& touches,Event* event)
{
for (auto touch: touches)
{
auto location = touch->getLocation();
addNewSpriteWithCoords( location );
}
}
4.載入包含了3D紋理和動畫的C3B檔案,具體轉化方法為在tools下使用fbx-conv.exe將3dmax匯出的檔案轉化為c3b
用Sprite3D來載入c3b,用Animation3D,和Animate3D來建立動畫
auto animation = Animation3D::create(fileName);
if (animation)
{
auto animate = Animate3D::create(animation);
sprite->runAction(RepeatForever::create(animate));
}
void Sprite3DWithSkinTest::addNewSpriteWithCoords(Vec2 p)
{
std::string fileName ="Sprite3DTest/orc.c3b";
auto sprite = EffectSprite3D::create(fileName);
sprite->setScale(3);
sprite->setRotation3D(Vec3(0,180,0));
addChild(sprite);
sprite->setPosition( Vec2( p.x, p.y) );
auto animation = Animation3D::create(fileName);
if (animation)
{
auto animate = Animate3D::create(animation);
bool inverse = (std::rand() %3 ==0);
int rand2 = std::rand();
float speed = 1.0f;
if(rand2 % 3 ==1)
{
speed = animate->getSpeed() + CCRANDOM_0_1();
}
else if(rand2 %3 ==2)
{
speed = animate->getSpeed() - 0.5 * CCRANDOM_0_1();
}
animate->setSpeed(inverse ? -speed : speed);
sprite->runAction(RepeatForever::create(animate));
}
}
5.在以上模型和動畫中新增特效(方法和在靜態模型上一樣)
voidSprite3DWithSkinOutlineTest::addNewSpriteWithCoords(Vec2 p)
{
std::string fileName ="Sprite3DTest/orc.c3b";
auto sprite = EffectSprite3D::create(fileName);
Effect3DOutline* effect =Effect3DOutline::create();
effect->setOutlineColor(Vec3(1,0,0));
effect->setOutlineWidth(0.01f);
sprite->addEffect(effect, -1);
Effect3DOutline* effect2 =Effect3DOutline::create();
effect2->setOutlineWidth(0.02f);
effect2->setOutlineColor(Vec3(1,1,0));
sprite->addEffect(effect2, -2);
sprite->setScale(3);
sprite->setRotation3D(Vec3(0,180,0));
addChild(sprite);
sprite->setPosition( Vec2( p.x, p.y) );
auto animation = Animation3D::create(fileName);
if (animation)
{
auto animate = Animate3D::create(animation);
bool inverse = (std::rand() %3 ==0);
int rand2 = std::rand();
float speed = 1.0f;
if(rand2 % 3 ==1)
{
speed = animate->getSpeed() + CCRANDOM_0_1();
}
else if(rand2 %3 ==2)
{
speed = animate->getSpeed() - 0.5 * CCRANDOM_0_1();
}
animate->setSpeed(inverse ? -speed : speed);
sprite->runAction(RepeatForever::create(animate));
}
}
6.動畫的切換
既然每個3D動畫是Action就可以通過切換每個Sprite3D的Action來切換動畫,下面是一個小烏龜的2個動畫之間的切換
Animate3DTest::Animate3DTest()
: _hurt(nullptr)
, _swim(nullptr)
, _sprite(nullptr)
, _moveAction(nullptr)
, _elapseTransTime(0.f)
{ //新增小烏龜
addSprite3D();
auto listener =EventListenerTouchAllAtOnce::create();
listener->onTouchesEnded = CC_CALLBACK_2(Animate3DTest::onTouchesEnded,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
scheduleUpdate();
}
Animate3DTest::~Animate3DTest()
{
CC_SAFE_RELEASE(_moveAction);
CC_SAFE_RELEASE(_hurt);
CC_SAFE_RELEASE(_swim);
}
void Animate3DTest::update(float dt)
{
if (_state ==State::HURT_TO_SWIMMING)
{
_elapseTransTime += dt;
if (_elapseTransTime >=Animate3D::getTransitionTime())
{
_sprite->stopAction(_hurt);
_state = State::SWIMMING;
}
}
elseif (_state ==State::SWIMMING_TO_HURT)
{
_elapseTransTime += dt;
if (_elapseTransTime >=Animate3D::getTransitionTime())
{
_sprite->stopAction(_swim);
_state = State::HURT;
}
}
}
void Animate3DTest::addSprite3D()
{
std::string fileName ="Sprite3DTest/tortoise.c3b";
auto sprite = Sprite3D::create(fileName);
sprite->setScale(0.1f);
auto s =Director::getInstance()->getWinSize();
sprite->setPosition(Vec2(s.width *4.f /5.f, s.height /2.f));
addChild(sprite);
_sprite = sprite;
auto animation = Animation3D::create(fileName);
if (animation)
{ //2個動畫的時間不同,這些在3Dmax中定義
auto animate = Animate3D::create(animation, 0.f,1.933f);
_swim = RepeatForever::create(animate);
sprite->runAction(_swim);
_swim->retain();
_hurt = Animate3D::create(animation,1.933f,2.8f);
_hurt->retain();
_state = State::SWIMMING;
}
_moveAction = MoveTo::create(