Cocos2d-x 3.x 图形学渲染系列七

原创 2017年01月07日 13:44:19

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;

已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社 和《Unity3D实战核心技术详解》电子工业出版社等。

3D核心模块是Cocos2D-x擎中非常重要的,并且是与开发者交互最多的模块,游戏大部分逻辑的编写都与3D模块有关。先把该3D核心模块的架构展示如下图:


图中列出了3D核心模块的各个子模块组成,接下来把游戏编写逻辑经常使用的子模块给读者介绍,文中没有介绍到的子模块开发者可自行学习。

游戏中常用的天空盒一般分为三类:立方体、半球体、以及动态生成的天空盒,对于程序实现的难易度来说,立方体天空盒实现起来最容易,而Cocos2D-x引擎实现的就是立方体天空盒。CCSkybox子模块的实现原理是:使用六张贴图围成一个立方体盒子,官方提供了实现立方体天空盒的Demo这六张贴图在Cocos2D-x引擎的Resources文件下有个sky文件夹,在其中有六张贴图,六张贴图之间是无缝拼接的,它的制作可以通过PhotoShop工具实现的,下面通过代码介绍它的实现过程,引擎创建天空盒函数如下所示:

Skybox* Skybox::create(const std::string& positive_x, const std::string& negative_x,
const std::string& positive_y, const std::string& negative_y,
const std::string& positive_z, const std::string& negative_z)
{
	auto ret = new (std::nothrow) Skybox();
	ret->init(positive_x, negative_x, positive_y, negative_y, positive_z, negative_z);

	ret->autorelease();
	return ret;
}

开发者写逻辑时只需要调用该函数即可,在函数中设置了六个字符串参数用于加载六副贴图的路径,函数首先做的事情是初始化六张贴图,函数调用了Skybox类的init函数初始化,为了弄清楚它的实现,继续深入代码查看,init函数实现如下所示:

bool Skybox::init(const std::string& positive_x, const std::string& negative_x,
const std::string& positive_y, const std::string& negative_y,
const std::string& positive_z, const std::string& negative_z)
{
	auto texture = TextureCube::create(positive_x, negative_x, positive_y, 	negative_y, positive_z, negative_z);
	if (texture == nullptr)
	return false;

	init();
	setTexture(texture);
	return true;
}

init函数中调用了TextureCube类的Create函数用于生成立方体纹理,创建立方体天空盒。再进入到TextureCube类的内部函数实现如下所示:

TextureCube* TextureCube::create(const std::string& positive_x, const std::string& negative_x,const std::string& positive_y, const std::string& negative_y,
const std::string& positive_z, conststd::string& negative_z)
{
	auto ret = new (std::nothrow) TextureCube();
	if (ret && ret->init(positive_x, negative_x, positive_y, negative_y, positive_z, 	negative_z))
    {
        ret->autorelease();
		return ret;
    }
	CC_SAFE_DELETE(ret);
	return nullptr;
}

create函数中继续调用init函数进行初始化,在这里也是告诉读者,引擎各个模块类的代码实现方式是类似的,这需要制定一个架构供引擎开发者按照这个框架模式编写代码,而不是每个人根据自己的想法随意搞一套,导致引擎的代码不利于维护,继续分析init函数内容如下所示。

bool TextureCube::init(const std::string& positive_x, const std::string& negative_x,
const std::string& positive_y, const std::string& negative_y,
const std::string& positive_z, const std::string& negative_z)
{
	_imgPath[0] = positive_x;
	_imgPath[1] = negative_x;
	_imgPath[2] = positive_y;
	_imgPath[3] = negative_y;
	_imgPath[4] = positive_z;
	_imgPath[5] = negative_z;

	std::vector<Image*> images(6);

	images[0] = createImage(positive_x);
	images[1] = createImage(negative_x);
	images[2] = createImage(positive_y);
	images[3] = createImage(negative_y);
	images[4] = createImage(positive_z);
	images[5] = createImage(negative_z);

	GLuint handle;
	glGenTextures(1, &handle);

	GL::bindTextureN(0, handle, GL_TEXTURE_CUBE_MAP);

	for (int i = 0; i <6; i++)
    {
		Image* img = images[i];

		Texture2D::PixelFormat  ePixelFmt;
		unsigned char*   pData = getImageData(img, ePixelFmt);
		if (ePixelFmt == Texture2D::PixelFormat::RGBA8888 || ePixelFmt == 				Texture2D::PixelFormat::DEFAULT)
        {
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
			0,   // 等级
			GL_RGBA,   // 内部格式
                         img->getWidth(),    // 宽
                         img->getHeight(),   // 高
			0,   // 边界
			GL_RGBA,   // 格式
			GL_UNSIGNED_BYTE,   // 类型
            pData);   // 像素数据
        }
		else if (ePixelFmt == Texture2D::PixelFormat::RGB888)
        {
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
			0,   // 等级
			GL_RGB,   // 内部格式
            img->getWidth(),    // 宽
            img->getHeight(),   // 高
			0,   // 边界
			GL_RGB,   // 格式
			GL_UNSIGNED_BYTE,   // 类型
            pData);   //像素数据
        }

	if (pData != img->getData())
		delete[] pData;
    }

	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, 	GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, 	GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, 	GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, 	GL_CLAMP_TO_EDGE);

	_name = handle;

	GL::bindTextureN(0, 0, GL_TEXTURE_CUBE_MAP);

	for (auto img: images)
    {
		CC_SAFE_RELEASE(img);
    }

	return true;
}

函数的实现是将加载到的六张贴图通过OpenGL将它们拼接并绘制出来。在绘制天空盒时,需要设置贴图格式,从而避免贴图之间有较大的缝隙。

函数是在引擎内部实现的天空盒绘制,除了C++的编写,还需要在Shader中对顶点着色器和片段着色器的编写。在实际逻辑编写时,可充分利用Shader编程实现天空盒,在Cocos2d-x引擎中有顶点着色器和片段着色器用于天空盒的创建。以下是引擎实现天空盒的Shader代码,首先介绍天空盒的顶点着色器。文件cube_map.vert是天空盒顶点着色器,内容如下所示:

attribute vec4 a_position;
attribute vec3 a_normal;

varying vec3 v_reflect;

void main(void)
{
	gl_Position = CC_MVPMatrix * a_position;

	// 计算反射
	vec4 positionWorldViewSpace = CC_MVMatrix * a_position;;
	vec3 vEyeVertex     = normalize(positionWorldViewSpace.xyz);

	vec3 v_normalVector = CC_NormalMatrix * a_normal;
v_reflect           = normalize(reflect(-vEyeVertex, v_normalVector));
}

顶点着色器主要是对天空盒立方体点的变换,将天空盒立方体的点通过模型、视口、投影矩阵将它们转换到投影空间,还需要计算摄像机朝向天空盒的方向,以及计算它们的反射,对于计算得到的反射参数,会传递给片段着色器文件中。文件cube_map.frag是天空盒的片段着色器,内容如下所示:

#ifdef GL_ES
precision mediump float;
#endif

varying vec3 v_reflect;
uniform samplerCube u_cubeTex;
uniform vec4 u_color;

void main(void)
{
	gl_FragColor = textureCube(u_cubeTex, v_reflect) * u_color;
}

着色器通过textureCube函数计算获取颜色值,它的计算使用了v_reflect数,它是通过顶点着色器传递过来的。以上两个Shader文件实现了天空盒的顶点着色器和片段着色器代码,接下来告诉读者如何在编写逻辑时调用该Shader代码,以下代码块是创建天空盒代码如下所示。

// 创建天空盒
//创建和设置自定义Shader
auto shader = GLProgram::createWithFilenames("Sprite3DTest/cube_map.vert",
"Sprite3DTest/cube_map.frag");
auto state = GLProgramState::create(shader);

// 创建天空盒纹理图片
_textureCube = TextureCube::create("Sprite3DTest/skybox/left.jpg",
"Sprite3DTest/skybox/right.jpg",
"Sprite3DTest/skybox/top.jpg",
"Sprite3DTest/skybox/bottom.jpg",
"Sprite3DTest/skybox/front.jpg",
"Sprite3DTest/skybox/back.jpg");
//设置纹理参数
Texture2D::TexParams tRepeatParams;
    tRepeatParams.magFilter = GL_LINEAR;
    tRepeatParams.minFilter = GL_LINEAR;
    tRepeatParams.wrapS = GL_MIRRORED_REPEAT;
    tRepeatParams.wrapT = GL_MIRRORED_REPEAT;
	_textureCube->setTexParameters(tRepeatParams);

// 把纹理取样传递给上面编写的Shader
    state->setUniformTexture("u_cubeTex", _textureCube);

	// 实现天空盒,同时设置其放大缩小。
	_skyBox = Skybox::create();
	_skyBox->setCameraMask(s_CM[LAYER_BACKGROUND]);
	_skyBox->setTexture(_textureCube);
	_skyBox->setScale(700.f);

实现思路:先加载天空盒的渲染Shader文件,再加载天空盒的六张贴图,并将其赋值给_textureCube传递给片段着色器中功能,为了方便其他程序调用,可将上述代码块封装在一个函数中专用于天空盒的创建实现,下面给读者展示官方提供的天空盒丝线效果图:



接下来再介绍创建球状天空盒,相对立方体天空盒来说比较繁琐,通常的做法是美术制作一个半球状天空盒,当然也可以通过程序实现,在这里把实现的效果给读者展示如下图



以上是关于天空盒的分享,希望对你有所帮助吧。


版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

我为什么放弃java学习Kotlin?

Kotlin简介Kotlin早已是Android开发中的热门话题了,github上越来越多的项目是用Kotlin开发。但是这门语言一直处于很尴尬的境地,一方面自己不遗余力的挖掘自己在Android开发...

Unity3D 关于模型变形技术代码实现

本篇博客给读者介绍关于如何实现模型的变形,在项目开发中经常会涉及到模型的变形操作,比如如下效果图: 第一部分准备工作 首先在Unity中建立一个场景,在场景中放置一个球体,这个球体可以使用Max工具建...

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

百行代码打造高级联动特效

前两天突然看到一个联动效果蛮不错的,虽然不知道具体什么地方会用到。不过也随手鲁了一个。效果如下图: 效果是不是挺好玩的~~。那么让我们接下来一步步的分析一下。思路首先,让我们想象一下如何实现?自定义...

大数据竞赛平台——Kaggle 入门

大数据竞赛平台——Kaggle 入门篇 这篇文章适合那些刚接触Kaggle、想尽快熟悉Kaggle并且独立完成一个竞赛项目的网友,对于已经在Kaggle上参赛过的网友来说,大可不必耗费时间阅读本文。...

阿里面试回来,想和Java程序员谈一谈

引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来。LZ自己当初面试完以后,除了记住一些聊过的知识点以外,具体的内...

【机器学习算法实现】kNN算法__手写识别——基于Python和NumPy函数库

kNN算法,即K最近邻(k-NearestNeighbor)分类算法,是最简单的机器学习算法之一,算法思想很简单:从训练样本集中选择k个与测试样本“距离”最近的样本,这k个样本中出现频率最高的类别即作...

Unity3D优化技巧系列二

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》...

小公司程序员怎么进大公司

进了小公司的程序员如何翻身进入大公司?这里有四种方式。
  • foruok
  • foruok
  • 2017-07-10 06:51
  • 12473

【干货】Kaggle 数据挖掘比赛经验分享(mark 专业的数据建模过程)

简介Kaggle 于 2010 年创立,专注数据科学,机器学习竞赛的举办,是全球最大的数据科学社区和数据竞赛平台。笔者从 2013 年开始,陆续参加了多场 Kaggle上面举办的比赛,相继获得了 Cr...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)