文章目录
一、【着色文件转换为字符数组】前言
二、【着色文件转换为字符数组】Shader转换
三、【着色文件转换为字符数组】转换函数
1.转换函数
2.字符替换函数
四、【着色文件转换为字符数组】示例
1.GLSL2Cpp.cpp文件:
2.Qt pro文件:
五、【着色文件转换为字符数组】运行效果
六、【着色文件转换为字符数组】总结
一、【着色文件转换为字符数组】前言
对于着色语言,我们常常采用单独的文件进行管理和存储,然后通过程序读取对应的着色语言文件,再加载到场景中,实现三维渲染。
但是,单独存放的着色语言文件存在以下问题:
- 程序部署时,要把着色语言文件同步拷贝的指定的目录下。
- 着色语言的具体内容暴露在外,不利于代码保密。
- 若用户自行修改了着色语言文件的内容,可能会导致程序无法运行。
若把着色语言书写为.cpp文件,加入到程序的编译文件之中,那么可快速解决上述问题。
在OpenSceneGraph(OSG)的核心库osgDB中,提供了osgDB::readRefShaderFile方法,可读取GLSL文件,构建osg::Shader的智能指针。
二、【着色文件转换为字符数组】Shader转换
GLSL转为字符数组,形成.cpp的步骤流程如下:
打开一个ofstream对象
fout
,以写入模式打开名为cppFileName
的文件。检查文件是否成功打开,如果没有打开成功,则输出错误信息并退出函数。
获取
shader
对象的着色器源代码,并保存在字符串变量shaderSource
中。调用
searchAndReplace
函数,将shaderSource
中的"\r\n"、"\r"和"""分别替换为"\n"和"\""。构造变量字符串
variableString
,例如char variableName[] =
。初始化变量
startOfLine
为0,用于记录每行的起始位置。查找
shaderSource
中第一个换行符的位置,保存在endOfLine
中。如果找不到换行符,则表示
shaderSource
只有一行代码,直接将variableString
、shaderSource
和"\\n\";"
写入文件中。否则,进入循环,循环结束条件为查找不到换行符。
将当前行的内容写入文件,注意在行末加上"
\\n\"
。更新
startOfLine
的值,使其指向下一行的起始位置。继续查找下一个换行符的位置,保存在
endOfLine
中。循环结束后,最后一行的内容仍未写入文件中,将其写入,并在行末加上"
\\n\";
"。输出写入完成的提示信息。
三、【着色文件转换为字符数组】转换函数
1.转换函数
// 将shader的源代码写入到cppFileName指定的文件中,并将变量名设置为variableName
void writeShader(osg::Shader* shader, const std::string& cppFileName, const std::string& variableName)
{
osgDB::ofstream fout(cppFileName.c_str()); // 打开要写入的文件
if (!fout)
{
std::cout<<"Error: could not open file `"<<cppFileName<<"` for writing."<<std::endl; // 如果打开失败,则输出错误信息并退出函数
return;
}
std::string shaderSource = shader->getShaderSource(); // 获取shader的源代码
searchAndReplace(shaderSource, "\r\n", "\n"); // 将Windows风格的换行符替换为Unix风格的
searchAndReplace(shaderSource, "\r", "\n"); // 将Macintosh风格的换行符替换为Unix风格的
searchAndReplace(shaderSource, "\"", "\\\""); // 将所有双引号转义
std::string variableString = std::string("char ") + variableName + std::string("[] = "); // 构造变量字符串
std::string::size_type startOfLine = 0; // 记录每行的起始位置
std::string::size_type endOfLine = shaderSource.find_first_of('\n', startOfLine); // 查找第一个换行符的位置
if (endOfLine == std::string::npos) // 如果找不到换行符,则表示该shader只有一行
{
fout << variableString << shaderSource << "\\n\";" << std::endl; // 直接将该行代码写入文件中
}
else
{
// 逐行将shader的源代码写入文件中
std::string padding(variableString.size(), ' '); // 构造填充字符串,用于保持格式一致
fout << variableString << "\"" << shaderSource.substr(startOfLine, endOfLine - startOfLine) << "\\n\"" << std::endl;
startOfLine = endOfLine + 1;
endOfLine = shaderSource.find_first_of('\n', startOfLine);
while (endOfLine != std::string::npos)
{
fout << padding << "\"" << shaderSource.substr(startOfLine, endOfLine - startOfLine) << "\\n\"" << std::endl;
startOfLine = endOfLine + 1;
endOfLine = shaderSource.find_first_of('\n', startOfLine);
}
// 最后一行的内容需要单独处理
fout << padding << "\"" << shaderSource.substr(startOfLine, endOfLine - startOfLine) << "\\n\";" << std::endl;
}
std::cout << "Written shader to `" << cppFileName << "`" << std::endl; // 输出写入完成的提示信息
}
2.字符替换函数
// 在字符串str中搜索所有与spat匹配的子串,并用rpat替换它们。
void searchAndReplace(std::string& str, const std::string& spat, const std::string& rpat)
{
std::string::size_type pos = 0;
while ((pos = str.find(spat, pos)) != std::string::npos) // 查找所有spat出现的位置
{
str.replace(pos, spat.length(), rpat); // 用rpat替换spat
pos += rpat.length(); // 更新pos的位置
}
}
四、【着色文件转换为字符数组】示例
1.GLSL2Cpp.cpp文件:
#include <osg/ArgumentParser>
#include <osg/ApplicationUsage>
#include <osgDB/ReadFile>
#include <osgDB/FileNameUtils>
#include <osgDB/fstream>
#include <iostream>
// 在字符串str中搜索所有出现的spat并替换为rpat
void searchAndReplace(std::string& str, const std::string& spat, const std::string& rpat)
{
std::string::size_type pos = 0;
while ((pos = str.find(spat, pos)) != std::string::npos)
{
str.replace(pos, spat.length(), rpat);
pos += rpat.length();
}
}
// 将shader写入cppFileName所指定的文件中,变量名为variableName
void writeShader(osg::Shader* shader, const std::string& cppFileName, const std::string& variableName)
{
osgDB::ofstream fout(cppFileName.c_str());
if (!fout)
{
std::cout<<"Error: could not open file `"<<cppFileName<<"` for writing."<<std::endl;
}
std::string shaderSource = shader->getShaderSource();
searchAndReplace(shaderSource, "\r\n", "\n");
searchAndReplace(shaderSource, "\r", "\n");
searchAndReplace(shaderSource, "\"", "\\\"");
std::string variableString = std::string("char ")+variableName+std::string("[] = ");
std::string::size_type startOfLine = 0;
std::string::size_type endOfLine = shaderSource.find_first_of('\n', startOfLine);
if (endOfLine==std::string::npos)
{
fout<<variableString<<shaderSource<<"\\n\";"<<std::endl;
}
else
{
std::string padding(variableString.size(),' ');
fout<<variableString<<"\""<<shaderSource.substr(startOfLine,endOfLine-startOfLine)<<"\\n\""<<std::endl;
startOfLine = endOfLine+1;
endOfLine = shaderSource.find_first_of('\n', startOfLine);
while (endOfLine != std::string::npos)
{
fout<<padding<<"\""<<shaderSource.substr(startOfLine,endOfLine-startOfLine)<<"\\n\""<<std::endl;
startOfLine = endOfLine + 1;
endOfLine = shaderSource.find_first_of('\n', startOfLine);
}
fout<<padding<<"\""<<shaderSource.substr(startOfLine,endOfLine-startOfLine)<<"\\n\";"<<std::endl;
}
std::cout<<"Written shader to `"<<cppFileName<<"`"<<std::endl;
}
int main( int argc, char **argv )
{
osg::ArgumentParser arguments(&argc,argv); // 创建ArgumentParser对象以处理程序参数
arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName()); // 设置应用程序名称为参数中的应用程序名称
arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is a utility for converting glsl shader files into char arrays that can be compiled into applications."); // 设置应用程序描述
arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ..."); // 设置命令行使用说明
arguments.getApplicationUsage()->addCommandLineOption("--shader <filename>","Shader file to create a .cpp file for."); // 添加命令行选项--shader <filename>
arguments.getApplicationUsage()->addCommandLineOption("--write-to-source-file-directory","Use the path to the source filename as the directory to write to."); // 添加命令行选项--write-to-source-file-directory
arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display command line parameters"); // 添加命令行选项-h或--help
if (arguments.read("-h") || arguments.read("--help")) // 如果用户输入了-h或--help参数,则输出命令行参数帮助文档并返回1
{
arguments.getApplicationUsage()->write(std::cout);
return 1;
}
bool useSamePathAsSourceFile = false;
if (arguments.read("--write-to-source-file-directory")) useSamePathAsSourceFile = true; // 如果用户使用了--write-to-source-file-directory选项,则将useSamePathAsSourceFile设置为true
std::string filename;
if (arguments.read("--shader",filename)) // 如果用户使用了--shader选项,则读取shader文件
{
osg::ref_ptr<osg::Shader> shader = osgDB::readRefShaderFile(filename); // 读取shader文件
if (shader.valid())
{
std::string name = osgDB::getStrippedName(filename); // 获取文件名
std::string path = osgDB::getFilePath(filename); // 获取文件所在路径
std::string invalidCharacters = "-+/\\*=(){}[]:;<>,.?@'~#`!\""; // 无效的字符
std::string numbericCharacters = "0123456789"; // 数字字符
std::string::size_type pos = name.find_first_of(invalidCharacters);
while (pos != std::string::npos) // 将文件名中的无效字符替换为下划线
{
name[pos] = '_';
pos = name.find_first_of(invalidCharacters);
}
std::string ext = osgDB::getFileExtension(filename); // 获取文件扩展名
std::string cppFileName = name + "_" + ext + ".cpp"; // 生成输出的cpp文件名
if (useSamePathAsSourceFile) cppFileName = osgDB::concatPaths(path, cppFileName);
std::string variableName = name + "_" + ext; // 变量名
writeShader(shader.get(), cppFileName, variableName); // 将shader写入cpp文件
return 0;
}
else
{
std::cout<<"Error: could not find file '"<<filename<<"'"<<std::endl;
return 1;
}
}
std::cout<<"No appropriate command line options used."<<std::endl;
arguments.getApplicationUsage()->write(std::cout);
return 1;
}
2.Qt pro文件:
QT += core
TEMPLATE = app
CONFIG += console
DESTDIR = ../3rdParty
if(contains(DEFINES,MSVC2015)){
DESTDIR = ../3rdParty-2015
CONFIG(debug, debug|release){
TARGET = eg_GLSL2Cppd
MOC_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Debug/moc
RCC_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Debug/rcc
UI_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Debug/ui
OBJECTS_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Debug/obj
}else{
TARGET = eg_GLSL2Cpp
MOC_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Release/moc
RCC_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Release/rcc
UI_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Release/ui
OBJECTS_DIR = ../build-OpenSceneGraph-2015/eg_GLSL2Cpp/Release/obj
}
}else{
DESTDIR = ../3rdParty
CONFIG(debug, debug|release){
TARGET = eg_GLSL2Cppd
MOC_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Debug/moc
RCC_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Debug/rcc
UI_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Debug/ui
OBJECTS_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Debug/obj
}else{
TARGET = eg_GLSL2Cpp
MOC_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Release/moc
RCC_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Release/rcc
UI_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Release/ui
OBJECTS_DIR = ../build-OpenSceneGraph/eg_GLSL2Cpp/Release/obj
}
}
DEFINES -= UNICODE _UNICODE
win32 {
DEFINES += _CRT_SECURE_NO_DEPRECATE _CRT_NONSTDC_NO_DEPRECATE
}
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
#当前目录
INCLUDEPATH += ./ ./include
#LIBS
#LIBS
if(contains(DEFINES,MSVC2015)){
LIBS += -L../3rdParty-2015
}else{
LIBS += -L../3rdParty
}
CONFIG(debug, debug|release){
LIBS += -lOpenThreadsd -losgd -losgDBd
}else{
LIBS += -lOpenThreads -losg -losgDB
}
#win32: LIBS += -lopengl32
SOURCES += ./examples/osg2cpp/osg2cpp.cpp
# Default rules for deployment.
#unix {
# target.path = /usr/lib
#}
#!isEmpty(target.path): INSTALLS += target
五、【着色文件转换为字符数组】运行效果
原始的blocky.frag文件:
形成的blocky_frag.cpp文件:
六、【着色文件转换为字符数组】总结
上述代码是一个用于将GLSL着色器文件转换为字符数组的实用程序。它通过读取着色器文件的内容,并将其以字符数组的形式写入一个与源文件同名但扩展名为.cpp的文件中。这个程序可以方便地将着色器源代码嵌入到C++代码中,以便在应用程序中使用。