GVKun编程网logo

Cocos2dx使用OpenGL es建三维模型遮挡问题(cocos creator opengl)

23

最近很多小伙伴都在问Cocos2dx使用OpenGLes建三维模型遮挡问题和cocoscreatoropengl这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展cocos2d-xl

最近很多小伙伴都在问Cocos2dx使用OpenGL es建三维模型遮挡问题cocos creator opengl这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展cocos2d-x lua 屏幕适配问题(OpenGL调用),版本号(cocos2dx v3.4)、cocos2d-x OpenGL ES 坐标系总结、cocos2D-X源码分析之从cocos2D-X学习OpenGL(10)----MVP矩阵、cocos2D-X源码分析之从cocos2D-X学习OpenGL(11)----摄像机等相关知识,下面开始了哦!

本文目录一览:

Cocos2dx使用OpenGL es建三维模型遮挡问题(cocos creator opengl)

Cocos2dx使用OpenGL es建三维模型遮挡问题(cocos creator opengl)

顶点输入渲染管线时,要有顺序,这个顺序的影响之一就是遮挡问题。如下图:



例如我画一个齿轮,顶点顺序是先将齿轮正面和后面点输入,再输入边侧顶点,那么总是边侧面在前,效果是错误的。要先判断哪个面遮挡哪个面,本例中是正面和侧面遮挡边侧面,所以要先输入边侧面的点,再输入正面和背面的点,这样就正确了。

cocos2d-x lua 屏幕适配问题(OpenGL调用),版本号(cocos2dx v3.4)

cocos2d-x lua 屏幕适配问题(OpenGL调用),版本号(cocos2dx v3.4)

前言

我们知道,cocos2dx 中屏幕适配的设置方法是

Director::getInstance()->getopenGLView()->setDesignResolutionSize(960,640,kResolutionShowAll);

为了保持我们的游戏不被拉伸,选择showAll方法。但是有一个问题,showAll会留黑边,那么我们只需要在openGL中渲染黑边即可。这样黑边就会被填充为我们自己设置的图片。话不多说,看看下面的代码。

MainScene.lua

调用示例如下,只需要在onEnter里面渲染即可

function MainScene:onEnter()

    local function createSpriteWithPathPosScale(path,pos,scale)
        -- body
        local sprite = cc.Sprite:create(path)
        sprite : setAnchorPoint(cc.p(0,0))
        sprite : setPosition(pos)
        sprite : setScale(scale)
        return sprite
    end

    local layer = cp.ScreenMatchLayer : create()
    layer:retain()


    local sprite = createSpriteWithPathPosScale("ui_img_left.jpg",cc.p(0 - 88,0),1)
    layer : getChildByName("_node") : addChild(sprite)

    sprite = createSpriteWithPathPosScale("ui_img_left.jpg",cc.p(960,-1)
    layer : getChildByName("_node") : addChild(sprite)

    sprite = createSpriteWithPathPosScale("ui_img_top.jpg",cc.p(480,640 + 32.5),1)
    layer : getChildByName("_node") : addChild(sprite)

    sprite = createSpriteWithPathPosScale("ui_img_top.jpg",-480-32.5),-1)
    layer : getChildByName("_node") : addChild(sprite)
end


AppDelegate.cpp

这里面对ScreenMatchLayer进行注册以便被lua调用

bool AppDelegate::applicationDidFinishLaunching()
{
    // set default FPS
    Director::getInstance()->setAnimationInterval(1.0 / 60.0f);
   
    // register lua module
    auto engine = LuaEngine::getInstance();
    ScriptEngineManager::getInstance()->setScriptEngine(engine);
    lua_State* L = engine->getLuaStack()->getLuaState();
    lua_module_register(L);
	ScreenMatchLayer::lua_bind_AllFunction(L);

    // If you want to use Quick-Cocos2d-X,please uncomment below code
    // register_all_quick_manual(L);

    LuaStack* stack = engine->getLuaStack();
    stack->setXXTEAKeyAndSign("2dxLua",strlen("2dxLua"),"XXTEA",strlen("XXTEA"));
    
    //register custom function
    //LuaStack* stack = engine->getLuaStack();
    //register_custom_function(stack->getLuaState());
    
#if (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)
    // NOTE:Please don't remove this call if you want to debug with Cocos Code IDE
    RuntimeEngine::getInstance()->start();
    cocos2d::log("iShow!");
#else
    if (engine->executeScriptFile("src/main.lua"))
    {
        return false;
    }
#endif
    
    return true;
}



ScreenMatchLayer.h


#ifndef __SCREENMATCHLAYER_H__
#define __SCREENMATCHLAYER_H__

#include "cocos2d.h"

namespace cocos2d{

	class ScreenMatchLayer : public  Layer
	{
	public:
		ScreenMatchLayer(void);
		~ScreenMatchLayer(void);
		static ScreenMatchLayer* create();
		bool init();

		void visit(Renderer *renderer,const Mat4& parentTransform,uint32_t parentFlags);
		void onBeforeVisit();
		void onAfterVisit();

		static int  lua_bind_AllFunction(lua_State* tolua_S);
		static const std::string classprefix;
		static const std::string fullName;
		static const std::string className;
	private:
		Node* _node;
		CustomCommand _beforeVisit;
		CustomCommand _afterVisit;
	};
}

int lua_cocos2dx_ScreenMatchLayer_create(lua_State* tolua_S);
#endif

ScreenMatchLayer.cpp

#include "ScreenMatchLayer.h"
#include "tolua_fix.h"
#include "LuaBasicConversions.h"
using namespace cocos2d;

const std::string ScreenMatchLayer::classprefix = "cp";
const std::string ScreenMatchLayer::className = "ScreenMatchLayer";
const std::string ScreenMatchLayer::fullName = classprefix + "." + className;

ScreenMatchLayer::ScreenMatchLayer(void)
{
}


ScreenMatchLayer::~ScreenMatchLayer(void)
{
}

bool ScreenMatchLayer::init()
{
	if ( !Layer::init() ) {
		return false;
	}
	
	_node = Node::create();
	_node->setName("_node");
	_node->setAnchorPoint(ccp(0,0));
	addChild(_node);

	GLView* openGLView = this->_director->getopenGLView();
	Size frameSize = openGLView->getFrameSize();
	Size designSize = Size(960,640); 

	Size nodeSize = Size(0,0);

	bool frameSizeWidthLarger = (designSize.width / designSize.height) < (frameSize.width / frameSize.height );
	
	if(frameSizeWidthLarger)
	{
		nodeSize.height = designSize.height;
		nodeSize.width = nodeSize.height * (frameSize.width / frameSize.height);
	}
	else
	{
		nodeSize.width = designSize.width;
		nodeSize.height = nodeSize.width * (frameSize.height / frameSize.width);
	}
 	 _node->setPosition(ccp((nodeSize.width - designSize.width) / 2,(nodeSize.height - designSize.height) / 2));

	auto _afterDrawListener = EventListenerCustom::create(Director::EVENT_AFTER_VISIT,[this](EventCustom* event) {
			this->visit(this->_director->getRenderer(),Mat4::IDENTITY,0);
	});
	auto eventdispatcher = Director::getInstance()->getEventdispatcher();
	eventdispatcher->addEventListenerWithFixedPriority(_afterDrawListener,1);

	return true;
}

void ScreenMatchLayer::onBeforeVisit()
{
	GLView* openGLView = this->_director->getopenGLView();
	Size frameSize = openGLView->getFrameSize();
	glViewport((GLint)(0),(GLint)(0),(GLsizei)(frameSize.width),(GLsizei)(frameSize.height));
}

void ScreenMatchLayer::onAfterVisit()
{
	GLView* openGLView = this->_director->getopenGLView();
	Size designSize = openGLView->getDesignResolutionSize();
	openGLView->setViewPortInPoints(0,designSize.width,designSize.height);
}

void ScreenMatchLayer::visit(Renderer *renderer,uint32_t parentFlags)
{
	if(!_visible)
        return;

    _beforeVisit.init(_globalZOrder);
    _beforeVisit.func = CC_CALLBACK_0(ScreenMatchLayer::onBeforeVisit,this);
    renderer->addCommand(&_beforeVisit);

	Node::visit(renderer,parentTransform,parentFlags);
    
    _afterVisit.init(_globalZOrder);
    _afterVisit.func = CC_CALLBACK_0(ScreenMatchLayer::onAfterVisit,this);
    renderer->addCommand(&_afterVisit);
}

ScreenMatchLayer * ScreenMatchLayer::create()
{
	ScreenMatchLayer *pRet = new ScreenMatchLayer();
	if ( pRet && pRet->init() ) {
        pRet->autorelease();
        return pRet;
    }
    CC_SAFE_DELETE(pRet);
    return nullptr;
}

int ScreenMatchLayer::lua_bind_AllFunction(lua_State* tolua_S)
{
		lua_getglobal(tolua_S,"_G");
		if (lua_istable(tolua_S,-1))//stack:...,_G,{
			tolua_open(tolua_S);
		
			tolua_module(tolua_S,classprefix.c_str(),0);
			tolua_beginmodule(tolua_S,classprefix.c_str());

			tolua_usertype(tolua_S,ScreenMatchLayer::fullName.c_str());
			tolua_cclass(tolua_S,className.c_str(),ScreenMatchLayer::fullName.c_str(),"cc.Layer",nullptr);

			tolua_beginmodule(tolua_S,className.c_str());
				tolua_function(tolua_S,"create",lua_cocos2dx_ScreenMatchLayer_create);
			tolua_endmodule(tolua_S);
			g_luaType[typeid(ScreenMatchLayer).name()] = ScreenMatchLayer::fullName;
			g_typeCast[className] = ScreenMatchLayer::fullName;
			
			tolua_endmodule(tolua_S);
		}
		lua_pop(tolua_S,1);
	return 1;
}

int lua_cocos2dx_ScreenMatchLayer_create(lua_State* tolua_S)
{
    int argc = 0;
	ScreenMatchLayer* parentOfFunction = nullptr;
	const std::string &functionString = "'lua_cocos2dx_ScreenMatchLayer_create'";
	const std::string &luaFunctionString = ScreenMatchLayer::fullName + ":create";

#if COCOS2D_DEBUG >= 1
	tolua_Error tolua_err;
	if (!tolua_isusertable(tolua_S,1,&tolua_err))
	{
		  tolua_error(tolua_S,("#ferror in function " + functionString).c_str(),&tolua_err);
		  return 0;
	}

	parentOfFunction = (ScreenMatchLayer*)tolua_tousertype(tolua_S,0);
	 if (!parentOfFunction) 
    {
		//tolua_error(tolua_S,("invalid 'cobj' in function " + functionString).c_str(),nullptr);
        //return 0;
    }
#endif

    argc = lua_gettop(tolua_S) - 1;

    if (argc == 0)
    {
		ScreenMatchLayer* ret = ScreenMatchLayer::create();
        object_to_luaval<ScreenMatchLayer>(tolua_S,(ScreenMatchLayer*)ret);
        return 1;
    }
    else 	
	{luaL_error(tolua_S,"%s has wrong number of arguments: %d,was expecting %d \n",luaFunctionString.c_str(),argc,1);}
    return 0;

}

cocos2d-x OpenGL ES 坐标系总结

cocos2d-x OpenGL ES 坐标系总结

许多教程都说cocos2d-x OpenGL ES世界坐标系原点在左下角,但至于为什么在左下角却从来没有人提过,这导致大部分人认为这是OpenGL ES的规定,其实这是错的,OpenGL ES的坐标原点在左下角还是在屏幕中心跟投影矩阵有关,如果我们把cocos2d-x的投影矩阵设置为单位矩阵那么坐标原点会在屏幕中心。而cocos2d-x他的投影矩阵是(这里仅显示平移x,y的值:x = -size.width/2,y = -size.height/2),由此可知它的投影矩阵是把它从屏幕中心移到了左下角。实际情况OpenGL的默认坐标原点在屏幕中心。 (另外提示一下:永远不要轻易的认为(0,0)点不在屏幕左下角就一定在左上角)

cocos2D-X源码分析之从cocos2D-X学习OpenGL(10)----MVP矩阵

cocos2D-X源码分析之从cocos2D-X学习OpenGL(10)----MVP矩阵

上一篇介绍了openGL的变换和坐标系,本篇就介绍游戏引擎中一个重要的概念-MVP矩阵,首先涉及到透视投影和正交投影的概念,关于这个概念请参考之前的文章(文章地址:http://blog.csdn.net/bill_man/article/details/48199593),其中介绍了透视投影和正交投影的概念。

上一篇文章介绍了MVP矩阵的概念,但是并没有在代码上涉及。

在cocos2d-x中的GLProgram类中,有一个统一设置uniform变量的地方(如果你已经忘记了uniform变量,请翻阅之前的教程),那就是setUniformsForBuiltins函数,它会在onDraw函数调用时被调用:

auto glProgram = getGLProgram();
glProgram->use();
glProgram->setUniformsForBuiltins(transform);
注意,当你要为uniform设置值得时候,一定要保持那个着色器正被使用,而当你调用glGetUniformlocation并不需要,cocos2d-x做了一个整理统一,首先,这些经常会被使用的uniform变量,比如mvp变量:
void main()
{
    v_color = vec4(a_color.rgb * a_color.a,a_color.a);
    v_texcoord = a_texcoord;

    gl_Position = CC_MVPMatrix * a_position;
}
这种大写字母开头,并且是CC开头的,都是统一名字的uniform变量,第二,cocos2d-x把shader的处理高度的归类统一化,首先是在updateUniforms获取这些uniform变量变量,而且这个函数只在着色器创建或改变时才被调用,并且这个函数中会获得这些uniform有没有在着色器中使用,这节约了效率
    //获取uniform位置
    _builtInUniforms[UNIFORM_AMBIENT_COLOR] = glGetUniformlocation(_program,UNIFORM_NAME_AMBIENT_COLOR);
    _builtInUniforms[UNIFORM_P_MATRIX] = glGetUniformlocation(_program,UNIFORM_NAME_P_MATRIX);
    _builtInUniforms[UNIFORM_MV_MATRIX] = glGetUniformlocation(_program,UNIFORM_NAME_MV_MATRIX);
    _builtInUniforms[UNIFORM_MVP_MATRIX] = glGetUniformlocation(_program,UNIFORM_NAME_MVP_MATRIX);
    _builtInUniforms[UNIFORM_norMAL_MATRIX] = glGetUniformlocation(_program,UNIFORM_NAME_norMAL_MATRIX);

    _builtInUniforms[UNIFORM_TIME] = glGetUniformlocation(_program,UNIFORM_NAME_TIME);
    _builtInUniforms[UNIFORM_SIN_TIME] = glGetUniformlocation(_program,UNIFORM_NAME_SIN_TIME);
    _builtInUniforms[UNIFORM_COS_TIME] = glGetUniformlocation(_program,UNIFORM_NAME_COS_TIME);

    _builtInUniforms[UNIFORM_RANDOM01] = glGetUniformlocation(_program,UNIFORM_NAME_RANDOM01);

    _builtInUniforms[UNIFORM_SAMPLER0] = glGetUniformlocation(_program,UNIFORM_NAME_SAMPLER0);
    _builtInUniforms[UNIFORM_SAMPLER1] = glGetUniformlocation(_program,UNIFORM_NAME_SAMPLER1);
    _builtInUniforms[UNIFORM_SAMPLER2] = glGetUniformlocation(_program,UNIFORM_NAME_SAMPLER2);
    _builtInUniforms[UNIFORM_SAMPLER3] = glGetUniformlocation(_program,UNIFORM_NAME_SAMPLER3);
    //判断是否使用了
    _flags.usesP = _builtInUniforms[UNIFORM_P_MATRIX] != -1;
    _flags.usesMV = _builtInUniforms[UNIFORM_MV_MATRIX] != -1;
    _flags.usesMVP = _builtInUniforms[UNIFORM_MVP_MATRIX] != -1;
    _flags.usesnormal = _builtInUniforms[UNIFORM_norMAL_MATRIX] != -1;
    _flags.usesTime = (
                       _builtInUniforms[UNIFORM_TIME] != -1 ||
                       _builtInUniforms[UNIFORM_SIN_TIME] != -1 ||
                       _builtInUniforms[UNIFORM_COS_TIME] != -1
                       );
    _flags.usesRandom = _builtInUniforms[UNIFORM_RANDOM01] != -1;
因为glGetUniformlocation在找不到相应变量时会返回-1,所以这里用这个来判断uniform是否被使用。然后在setUniformsForBuiltins设置这些uniform:
    if (_flags.usesP)
        setUniformlocationWithmatrix4fv(_builtInUniforms[UNIFORM_P_MATRIX],matrixP.m,1);

    if (_flags.usesMV)
        setUniformlocationWithmatrix4fv(_builtInUniforms[UNIFORM_MV_MATRIX],matrixMV.m,1);

    if (_flags.usesMVP) {
        Mat4 matrixMVP = matrixP * matrixMV;
        setUniformlocationWithmatrix4fv(_builtInUniforms[UNIFORM_MVP_MATRIX],matrixMVP.m,1);
    }

每个本地坐标转换为裁剪坐标,都有要和MVP矩阵相乘,然后在裁剪空间中被计算成标准设备坐标,从而映射到屏幕上,cocos2d-x中Model和View矩阵一般被结合在了一起,在visit函数中被父子节点之间传递,当在这一帧内调用了设置位置,缩放,旋转等函数,就会调用getNodetoParentTransform函数修改MV矩阵。

而投影矩阵则有Director中的setProjection函数控制,在之其中会区分2D和3D做不同的处理,

    switch (projection)
    {
        case Projection::_2D:
        {
            loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);

            Mat4 orthoMatrix;
            Mat4::createOrthographicOffCenter(0,size.width,size.height,-1024,1024,&orthoMatrix);
            multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,orthoMatrix);
            loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
            break;
        }
            
        case Projection::_3D:
        {
            float zeye = this->getZEye();

            Mat4 matrixPerspective,matrixLookup;

            loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);

            // issue #1334
            Mat4::createPerspective(60,(GLfloat)size.width/size.height,10,zeye+size.height/2,&matrixPerspective);

            multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,matrixPerspective);

            Vec3 eye(size.width/2,size.height/2,zeye),center(size.width/2,0.0f),up(0.0f,1.0f,0.0f);
            Mat4::createLookAt(eye,center,up,&matrixLookup);
            multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,matrixLookup);
            
            loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
            break;
        }

        case Projection::CUSTOM:
            // Projection Delegate is no longer needed
            // since the event "PROJECTION CHANGED" is emitted
            break;

        default:
            cclOG("cocos2d: Director: unrecognized projection");
            break;
    }
可以看到2D和3D其实就是正交投影和透视投影的不同处理,createOrthographicOffCenter函数创建了一个正交投影矩阵,并把它传到最后一个参数的地址,前四个参数第一个视锥的矩形,第五个参数和第六个参数定义了近平面和远平面的大小。同样的createPerspective函数创建了一个透视投影矩阵,第一个参数是视角的角度,第二个参数是宽高比,第三个参数和第四个参数分别定义了近平面和远平面。不同的是透视投影这个矩阵的处理,下面加了一个眼睛位置,并且通过眼睛位置获得了一个矩阵,其实一般的处理里,这应该算作View矩阵的范畴,cocos2d-x把这个矩阵放在这里可能有自己的原因,或者是历史问题吧,至于这个函数中的三个参数,会在介绍摄像机时详细介绍。

下一篇介绍摄像机


能力不足,水平有限,如有错误,欢迎指出。

cocos2D-X源码分析之从cocos2D-X学习OpenGL(11)----摄像机

cocos2D-X源码分析之从cocos2D-X学习OpenGL(11)----摄像机

本篇文章介绍一个在游戏中的重要概念,在MVP矩阵中,视图矩阵和投影矩阵都和摄像机有关,说句白话,摄像机其实就是生成投影矩阵和视图矩阵的方式和原因,cocos2d-x中使用GamePlay3D类的Mat4类生成各种矩阵,一下就通过分析摄像机Camera类的代码来看这些矩阵是如何生成的。

首先来看正交矩阵的初始化代码:

bool Camera::initOrthographic(float zoomX,float zoomY,float nearPlane,float farPlane)
{
    _zoom[0] = zoomX;
    _zoom[1] = zoomY;
    _nearPlane = nearPlane;
    _farPlane = farPlane;
    Mat4::createOrthographicOffCenter(0,_zoom[0],_zoom[1],_nearPlane,_farPlane,&_projection);
    _viewProjectionDirty = true;
    _frustumDirty = true;
    
    return true;
}
正交投影就是没有近大远小概念的投影,它的视锥体就是矩形,所以定义它的时候只要定义视口宽高和近平面以及远平面就可以了。调用Mat4的

createOrthographicOffCenter生成投影矩阵把它传给_projection。

bool Camera::initPerspective(float fieldOfView,float aspectRatio,float farPlane)
{
    _fieldOfView = fieldOfView;
    _aspectRatio = aspectRatio;
    _nearPlane = nearPlane;
    _farPlane = farPlane;
    Mat4::createPerspective(_fieldOfView,_aspectRatio,&_projection);
    _viewProjectionDirty = true;
    _frustumDirty = true;
    
    return true;
}
而透视投影的初始化方式类似,参数分别为:视角角度,宽高比,近平面,远平面,最后函数会把投影矩阵传递给_projection。
定义一个摄像机,需要摄像机位置,观察方向,还有一个指向它右侧的向量,以及一个它上方的向量,这实际上是一个以摄像机位置为原点的坐标系,如图所示:

所以说在创建摄像机以后,就要定义它的位置和lookAt

_camera->setPosition3D(Vec3(0,130,130) + _sprite3D->getPosition3D());
_camera->lookAt(_sprite3D->getPosition3D());
lookAt定义了观察目标位置和摄像机向上位置,如果不传参数第二个参数就默认为y轴方向

在lookAt中,我们定义了lookAt矩阵,也就是视图矩阵,至于视图矩阵的推导,可以参考这篇文章:http://www.cnblogs.com/mincomp/archive/2012/09/06/2672888.html,这里我们只需要知道这个结果就可以:

R为右向量,U为上向量,D为方向向量,它们的计算过程就在lookAt函数中:

    //向上向量
    Vec3 upv = up;
    upv.normalize();
    //摄像机方向
    Vec3 zaxis;
    Vec3::subtract(this->getPosition3D(),lookAtPos,&zaxis);
    zaxis.normalize();
    //右轴
    Vec3 xaxis;
    Vec3::cross(upv,zaxis,&xaxis);
    xaxis.normalize();
    //上轴
    Vec3 yaxis;
    Vec3::cross(zaxis,xaxis,&yaxis);
    yaxis.normalize();
    //lookAt矩阵
    Mat4  rotation;
    rotation.m[0] = xaxis.x;
    rotation.m[1] = xaxis.y;
    rotation.m[2] = xaxis.z;
    rotation.m[3] = 0;
    
    rotation.m[4] = yaxis.x;
    rotation.m[5] = yaxis.y;
    rotation.m[6] = yaxis.z;
    rotation.m[7] = 0;
    rotation.m[8] = zaxis.x;
    rotation.m[9] = zaxis.y;
    rotation.m[10] = zaxis.z;
    rotation.m[11] = 0;
注意,这里的摄像机方向实际上是摄像机位置向量指向它看着的方向的相反方向,然后下面用向量的叉乘获得右轴和上轴,从而获得lookAt矩阵所需要的值,这些计算的原理可以参考:https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process

下一篇将介绍天空盒的原理


能力不足,水平有限,如有问题,欢迎指出。

关于Cocos2dx使用OpenGL es建三维模型遮挡问题cocos creator opengl的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于cocos2d-x lua 屏幕适配问题(OpenGL调用),版本号(cocos2dx v3.4)、cocos2d-x OpenGL ES 坐标系总结、cocos2D-X源码分析之从cocos2D-X学习OpenGL(10)----MVP矩阵、cocos2D-X源码分析之从cocos2D-X学习OpenGL(11)----摄像机的相关信息,请在本站寻找。

本文标签: