内容简介: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模型文件信息,会在下一个系列中继续介绍。。。。。。。。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Alone Together
Sherry Turkle / Basic Books / 2011-1-11 / USD 28.95
Consider Facebookit’s human contact, only easier to engage with and easier to avoid. Developing technology promises closeness. Sometimes it delivers, but much of our modern life leaves us less connect......一起来看看 《Alone Together》 这本书的介绍吧!