cocos2dx 3.x 相机机制

来源:互联网 发布:lumia刷linux 编辑:程序博客网 时间:2024/06/06 07:38

cocos2dx 3.x 相机机制

一,3.x相机使用方法:

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

Camera* camera=Camera::create();camera->setCameraFlag(CameraFlag::USER1);this->addChild(camera);sprite->setCameraMask(2);    //因为2 & CameraFlag::USER1 !=0,所以cameraMask=2与CameraFlag::USER1匹配,sprite将使用上面创建的cameraVec3 eyePosOld=camera->getPosition3D();Vec3 eyePos=Vec3(x,y,eyePosOld.z);camera->setPosition3D(eyePos);

  assert(eyePos.z>0);

camera->lookAt(Vec3(eyePos.x,eyePos.y,0), Vec3(0, 1, 0));

注意,这里有个坑:camera->lookAt必须在camera->setPostion3D之后,因为lookAt中有一句

Vec3::subtract(this->getPosition3D(), lookAtPos, &zaxis),即相减得出相机空间z轴,

使用了getPosition3D。所以必须先设定好position3D再调lookAt才能得到正确结果。

参考:

http://www.cocos2d-x.org/news/344

cocos2d_tests - Camera3DTest.cpp

二,3.x与2.x相机的差别:

cocos2dx 3.x中的相机机制与cocos2dx 2.x中差别很大。

在2.x中每个节点都有camera,所以每个节点都有自己的局部view矩阵。矩阵堆栈将形如:(proj,view,model,view,model,…)

而在3.x中相机不是隶属于节点的,而是全局的,所以节点没有自己的局部view矩阵,只有一个起始的view矩阵,即矩阵堆栈将形如:(proj,view,model,model,model,…)

三,3.x相机实现原理:

前面已经看到,使用3.x相机关键有三点:

1,用户自己创建相机并指定cameraFlag。

2,为节点指定与cameraFlg按位与不为0的cameraMask,则此节点即使用此相机。

3,相机可addChild到任意一个节点(尽量使用根节点)。

自定义相机的cameraFlag可取USER1~USER8,定义如下:

enum class CameraFlag

{

DEFAULT = 1,USER1 = 1 << 1,USER2 = 1 << 2,USER3 = 1 << 3,USER4 = 1 << 4,USER5 = 1 << 5,USER6 = 1 << 6,USER7 = 1 << 7,USER8 = 1 << 8,

};

Node::setCameraMask(unsigned short mask, bool applyChildren)用来指定cameraMask,其第二个参数用来指明子节点是否递归地使用相同的mask,默认为true。要特别注意:node->setCameraMask(mask,true)只能使node的当前所有子节点的cameraMask设置为mask,但在此之后新添加的子节点则不会受影响(仍然是默认camera),需要记着手动进行设置,这里很容易被坑。又比如在Layer::init()里开头写了一句this->setCameraMask(mask,true)紧接着加了些子节点,那么要意识到这些子节点是不会被设置的,要么对每个子节点都手动调一次setCameraMask,要不就把this->setCameraMask写到init函数的末尾。

不管camera被addChild到哪个节点,其都会被添加到Scene的_cameras成员中。如果Scene中任何一个节点都没有添加用户自定义相机,则scene的_cameras成员中只含有一个默认相机,就是Scene::_defaultCamera所引用的相机;如果Scene中某些节点添加了用户自定义相机,则_cameras[0]是默认相机,其余元素是用户相机。

至于camera是如何被添加到_cameras中的,逻辑在Camera的onEnter函数中,如下:

void Camera::onEnter()

{

if (_scene == nullptr){    auto scene = getScene();    if (scene)        setScene(scene);}Node::onEnter();

}

void Camera::setScene(Scene* scene)

{

if (_scene != scene){    //remove old scene    if (_scene)    {        auto& cameras = _scene->_cameras;        auto it = std::find(cameras.begin(), cameras.end(), this);        if (it != cameras.end())            cameras.erase(it);        _scene = nullptr;    }    //set new scene    if (scene)    {        _scene = scene;        auto& cameras = _scene->_cameras;        auto it = std::find(cameras.begin(), cameras.end(), this);        if (it == cameras.end())            _scene->_cameras.push_back(this);    }}

}

下面解释3.x是如何实现相机与节点通过cameraFlag/cameraMask值进行配对儿的:

只需看Scene::render(Renderer* renderer)函数:

void Scene::render(Renderer* renderer)

{

auto director = Director::getInstance();Camera* defaultCamera = nullptr;for (const auto& camera : _cameras){    Camera::_visitingCamera = camera;    if (Camera::_visitingCamera->getCameraFlag() == CameraFlag::DEFAULT)    {        defaultCamera = Camera::_visitingCamera;        continue;    }    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix());    //visit the scene    visit(renderer, Mat4::IDENTITY, 0);    renderer->render();    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);}//draw with default cameraif (defaultCamera){    Camera::_visitingCamera = defaultCamera;    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, Camera::_visitingCamera->getViewProjectionMatrix());    //visit the scene    visit(renderer, Mat4::IDENTITY, 0);    renderer->render();    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);}Camera::_visitingCamera = nullptr;

}

上面函数逻辑看起来写得很墨迹,其实意思是:

对于这个Scene,用其_cameras中各相机分别render一遍。(不过它把默认相机的那遍render强制放到最后了,正是这个举动使代码变墨迹的)。

针对上面逻辑,需澄清下面两个问题:

问题1,关于“_cameras中各相机分别render一遍”:

问题来了,假如_cameras里有十个相机,那么Scene就要render十遍,这不坑爹吗?其实也不算很坑爹,因为每遍render只render与当前相机匹配的节点,所以总起来仍然是每个节点render一次(除非用户人为地为某节点创建了两个cameraFlg与此节点cameraMask相匹配的相机)。

从哪里可以看出每次render只render与当前相机匹配的节点呢?下面Node::visit(…)中的bool visibleByCamera = isVisitableByVisitingCamera()一句就是这个作用。

void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)

{

// quick return if not visible. children won't be drawn.if (!_visible){    return;}uint32_t flags = processParentFlags(parentTransform, parentFlags);// IMPORTANT:// To ease the migration to v3.0, we still support the Mat4 stack,// but it is deprecated and your code should not rely on itDirector* director = Director::getInstance();director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);bool visibleByCamera = isVisitableByVisitingCamera();int i = 0;if(!_children.empty()){    sortAllChildren();    // draw children zOrder < 0    for( ; i < _children.size(); i++ )    {        auto node = _children.at(i);        if ( node && node->_localZOrder < 0 )            node->visit(renderer, _modelViewTransform, flags);        else            break;    }    // self draw    if (visibleByCamera)        this->draw(renderer, _modelViewTransform, flags);    for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)        (*it)->visit(renderer, _modelViewTransform, flags);}else if (visibleByCamera){    this->draw(renderer, _modelViewTransform, flags);}director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);// FIX ME: Why need to set _orderOfArrival to 0??// Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920// reset for next frame// _orderOfArrival = 0;

}

bool Node::isVisitableByVisitingCamera() const

{

auto camera = Camera::getVisitingCamera();bool visibleByCamera = camera ? (unsigned short)camera->getCameraFlag() & _cameraMask : true;return visibleByCamera;

}

问题2,关于“把默认相机的那遍render强制放到最后”:

不知道cocos团队为何要这样做,我还由于这个被坑了一次,经过如下:

我在layer上加了一个background sprite (zOrder=0),然后又加了一个layer2 (zOrder=1),其中layer2使用了自定义相机camera2。
本想让layer2显示在background sprite前面,但显示结果却是background会挡住layer2。
开始不解,然后去看引擎代码,发现此结果是Scene::render(…)函数的逻辑造成:
Scene::render(…)的逻辑是先用自定义相机去渲染使用它的相应节点,最后再用default相机渲染使用它的相应节点。
所以导致无论zOrder怎么设,都会先渲染使用自定义相机的layer2,然后再渲染使用默认相机的background sprite。
于是为了能够先渲染background sprite再渲染layer2,我只好又定义了一个camera1,让background sprite改为使用camera1(并且保证camera1是在camera2之前加到Scene:: _cameras中,即camera1先于camera2加到场景中),这样渲染效果就正常了。

还是拿坦克展示的代码来分析,相比之前,我们加入了粒子,看起来更炫,而且打开界面之后让坦克模型自动旋转,除非手动去旋转它。

local TankShowLayer = class("TankShowLayer", function () return cc.Layer:create() end)local modelList = {    [1] = "res/model3d/1002_Cineng/1002_Cineng.c3b",    [2] = "res/model3d/1003_Weilai/1003_Weilai.c3b",    [3] = "res/model3d/1000_Tianqi/1000_Tianqi.c3b",    [4] = "res/model3d/1001_Guangleng/1001_Guangleng.c3b",    [5] = "res/model3d/1004_Yadianna/1004_Yadianna.c3b",    [6] = "res/model3d/1005_Huoyan/1005_Huoyan.c3b",}local curIndex = 1local modelParicleList = {    [1] = {            { pos = {-42, -35, 55}, name = "res/paricle3d/chibaotanke/scripts/Cshandian.pu", scale = 3.0, },            { pos = {42, -35, 55}, name = "res/paricle3d/chibaotanke/scripts/Cshandian.pu", scale = 3.0, },          },    [2] = {             { pos = {-69, -43, 116}, name = "res/paricle3d/weilaitanke/scripts/Cxishou5.pu", scale = 0.3, },            { pos = {69, -43, 116}, name = "res/paricle3d/weilaitanke/scripts/Cxishou5.pu", scale = 0.3, },          },    [3] = {},    [4] = {},    [5] = {},    [6] = {},}function TankShowLayer:ctor()    local node = cc.CSLoader:createNode("scene3d/tankShow/Scene3D.csb")    self:addChild(node) --Scene3D.csb将坦克的转盘和底座合成到了一个工程里    --由于2D会始终放在3D的上面挡住,所以背景不能用2D的精灵来做,用cc.BillBoard    --BillBoard 用于实现3D场景的公告板功能,所谓的公告板,即始终朝向相机所在点,或始终朝向相机局部坐标系的XoY平面的2D精灵    local billborad = cc.BillBoard:create("scene3d/tankShow/Scene_002.png")    billborad:setPosition3D( cc.vec3(0, 0, -200) )    billborad:setScaleX(1.05)    billborad:setCameraMask(cc.CameraFlag.USER1)--如果这里设成USER2则背景看不到了    node:addChild(billborad)    local panelNode = node:getChildByTag(7):getChildByTag(9)    panelNode:setRotation3D( cc.vec3(0, 0, 45) )    self.panelNode = panelNode    self.panelNodeSrcRotate = panelNode:getRotation3D()    local lis = cc.EventListenerTouchOneByOne:create()    lis:registerScriptHandler(function (touch, event)        self.handMakeTrun = true        return true    end,cc.Handler.EVENT_TOUCH_BEGAN )    lis:registerScriptHandler(function (touch, event)        self.handMakeTrun = false    end,cc.Handler.EVENT_TOUCH_ENDED )    lis:registerScriptHandler(function (touch, event)        local dx = touch:getDelta().x        local rotation3D = panelNode:getRotation3D()        rotation3D.z = rotation3D.z - dx * 1        panelNode:setRotation3D(rotation3D)    end, cc.Handler.EVENT_TOUCH_MOVED)    local eventDispatcher = self:getEventDispatcher()    eventDispatcher:addEventListenerWithSceneGraphPriority(lis, self)    self:addButton()    self:updateModel()    schedule(self, function() self:update() end, 0)--自动旋转endfunction TankShowLayer:update()    if not self.handMakeTrun then        local rotation3D = self.panelNode:getRotation3D()        rotation3D.z = rotation3D.z + 0.2        self.panelNode:setRotation3D(rotation3D)    endendfunction TankShowLayer:updateModel()    local modelPath = modelList[curIndex]    if self.tankSprite then        self.tankSprite:removeFromParent()    end    if self.tankParicleList then        for _, tankParicle in ipairs(self.tankParicleList) do            tankParicle:removeFromParent()        end        self.tankParicleList = {}    end    self.panelNode:setRotation3D(self.panelNodeSrcRotate)    local sprite = cc.Sprite3D:create(modelPath)    --sprite:setPosition3D( cc.vec3(0, 0, -19) )    sprite:setRotation3D( cc.vec3(0, 0, 0) )    sprite:setCameraMask(cc.CameraFlag.USER1)    self.panelNode:addChild(sprite)    self.tankSprite = sprite    -- 粒子特效    self.tankParicleList = {}    local paricleConfigList = modelParicleList[curIndex]    for _, paricleConfig in ipairs(paricleConfigList) do        local paricle = cc.PUParticleSystem3D:create(paricleConfig.name)        if paricleConfig.scale then            paricle:setScale(paricleConfig.scale)        end        local pos = paricleConfig.pos        paricle:setPosition3D( cc.vec3( pos[1], pos[2], pos[3] ) )        paricle:setCameraMask(cc.CameraFlag.USER1)        paricle:startParticleSystem()        self.panelNode:addChild(paricle)        table.insert(self.tankParicleList, paricle)     endend
0 0