概述
Cocos2d摄像机详解
摄像机的作用
在3D系统中都会有一个摄像机的概念,物体在显示之前需要先将物体的坐标转换到视角坐标,也就是摄像机坐标,然后再投影,最后还需对投影画面进行缩放到视口显示的大小。Coscos2d-x 3.x支持3D的,所以其肯定会有摄像机。在Cocos中摄像机由类Camera实现,主要作用有3个:1、设置视口,2、将全局坐标转换到摄像机坐标中,3、投影。
设置视口
视口,就是显示的位置,如果是显示在屏幕上,视口则是指在屏幕上显示的矩形区域。设置视口作用是设置渲染结果最终的显示方式、显示位置以及显示大小。渲染结果可以显示在屏幕的窗口上,也可以写入帧缓存中,Camera类支持这两种方式。摄像机默认情况显示到窗口,可以通过函数Camera::setFrameBufferObject设置渲染到帧缓存。视口位置和大小决定可最后投影生成的图像显示的位置和大小,所有显示到窗口的摄像机都使用同一个视口static Viewport Camera::_defaultViewport,可以通过函数static Camera::setDefaultViewport设置,帧缓存的视口可以通过函数Camera::setViewport设置。
Cocos也支持场景多视口显示,可以显示一个物体的多个视角,例如同时显示物体的正面视图和俯视图。
摄像机坐标
物体投影显示之前需要转换到摄像机坐标,这个功能在Cocos中实现还是比较简单的。在Cocos中每个继承至Node的UI节点都有局部坐标系,摄像机坐标系本身也只是一个局部坐标系,所以Camera只需要继承Node节点就可以实现该功能。具体坐标转换解释和原理请参考:Cocos2d-x 坐标系统详解。
为了更直观的设置摄像机,可以函数Camera::lookAt设置摄像机的方向。
投影
投影的作用是将3D物体,转换成2D坐标,转换后物体x,y坐标值都会压缩在[-1.0, 1.0]。显示的时候会将坐标拉伸到视口大小显示。
投影的方式有两种,一种是物体离摄像机越远越小,这种为透视投影,另一中是,远近物体都一样大的正交投影,默认情况下摄像机使用的是透视投影。
透视投影可以使用函数Camera::initPerspective设置。
正交投影可以使用函数Camera::initOrthographic设置。
2D显示
Cocos中使用Z=0平面显示2D元素,2D精灵、菜单、Label等z轴的值都为0。为了防止2D元素投影后图像会被拉伸或压缩,当视口宽高为w,h时,会将z=0平面的物体位置在(-w/2, -h/2)投影为(-1, -1),(w/2, h/2)投影为(1, 1) ,摄像机的位置为(w/2, h/2)投影后为(0, 0)。以下是默认摄像机初始化代码:
bool Camera::initDefault() { auto size = Director::getInstance()->getWinSize(); //create default camera auto projection = Director::getInstance()->getProjection(); switch (projection) { ......
case Director::Projection::_3D: { float zeye = Director::getInstance()->getZEye(); initPerspective(60, (GLfloat)size.width / size.height, 10, zeye + size.height / 2.0f); Vec3 eye(size.width/2, size.height/2.0f, zeye), center(size.width/2, size.height/2, 0.0f), up(0.0f, 1.0f, 0.0f); setPosition3D(eye); lookAt(center, up); break; } default: CCLOG("unrecognized projection"); break; } return true; } |
注意代码initPerspective的参数
float zeye = Director::getInstance()->getZEye(); initPerspective(60, (GLfloat)size.width / size.height, 10, zeye + size.height / 2.0f); Vec3 eye(size.width/2, size.height/2.0f, zeye), setPosition3D(eye); |
Zeye的值如下
float Director::getZEye(void) const { return (_winSizeInPoints.height / 1.154700538379252f);//(2 * tanf(M_PI/6)) } |
从代码中也可以看到近裁平面和远裁平面分别为10和zeye + size.height / 2.0f。这里可以看出默认情况下Cocos的3D显示超过2D平面后面size.height / 2.0f位置的内容将被裁剪。
使用摄像机
渲染与摄像机
Camera类本身是一个Node节点,可以通过addChild加入到场景树中任何节点上。每个摄像机都属于一个Scene,存放在变量 Scene* Camera::_scene中,在Scene类中存放着一个数组std::vector<Camera*> Scene::_cameras,存放了Scene下所有的摄像机。
当场景第一次渲染时会调用所有节点的Node::onEnter函数,Camera类的OnEnter函数中会寻摄像机所属的场景,让还将摄像机加入到场景摄像机数组中。代码如下:
void Camera::onEnter() { if (_scene == nullptr) { auto scene = getScene(); if (scene) { setScene(scene); } } Node::onEnter(); } |
Cocos的渲染都是在函数Scene::render中,在Scene::render中会为当前场景中的每个摄像机执行一次渲染。
void Scene::render(Renderer* renderer, const Mat4* eyeTransforms, const Mat4* eyeProjections, unsigned int multiViewCount)
{
auto director = Director::getInstance();
Camera* defaultCamera = nullptr;
for (const auto& camera : getCameras())
{
if (!camera->isVisible())
continue;
Camera::_visitingCamera = camera;
if (Camera::_visitingCamera->getCameraFlag() == CameraFlag::DEFAULT)
{
defaultCamera = Camera::_visitingCamera;
}
for (unsigned int i = 0; i < multiViewCount; ++i) {
if (eyeProjections)
camera->setAdditionalProjection(eyeProjections[i] * camera->getProjectionMatrix().getInversed());
if (eyeTransforms)
camera->setAdditionalTransform(eyeTransforms[i].getInversed());
director->pushProjectionMatrix(i);
director->loadProjectionMatrix(Camera::_visitingCamera->getViewProjectionMatrix(), i);
}
camera->apply();
//clear background with max depth
camera->clearBackground();
//visit the scene
visit(renderer, transform, 0);
renderer->render();
camera->restore();
for (unsigned int i = 0; i < multiViewCount; ++i)
director->popProjectionMatrix(i);
}
………
Camera::_visitingCamera = nullptr;
}
每个摄像机渲染时还有有一个multiViewCount循环,这里是设置视口矩阵,多次是因为可以设置多个视口,默认情况下这个循环只执行一次。循环中最后两行代码:
director->pushProjectionMatrix(i);
director->loadProjectionMatrix(Camera::_visitingCamera->getViewProjectionMatrix(), i);
这两行代码时将摄像机的投影视图矩阵(投影矩阵和视图矩阵相乘的结果)压入到导演类的栈中。
在执行渲染命令时,会从栈中取出该值,设置Uniform值,具体函数为GLProgram::setUniformsForBuiltins,代码如下:
void GLProgram::setUniformsForBuiltins(const Mat4 &matrixMV) { const auto& matrixP = _director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
if (_flags.usesP) setUniformLocationWithMatrix4fv(_builtInUniforms[UNIFORM_P_MATRIX], matrixP.m, 1);
……
if (_flags.usesMVP) { Mat4 matrixMVP = matrixP * matrixMV; setUniformLocationWithMatrix4fv(_builtInUniforms[UNIFORM_MVP_MATRIX], matrixMVP.m, 1); } …… }
|
摄像机掩码
每个摄像机都可以设置一个CameraFlag,这是一个位枚举,
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, }; |
UI节点可以通过函数Node::setCameraMask设置摄像机掩码,可以使用或运算(“|”)同时设置多个掩码,这样可以同时在多个摄像机下可以,从而绘制多次。
绘制时可以将3D物体全部使用一个掩码,例如CameraFlag::USER1,然后单独设置一个摄像机并设置标识为CameraFlag::USER1,这样3D显示和2D显示可以分别使用两个摄像机控制,这样可以更容易的控制3D和2D的绘制。
摄像机使用步骤
1、创建摄像机,通过函数Camera::create()创建
2、设置摄像机位置,方向,视角(投影),分别通过函数Node::setPosition、Camera::lookAt、Camera::initPerspective设置。
3、设置摄像机表示,使用函数Camera::setCameraFlag
4、将摄像机添加到场景中。
3D透明
3D物体在绘制的时候使用深度缓存进行遮挡测试,无需关心先后绘制,所以不需要使用localZOrder和_globalOrder排序。但是3D物体如果有透明,只有按照物体在摄像机坐标系下Z轴由小到大绘制才可以正确显示透明。Cocos在绘制透明3D物体时,会生成一个_depth值,这个值代表了物体摄像机坐标系下z轴的值,绘制前,会使用该值对命令进行排序。
生成这个_depth值会使用到函数Camera::getDepthInView,代码如下:
float Camera::getDepthInView(const Mat4& transform) const
{
Mat4 camWorldMat = getNodeToWorldTransform();
const Mat4 &viewMat = camWorldMat.getInversed();
float depth = -(viewMat.m[2] * transform.m[12] + viewMat.m[6] * transform.m[13] + viewMat.m[10] * transform.m[14] + viewMat.m[14]);
return depth;
}
函数返回值depth是transform最后一列作为一个点,只进行位移转换到摄像机坐标系下,z轴的值。
绘制命令排序有如下代码:
std::stable_sort(std::begin(_commands[QUEUE_GROUP::TRANSPARENT_3D]),
std::end(_commands[QUEUE_GROUP::TRANSPARENT_3D]), compare3DCommand);
static bool compare3DCommand(RenderCommand* a, RenderCommand* b)
{
return
a->getDepth() > b->getDepth();
}
最后
以上就是舒服豌豆为你收集整理的Cocos2d摄像机详解Cocos2d摄像机详解的全部内容,希望文章能够帮你解决Cocos2d摄像机详解Cocos2d摄像机详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复