cocos2d源码分析(十):Material
Material类,即材质类,在cocos2d中相当重要,因为它内部管理着OpenGL相关的如着色器program,uniform以及OpenGL的状态和配置。它可以根据material配置文件来完成各种特效渲染,光照或贴图等效果。先看下它的类图:
由图可见,Material内有一个Technique类型的数组,Technique类内有一个Pass类的数组,Pass类内部成员有GLProgramState和VertexAttribBinging,可见,一个Pass对应着一个着色器。具体的如特效,光照的效果实际上就是在着色器中实现的。
下面看下一个具体的例子,cocos2d中的Material_2DEffects的demo,通过material文件创建Material实现2D特效。代码如下:
void Material_2DEffects::onEnter()
{
MaterialSystemBaseTest::onEnter();
//解析2d_effects.material文件,得到properties对象
auto properties = Properties::createNonRefCounted("Materials/2d_effects.material#sample");
// Print the properties of every namespace within this one.
printProperties(properties, 0); //打印
Material *mat1 = Material::createWithProperties(properties); //通过properties创建Material
//第一个是模糊效果
auto spriteBlur = Sprite::create("Images/grossini.png");
spriteBlur->setPositionNormalized(Vec2(0.2f, 0.5f));
this->addChild(spriteBlur);
spriteBlur->setGLProgramState(mat1->getTechniqueByName("blur")->getPassByIndex(0)->getGLProgramState()); //将Material内部的一个GLProgramState对象赋给了sprite的_glProgramState
//第二个是轮廓效果
auto spriteOutline = Sprite::create("Images/grossini.png");
spriteOutline->setPositionNormalized(Vec2(0.4f, 0.5f));
this->addChild(spriteOutline);
spriteOutline->setGLProgramState(mat1->getTechniqueByName("outline")->getPassByIndex(0)->getGLProgramState());
//第三个是噪声效果
auto spriteNoise = Sprite::create("Images/grossini.png");
spriteNoise->setPositionNormalized(Vec2(0.6f, 0.5f));
this->addChild(spriteNoise);
spriteNoise->setGLProgramState(mat1->getTechniqueByName("noise")->getPassByIndex(0)->getGLProgramState());
//第四个是边缘效果
auto spriteEdgeDetect = Sprite::create("Images/grossini.png");
spriteEdgeDetect->setPositionNormalized(Vec2(0.8f, 0.5f));
this->addChild(spriteEdgeDetect);
spriteEdgeDetect->setGLProgramState(mat1->getTechniqueByName("edge_detect")->getPassByIndex(0)->getGLProgramState());
// properties is not a "Ref" object
CC_SAFE_DELETE(properties);
}
材质内容的信息放在2d_effects.material文件中,我们看下它具体的内容是什么:
material sample
{
technique blur
{
pass 0
{
shader
{
defines = THIS_IS_AN_EXAMPLE 1;TOMORROW_IS_HOLIDAY 2
vertexShader = Shaders/example_Simple.vsh
fragmentShader = Shaders/example_Blur.fsh
// Uniforms
blurRadius = 3
sampleNum = 5
resolution = 100,100
}
}
}
technique outline
{
pass 0
{
shader
{
vertexShader = Shaders/example_Simple.vsh
fragmentShader = Shaders/example_Outline.fsh
u_outlineColor = 0.1, 0.2, 0.3
u_radius = 0.01
u_threshold = 1.75
}
}
}
technique noise {
pass 0
{
shader
{
vertexShader = Shaders/example_Simple.vsh
fragmentShader = Shaders/example_Noisy.fsh
resolution = 100,100
}
}
}
technique edge_detect
{
pass 0
{
shader
{
defines =
vertexShader = Shaders/example_Simple.vsh
fragmentShader = Shaders/example_EdgeDetection.fsh
resolution = 100, 100
}
}
}
}
material文件只是普通的文本文件,解析之后的信息放在properties对象中,Properties的类图如下:
由类图可见,Properties内部又有Properties的数组,一个Properties对应着material文件中的一个大括号,如此嵌套下去。Propertie类记录着一对key-value值,对应着最内部的大括号的等式。material文件解析后,对应的Properties类的内容为:
下面看下如何用Properties初始化Material,代码如下:
bool Material::initWithProperties(Properties* materialProperties)
{
return parseProperties(materialProperties);
}
parseProperties函数如下:
bool Material::parseProperties(Properties* materialProperties)
{
setName(materialProperties->getId());
auto space = materialProperties->getNextNamespace();
while (space) //循环解析子空间
{
const char* name = space->getNamespace();
if (strcmp(name, "technique") == 0)
{
parseTechnique(space); //解析technique块
}
else if (strcmp(name, "renderState") == 0)
{
parseRenderState(this, space); //解析renderState块
}
space = materialProperties->getNextNamespace();
}
return true;
}
因本次demo的2d_effects.material文件没有renderState块,我们只看parseTechnique函数
bool Material::parseTechnique(Properties* techniqueProperties)
{
auto technique = Technique::create(this);
_techniques.pushBack(technique);
// first one is the default one
if (!_currentTechnique)
_currentTechnique = technique;
// name
technique->setName(techniqueProperties->getId());
// passes
auto space = techniqueProperties->getNextNamespace();
while (space) //循环解析子空间
{
const char* name = space->getNamespace();
if (strcmp(name, "pass") == 0)
{
parsePass(technique, space); //解析pass块
}
else if (strcmp(name, "renderState") == 0)
{
parseRenderState(this, space);
}
space = techniqueProperties->getNextNamespace();
}
return true;
}
parsePass函数如下:
bool Material::parsePass(Technique* technique, Properties* passProperties)
{
auto pass = Pass::create(technique);
technique->addPass(pass);
// Pass can have 3 different namespaces:
// - one or more "sampler"
// - one "renderState"
// - one "shader"
auto space = passProperties->getNextNamespace();
while (space)
{
const char* name = space->getNamespace();
if (strcmp(name, "shader") == 0)
parseShader(pass, space); //解析shader块,生成OpenGL的program
else if (strcmp(name, "renderState") == 0)
parseRenderState(pass, space);
else {
CCASSERT(false, "Invalid namespace");
return false;
}
space = passProperties->getNextNamespace();
}
return true;
}
由代码看到正在解析shader块,比如blur中的内容,如下:
shader
{
defines = THIS_IS_AN_EXAMPLE 1;TOMORROW_IS_HOLIDAY 2 //这是着色器里的宏定义
vertexShader = Shaders/example_Simple.vsh //顶点着色器名称
fragmentShader = Shaders/example_Blur.fsh //片元着色器名称
// Uniforms
blurRadius = 3 //着色器中的uniform变量
sampleNum = 5
resolution = 100,100
}
parseShader函数内容如下:
bool Material::parseShader(Pass* pass, Properties* shaderProperties)
{
// vertexShader //得到顶点着色器的文件名
const char* vertShader = getOptionalString(shaderProperties, "vertexShader", nullptr);
// fragmentShader //得到片元着色器的文件名
const char* fragShader = getOptionalString(shaderProperties, "fragmentShader", nullptr);
// compileTimeDefines //这个是着色器里的宏定义
const char* compileTimeDefines = getOptionalString(shaderProperties, "defines", "");
if (vertShader && fragShader)
{ //根据vertShader和fragShader的文件名,创建glProgramState
auto glProgramState = GLProgramState::getOrCreateWithShaders(vertShader, fragShader, compileTimeDefines);
pass->setGLProgramState(glProgramState);
// Parse uniforms only if the GLProgramState was created
auto property = shaderProperties->getNextProperty();
while (property) //根据material文件设置program中的uniform变量
{
if (isValidUniform(property)) //是否是有效的uniform,只有"defines","vertexShader"和"fragmentShader"不是
{
parseUniform(glProgramState, shaderProperties, property); //保存到glProgramState中的_uniforms
}
property = shaderProperties->getNextProperty();
}
auto space = shaderProperties->getNextNamespace();
while (space)
{
const char* name = space->getNamespace();
if (strcmp(name, "sampler") == 0)
{
parseSampler(glProgramState, space);
}
space = shaderProperties->getNextNamespace();
}
}
return true;
}
分析都在注释里,简而言之就是通过shader块内的着色器文件名及uniform变量设置,来创建
glProgramState并保存到pass中。