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

Posted 海洋_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cocos2d-x 3.x 图形学渲染系列二十七相关的知识,希望对你有一定的参考价值。

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

CSDN视频网址:http://edu.csdn.net/lecturer/144

笔者以前在游戏公司开发大型MMOARPG游戏时,游戏中使用的模型为了防止产品发布后被破解,程序再做模型导出插件时对模型进行了加密处理。针对模型加密的方式非常多,通常的做法是通过已经编写的模型插件方式对其加密,类似AutoDesk提供的FBX模型插件,它的内容是二进制的。在Cocos2d-x引擎中的模型也做了加密处理,它就是c3b模型文件。模型实现过程是通过工具fbx-conv转化FBX模型文件得到的,这说明模型之间是相通的。c3b模型文件也是二进制的,本章给读者解密Cocos2d-x引擎使用的c3b模型文件如何生成二进制加密格式的。

本节真正涉及到模型文件的加密处理,加密的处理方法非常多:一种是对其内容进行二进制加密;还有就是对模型文件开头、中间、结尾等增加二进制标示加密。c3b模型本身是二进制的,然后再在文件开头处进行文件标示加密,加密方式是通过自定义的字符串,在Cocos2d-x引擎中可查看到读取c3b模型文件代码段,截取引擎中加密的代码段如下所示:

// 读取文件标示
    char identifier[] = { 'C', '3', 'B', '\\0'};
    char sig[4];
    if (_binaryReader.read(sig, 1, 4) != 4 || memcmp(sig, identifier, 4) != 0)
    {
        clear();
        CCLOG("warning: Invalid identifier: %s", path.c_str());
        return false;
    }

这段代码作用是加载模型时需要验证的加密标示,现在打开一个c3b模型文件给读者展示就一目了然了。文件内容如下图:



在第一行有三个字母“C3B”,这就是在模型中加的加密代码标示,加密的字符串,是通过工具fbx-conv转化时生成的,下面给读者进行加密模块的代码编写。

下面开始Cocos2d-x引擎加密工具的编写,相对来说这个模块架构比较简单,整个工程代码存放网址是:

https://github.com/cocos2d-x/fbx-conv,代码架构图如下:



除了上图所列的模块外,Fbx-conv还包括三个子模块:FbxConv模块、FbxConvCommand模块、Setting模块,三个子模块的功能是围绕工具的界面设置的,读者首先打开Cocos2d-x引擎中的tools文件夹,在tools文件夹下有fbx-conv文件夹在该文件夹中找到fbx-conv工具,接下来运行fbx-conv工具,首先在Windows中打开“运行”窗口并且输入cmd,然后把fbx-conv.exe拖入进去,点击回车键,效果如下窗口:



另外在Mac系统,打开终端,把fbx-conv执行程序拖到其中,按回车键,显示效果如下:



可以看到两个窗口中都出现了许多命令行,这些命令行是在一个类里面通过代码设置的,完整代码如下所示:

#ifndef FBXCONVCOMMAND_H
#define FBXCONVCOMMAND_H

//#define ALLOW_INPUT_TYPE
#include "Settings.h"
#include <string>
#include "log/log.h"

namespace fbxconv {

struct FbxConvCommand {
	constint argc;
	constchar **argv;
	int error;
	bool help;
	Settings *settings;
	log::Log *log;

	FbxConvCommand(log::Log *log, constint&argc, constchar** argv, Settings *settings)
		: log(log), argc(argc), argv(argv), settings(settings), error(log::iNoError) {
		help = (argc <= 1);

		settings->flipV = false;
		settings->packColors = false;
		settings->verbose = false;
		settings->maxNodePartBonesCount = 40;
		settings->maxVertexBonesCount = 4;
        settings->forceMaxVertexBoneCount = true;
		settings->maxVertexCount = (1<<15)-1;
		settings->maxIndexCount = (1<<15)-1;
		settings->outType = FILETYPE_C3B;
		settings->inType = FILETYPE_AUTO;
        settings->needReusableMesh = true;
        settings->normalizeVertexNormal = false;
        settings->exportPart = EXPORT_PART_ALL;
        settings->normalMap = false;
        settings->compressLevel = COMPRESS_LEVEL_DEFAULT;

		for (int i = 1; i < argc; i++) {
			constchar *arg = argv[i];
			constint len = (int)strlen(arg);
			if (len >1&& arg[0] == '-') {
				if (arg[1] == '?')
					help = true;
				elseif (arg[1] == 'f')
					settings->flipV = true;
				elseif (arg[1] == 'v')
					settings->verbose = true;
	
				elseif (arg[1] == 'g')
					settings->needReusableMesh = false;
				elseif (arg[1] == 'r')
                    settings->normalizeVertexNormal = true;
				elseif ((arg[1] == 'n') && (i + 1< argc))
					settings->maxNodePartBonesCount = atoi(argv[++i]);
				elseif ((arg[1] == 'm') && (i + 1< argc))
					settings->maxVertexCount = settings->maxIndexCount = 				atoi(argv[++i]);
				elseif((arg[1] == 'c') && (i + 1< argc))
                    settings->compressLevel = 				(COMPRESS_LEVEL)atoi(argv[++i]);
				elseif(arg[1] == 'b')
					settings->outType = FILETYPE_C3B;
				elseif(arg[1] == 't')
					settings->outType = FILETYPE_C3T;
				elseif(arg[1] == 'a')
					settings->outType = FILETYPE_ALL;
				elseif(arg[1] == 'l')
                    settings->exportPart = EXPORT_PART_MODEL;
				elseif(arg[1] == 'j')
                    settings->exportPart = EXPORT_PART_ANIMATION;
				elseif(arg[1] == 'p')
                    settings->normalMap = true;
				else
					log->error(error = log::eCommandLineUnknownOption, arg);
			}
			elseif (settings->inFile.length() <1)
				settings->inFile = arg;
			elseif (settings->outFile.length() <1)
				settings->outFile = arg;
			else
				log->error(error = log::eCommandLineUnknownArgument, arg);
			if (error != log::iNoError)
				break;
		}
		if (!help&&error == log::iNoError)
			validate();
	}

	void printCommand() const {
		for (int i = 1; i <argc; i++) {
			if (i >1)
				printf(" ");
			printf(argv[i]);
		}
		printf("\\n");
	}

	void printHelp() const {
printf("\\nUsage: fbx-conv [options] <input>\\n");
printf("\\nExample: fbx-conv -a xx.fbx \\n");
printf("\\n");
printf("Options:\\n");
printf("-?       : Display this help information.\\n");
printf("-f       : Flip the V texture coordinates.\\n");
printf("-p       : Export tagent binormal data.\\n");
printf("-m <size>: The maximum amount of vertices or indices a mesh may contain (default: 32k)\\n");
printf("-n <size>: The maximum amount of bones a nodepart can contain (default: 40)\\n");
printf("-v       : Verbose: print additional progress information\\n");
printf("-g       : Merge the mesh which have same vertex attribute\\n");
printf("-a       : Export l3b(binary) and c3t(text)\\n");
printf("-b       : Export l3b(binary)\\n");
printf("-t       : Export c3t(text)\\n");
printf("-c <size>: The compression level: 0 , 1 (default: 0)\\n");
printf("-l       : Export model data only.\\n");
printf("-j       : Export animation data only.\\n");
printf("\\n");
printf("<input>  : The filename of the file to convert.\\n");
	}
private:
void validate() {
if (settings->inFile.empty()) {
	log->error(error = log::eCommandLineMissingInputFile);
	return;
        }
#ifdef ALLOW_INPUT_TYPE
		if (inType == FILETYPE_AUTO)
			inType = guessType(inFile, FILETYPE_IN_DEFAULT);
#else
		settings->inType = FILETYPE_IN_DEFAULT;
#endif
		if (settings->outFile.empty())
			setExtension(
				settings->outFile = settings->inFile, 
				settings->outType = (settings->outType == FILETYPE_AUTO ? FILETYPE_OUT_DEFAULT : settings->outType));
		elseif (settings->outType == FILETYPE_AUTO)
			settings->outType = guessType(settings->outFile);
		if (settings->maxVertexBonesCount<0 || settings->maxVertexBonesCount>8) {
			log->error(error = log::eCommandLineInvalidVertexWeight);
				return;
		}
		if (settings->maxNodePartBonesCount<settings->maxVertexBonesCount) {
			log->error(error = log::eCommandLineInvalidBoneCount);
				return;
		}
		if (settings->maxVertexCount<0 || settings->maxVertexCount> (1<<15)-1) {
			log->error(error = log::eCommandLineInvalidVertexCount);
			return;
		}
	if (settings->compressLevel<COMPRESS_LEVEL_DEFAULT || settings->compressLevel>= COMPRESS_LEVEL_NUM) {
			log->error(error = log::eCommandLineUnknownCompressLevel, (COMPRESS_LEVEL_NUM - 1));
			return;
		}
	}

	int parseType(constchar* arg, constint&def = -1) {
		if (stricmp(arg, "fbx")==0)
			returnFILETYPE_FBX;
		elseif (stricmp(arg, "g3db")==0)
			returnFILETYPE_G3DB;
		elseif (stricmp(arg, "g3dj")==0)
			returnFILETYPE_G3DJ;
		elseif (stricmp(arg, "c3b")==0)
			returnFILETYPE_G3DB;
		elseif (stricmp(arg, "c3t")==0)
			returnFILETYPE_G3DJ;
		if (def <0)
			log->error(error = log::eCommandLineUnknownFiletype, arg);
		return def;
	}

	int guessType(conststd::string&fn, constint&def = -1) {
		int o = (int)fn.find_last_of('.');
		if (o == std::string::npos)
			return def;
		std::string ext = fn.substr(++o, fn.length() - o);
		returnparseType(ext.c_str(), def);
	}

	void setExtension(std::string&fn, conststd::string&ext) const {
		int o = (int)fn.find_last_of('.');
		if (o == std::string::npos)
			fn += "." + ext;
		else
			fn = fn.substr(0, ++o) + ext;
	}

	void setExtension(std::string&fn, constint&type) const {
		switch(type) {
			caseFILETYPE_FBX:	returnsetExtension(fn, "fbx");
			caseFILETYPE_G3DB:	returnsetExtension(fn, "c3b");
			caseFILETYPE_G3DJ:	returnsetExtension(fn, "c3t");
			default:			returnsetExtension(fn, "");
		}
	}
};

}

这个类主要作用是提供操作命令行设置,用户根据这些设置导出含有不同文件内容的模型文件,比如如果在模型中增加切向量坐标和法线坐标,可以通过在命令行中加入-p指令即可。接下来就是在窗口根据不同的指令执行不同的操作了,通过命令行窗口也可以完成FBX模型的加载以及c3b模型的转化保存,同时可以输出操作的日志信息,完整代码如下所示:

#ifdef _MSC_VER
#pragma once
#endif //_MSC_VER
#ifndef FBXCONV_FBXCONV_H
#define FBXCONV_FBXCONV_H

#ifndef BUILD_NUMBER
#define BUILD_NUMBER "0000"
#endif

#ifndef BUILD_ID
#ifdef DEBUG
#define BUILD_ID "debug version, cocos2d-x-3.4-beta0 or later version can use"
#else
#define BUILD_ID "release version, cocos2d-x-3.4-beta0 or later version can use"
#endif
#endif

#define BIT_COUNT	(sizeof(void*)*8)

#include "log/messages.h"
#include "modeldata/Model.h"
#include "Settings.h"
#include "readers/Reader.h"
#include "FbxConvCommand.h"
#include "json/JSONWriter.h"
#include "json/UBJSONWriter.h"
#include "readers/FbxConverter.h"
#include "modeldata/C3BFile.h"
namespace fbxconv {

void simpleTextureCallback(std::map<std::string, readers::TextureFileInfo>&textures) {
	for (std::map<std::string, readers::TextureFileInfo>::iterator it = textures.begin(); it != textures.end(); ++it) {
		it->second.path = it->first.substr(it->first.find_last_of("/\\\\")+1);
	}
}

class FbxConv {
	public:
		fbxconv::log::Log *log;

		FbxConv(fbxconv::log::Log *log) : log(log) {
			log->info(log::iNameAndVersion, modeldata::VERSION_HI, modeldata::VERSION_LO, /*BUILD_NUMBER,*/BIT_COUNT, BUILD_ID);
		}

		constchar *getVersionString() {
			returnlog->format(log::iVersion, modeldata::VERSION_HI, modeldata::VERSION_LO, BUILD_NUMBER, BIT_COUNT, BUILD_ID);
		}

		constchar *getNameAndVersionString() {
			returnlog->format(log::iNameAndVersion, modeldata::VERSION_HI, modeldata::VERSION_LO, /*BUILD_NUMBER,*/BIT_COUNT, BUILD_ID);
		}

		bool execute(intconst&argc, constchar** const&argv) {
			Settings settings;
			FbxConvCommand command(log, argc, argv, &settings);
#ifdef DEBUG
			log->filter |= log::Log::LOG_DEBUG;
#else
			log->filter &= ~log::Log::LOG_DEBUG;
#endif
			if (settings.verbose)
				log->filter |= log::Log::LOG_VERBOSE;
			else
				log->filter&= ~log::Log::LOG_VERBOSE;

			if (command.error != log::iNoError)
				command.printCommand();
			elseif (!command.help)
				returnexecute(&settings);
			command.printHelp();
			returnfalse;
		}

		bool execute(Settings * const&settings) {
			bool result = false;
			modeldata::Model *model = newmodeldata::Model();
			if (load(settings, model)) {
				if (settings->verbose)
					info(model);
				if (save(settings, model))
					result = true;
			}
			delete model;
			return result;
		}

		readers::Reader *createReader(constSettings * const&settings) {
			returncreateReader(settings->inType);
		}

		readers::Reader *createReader(constint&type) {
			switch(type) {
			caseFILETYPE_FBX: 
				returnnewreaders::FbxConverter(log, simpleTextureCallback);
			caseFILETYPE_G3DB:
			caseFILETYPE_G3DJ:
			default:
				log->error(log::eSourceLoadFiletypeUnknown);
				return0;
			}
		}

		bool load(Settings * const&settings, modeldata::Model *model) {
			log->status(log::sSourceLoad);

			readers::Reader *reader = createReader(settings);
			if (!reader)
				returnfalse;

			bool result = reader->load(settings);
			if (!result)
				log->error(log::eSourceLoadGeneral, settings->inFile.c_str());
			else {
				result = reader->convert(settings, model);
				log->status(log::sSourceConvert);
			}

			log->status(log::sSourceClose);

			delete reader;
			return result;
		}

		bool save(Settings * const&settings, modeldata::Model *model) {
			bool result = false;
			json::BaseJSONWriter *jsonWriter = 0;
            model->exportPart = settings->exportPart;
			if(settings->outType == FILETYPE_ALL || settings->outType == FILETYPE_C3T)
			{
				std::stringout = settings->outFile;
				int o = out.find_last_of(".");
				out = out.substr(0, o+1) +  "c3t";

				std::ofstream myfile;
				myfile.open (out.c_str(), std::ios::binary);

				log->status(log::sExportToG3DJ, out.c_str());
				jsonWriter = newjson::JSONWriter(myfile);
				(*jsonWriter) << model;
				delete jsonWriter;
				result = true;
				myfile.close();
			}
			if(settings->outType == FILETYPE_ALL || settings->outType == FILETYPE_C3B)
			{
				std::stringout = settings->outFile;
				int o = out.find_last_of(".");
				out = out.substr(0, o+1) + "c3b";
				C3BFile file;
				file.AddModel(model);
				file.saveBinary(out);
				log->status(log::sExportToG3DB, out.c_str());
			}

			log->status(log::sExportClose);
			return result;
		}

		void info(modeldata::Model *model) {
			if (!model)
				log->verbose(log::iModelInfoNull);
			else {
				log->verbose(log::iModelInfoStart);
				log->verbose(log::iModelInfoID, model->id.c_str());
				log->verbose(log::iModelInfoVersion, model->version[0], model->version[1]);
				log->verbose(log::iModelInfoMeshesSummary, model->meshes.size(), model->getTotalVertexCount(), model->getMeshpartCount(), model->getTotalIndexCount());
				log->verbose(log::iModelInfoNodesSummary, model->nodes.size(), model->getTotalNodeCount(), model->getTotalNodePartCount());
				log->verbose(log::iModelInfoMaterialsSummary, model->materials.size(), model->getTotalTextureCount());
			}
		}
	};
}

上述代码中提供了load函数用于加载模型文件和save函数用于保存模型信息,这两个函数是非常重要的。不同的模型类型表示利用宏定义实现的,模型文件格式定义如下:

#define FILETYPE_AUTO			0x00
#define FILETYPE_FBX			0x10
#define FILETYPE_G3DB			0x20
#define FILETYPE_G3DJ			0x21
#define FILETYPE_OUT_DEFAULT	FILETYPE_G3DB
#define FILETYPE_IN_DEFAULT		FILETYPE_FBX
#define FILETYPE_C3B			0X30
#define FILETYPE_C3T			0X31

定义文件格式的作用是方便程序扩展,json模块和log模块,这两个模块对读者来说比较熟悉,这里就不把代码列出来了,只把文件目录给读者看一下,它主要实现的功能是json文件的读写和转换模型时log日志的打印,代码目录结构如下图:



下面介绍在模型转换时最重要的模块modeldata,它主要包括FBX SDK,Cocos2d-x引擎的原始模型是FBX,FBX模型格式是通过工具转化而成的,它是Max自带的插件,其代码可以从如下网址: http://usa.autodesk.com/adsk/servlet/pc/item?siteID=123112&id=10775847下载程序所需要的SDK,SDK代码目录如下图:


FBX的SDK支持window系统,mac系统,linux系统并且为这些操作系统都提供了相应版本。因为fbx-conv工具需要加载FBX模型,需要下载到fbx sdk代码。下面开始将FBX转化后的c3b二进制文件,也就是写入类的创建,将二进制文件写入流的头文件完整代码如下所示:

#ifndef FILEIO_H_
#define FILEIO_H_

#include <cstdio>
#include <list>
#include <vector>
#include <stdio.h>
#include <string>

namespace fbxconv
{

/**
 * 写二进制数据到指定的数据流.
*/
void write(unsignedchar value, FILE* file);
void write(char value, FILE* file);
void write(constchar* str, FILE* file);
void write(unsignedint value, FILE* file);
void write(unsignedshort value, FILE* file);
void write(bool value, FILE* file);
void write(float value, FILE* file);
void write(constfloat* values, int length, FILE* file);

/**
 *把字符串和字符串的长度写入到二进制文件流 */
void write(conststd::string& str, FILE* file);

void writeZero(FILE* file);

void skipString(FILE* file);

void skipUint(FILE* file);


}

#endif

对应的完整源文件实现如下所示:

#include "FileIO.h"
#include <cassert>
namespace fbxconv
{

	void write(unsignedchar value, FILE* file)
{
	size_t r = fwrite(&value, sizeof(unsignedchar), 1, file);
	assert(r == 1);
}

void write(char value, FILE* file)
{
	size_t r = fwrite(&value, sizeof(char), 1, file);
	assert(r == 1);
}

void write(constchar* str, FILE* file)
{
	size_t length = strlen(str);
	size_t r = fwrite(str, 1, length, file);
	assert(r == length);
}

void write(unsignedint value, FILE* file)
{
	size_t r = fwrite(&value, sizeof(unsignedint), 1, file);
	assert(r == 1);
}

void write(unsignedshort value, FILE* file)
{
	size_t r = fwrite(&value, sizeof(unsignedshort), 1, file);
	assert(r == 1);
}

void write(bool value, FILE* file)
{
	// 把布尔值写成一个无符号字符
	unsignedchar b = value;
	write(b, file);
}
void write(float value, FILE* file)
{
	fwrite(&value, sizeof(float), 1, file);
}
void write(constfloat* values, int length, FILE* file)
{
	fwrite(values, sizeof(float), length, file);
}
void write(conststd::string& str, FILE* file)
{
	// 写字符串长度
	write((unsignedint)str.size(), file);

	if (str.size() >0)
    {
		write(str.c_str(), file);
    }
}

void writeZero(FILE* file)
{
	write((unsignedint)0, file);
}

void skipString(FILE* file)
{
	// 获取字符数组大小
	unsignedint length = 0;
	fread(&length, sizeof(unsignedint), 1, file);
	if (length >0)
    {
		//跳过字符数组
		long seek = (long)(length * sizeof(char));
		fseek(file, seek, SEEK_CUR);
    }
}

void skipUint(FILE* file)
{
	fseek(file, sizeof(unsignedint), SEEK_CUR);
}

}

接下来读取FBX模型文件信息,会在下一个系列中继续介绍。。。。。。。。

以上是关于Cocos2d-x 3.x 图形学渲染系列二十七的主要内容,如果未能解决你的问题,请参考以下文章

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

《我所理解的Cocos2d-x》PDF

cocos2d-x与着色器设计--入门篇(游云凌天原创)

计算机图形学(OPENGL):PBR理论

《Unity Shader 与 计算机图形学》第二章

Cocos2d-x 3.x 3.0版本的全新绘制系