一,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將使用上面創建的camera
Vec3 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 camera
if (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 it
Director* 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加到場景中),這樣渲染效果就正常了。