目标:(五十一)中的问题120
思路:编辑着色器文件->虚拟着色器程序VirtualProgram->osg着色器程序Program->编译、链接成GPU着色器程序
osgEarth/ShaderLoader.cpp
bool
ShaderLoader::load(VirtualProgram* vp,
const std::string& filename,
const ShaderPackage& package,
const osgDB::Options* dbOptions)
{
std::string multisource = load(filename, package, dbOptions);//会对源代码中的部分字符串进行替换
vp->setFunction( entryPoint, source, location, 0L, order );//针对着色器函数文件
vp->setShader( name, shader );//针对着色器文件
}
1、在程序启动,还未进入帧循环前,由虚拟着色器程序负责VirtualProgram加载着色器文件(osgEarth/ShaderLoader.cpp)
主要目的是将文件放入VirtualProgram的_shaderMap中。
1.1替换着色器源代码中的部分字符串
包括:版本号$GLSL_VERSION_STR、精度$GLSL_DEFAULT_PRECISION_FLOAT、包含#pragma include等。
osgEarth/ShaderLoader.cpp
std::string
ShaderLoader::load(const std::string& filename,
const std::string& inlineSource,
const osgDB::Options* dbOptions)
{
osgEarth::replaceIn(output, "$GLSL_VERSION_STR", GLSL_VERSION_STR);
osgEarth::replaceIn(output, "$GLSL_DEFAULT_PRECISION_FLOAT", GLSL_DEFAULT_PRECISION_FLOAT);
}
文件分为着色器文件和函数文件,区别在于着色器文件没有指定函数入口点vp_entryPoint,而函数文件指定了函数入口点。两种文件的处理方式略有不同。
1.2对于着色器文件,对文件简单处理后放入虚拟着色器的着色器map表_shaderMap:
osg::Shader* shader = new osg::Shader(type, source);
shader->setName( filename );
vp->setShader( filename, shader );
osgEarth/ViretualProgram.cpp
osg::Shader*
VirtualProgram::setShader(const std::string& shaderID,
osg::Shader* shader,
osg::StateAttribute::OverrideValue ov)
{
PolyShader* pshader = new PolyShader( shader );
pshader->prepare();//会对文件进行拆解,对变量进行一些处理变换,再拼装成文件
ShaderEntry& entry = _shaderMap[MAKE_SHADER_ID(shaderID)];
entry._shader = pshader;
entry._overrideValue = ov;
entry._accept = 0L;
}
1.3对于着色器函数文件,会将函数的入口点、顺序、函数名放入函数map表_functions,再将该文件放入虚拟着色器的着色器map表_shaderMap:
vp->setFunction( entryPoint, source, location, 0L, order );
osgEarth/ViretualProgram.cpp
void
VirtualProgram::setFunction(const std::string& functionName,
const std::string& shaderSource,
ShaderComp::FunctionLocation location,
ShaderComp::AcceptCallback* accept,
float ordering)
{
OrderedFunctionMap& ofm = _functions[location];
ShaderComp::Function function;
function._name = functionName;
function._accept = accept;
ofm.insert( OrderedFunction(ordering, function) );
ShaderEntry& entry = _shaderMap[MAKE_SHADER_ID(functionName)];
entry._shader = shader;
entry._overrideValue = osg::StateAttribute::ON;
entry._accept = accept;
}
1.3不论是着色器文件还是着色器函数文件,最终都会创建一个着色器对象osg::Shader,这个对象负责着色器代码的管理,以及编译链接等。对象创建之初会做一些操作:(1)处理#pragma import_defines、#pragma requires,并将这些内容放入_shaderDefines、_shaderRequirements中,这些Define会和状态机State中的_defineMap做比较,共有的部分会放入到着色器代码中(这部分工作在绘制阶段做,见步骤2、3)。(2)
osg/Shader.cpp
Shader::Shader(Type type, const std::string& source) :
_type(type),
_shaderDefinesMode(USE_SHADER_PRAGMA)
{
setShaderSource( source);
}
void Shader::setShaderSource( const std::string& sourceText )
{
_shaderSource = sourceText;
_computeShaderDefines();
dirtyShader();
}
2、在绘制阶段,由状态机State对VirtualProgram所在的状态集StateSet进行压栈操作,也就是让State记录当前的绘制状态,状态主要包括模式表_modeMap、属性表_attributeMap、一致变量表_uniformMap、定义表_defineMap。
osg/State.cpp
void State::pushStateSet(const StateSet* dstate)
{
pushModeList(_modeMap,dstate->getModeList());
pushAttributeList(_attributeMap,dstate->getAttributeList());
pushUniformList(_uniformMap,dstate->getUniformList());
pushDefineList(_defineMap,dstate->getDefineList());
}
3、在绘制阶段,对VirtualProgram进行apply操作。这个操作是重点,将虚拟着色器程序中代码,转换为gpu上可执行的着色器程序,主要有以下内容
3.1获取上层虚拟着色器程序中的所有着色器文件、属性绑定列表,并放入本地变量local.accumShaderMap、local.accumAttribBindings中。
3.2将本级虚拟着色器程序中的所有着色器文件、属性绑定列表,加入到本地变量local.accumShaderMap、local.accumAttribBindings中。
3.3将local.accumShaderMap中的所有着色器文件放入local.programKey中。该步在首次apply时不起作用,因为后面在2.5前面会清空这里面的内容。
3.4将上层和本级的函数map表_functions进行去重操作后放入本地变量accumFunctions。
3.5构建着色器程序