笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
每个引擎都有自己的处理Shader类,Cocos使用的是GLProgram类,之所以定义GLProgram类,是因为在引擎中需要有一个类管理模型的信息和矩阵信息声明。在GLProgram类中定义了模型顶点的属性,这些属性在加载模型时,用于解释模型文件内容时用于做属性标记处理,它是使用枚举定义的,枚举类代码如下所示:
enum { /**索引0用于定义位置*/ VERTEX_ATTRIB_POSITION, /**索引1用于定义颜色*/ VERTEX_ATTRIB_COLOR, /**索引2用于定义纹理坐标单元0*/ VERTEX_ATTRIB_TEX_COORD, /**索引3用于定义纹理坐标单元1.*/ VERTEX_ATTRIB_TEX_COORD1, /**索引4用于定义纹理坐标单元2.*/ VERTEX_ATTRIB_TEX_COORD2, /**索引5用于定义纹理坐标单元3.*/ VERTEX_ATTRIB_TEX_COORD3, /**索引6用于定义法线*/ VERTEX_ATTRIB_NORMAL, /**索引7用于定义混合权重*/ VERTEX_ATTRIB_BLEND_WEIGHT, /**索引8用于定义混合索引.*/ VERTEX_ATTRIB_BLEND_INDEX, /**索引9用于定义正切.*/ VERTEX_ATTRIB_TANGENT, /**索引10用于定义次法线.*/ VERTEX_ATTRIB_BINORMAL, VERTEX_ATTRIB_MAX, VERTEX_ATTRIB_TEX_COORDS = VERTEX_ATTRIB_TEX_COORD, };
在后面有关模型章节时,会有这方面的实际操作,接下来GLProgram类还定义了Shader中经常用于计算矩阵转换的标记,也是以枚举形式定义的如下所示:
enum { /**环境颜色.*/ UNIFORM_AMBIENT_COLOR, /**投影矩阵*/ UNIFORM_P_MATRIX, /**模型视口矩阵.*/ UNIFORM_MV_MATRIX, /**模型视口投影矩阵.*/ UNIFORM_MVP_MATRIX, /**法线矩阵.*/ UNIFORM_NORMAL_MATRIX, /**时间.*/ UNIFORM_TIME, /**sin(时间).*/ UNIFORM_SIN_TIME, /**cos(时间).*/ UNIFORM_COS_TIME, /**随机数字.*/ UNIFORM_RANDOM01, /** @{ * 对纹理的取样0-3 */ UNIFORM_SAMPLER0, UNIFORM_SAMPLER1, UNIFORM_SAMPLER2, UNIFORM_SAMPLER3, /**@}*/ UNIFORM_MAX, };
GLProgram类不只是只定义枚举属性,它还提供了加载顶点着色器和片段着色器接口函数,通过这些接口开发者可以知道它在加载Shader脚本时是如何解释其内容的。加载Shader脚本函数如下所示:
GLProgram* GLProgram::createWithFilenames(const std::string& vShaderFilename, const std::string& fShaderFilename, const std::string& compileTimeDefines) { auto ret = new (std::nothrow) GLProgram(); if(ret && ret->initWithFilenames(vShaderFilename, fShaderFilename, compileTimeDefines)) { ret->link(); ret->updateUniforms(); ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return nullptr; }
createWithFilenames函数中的参数是顶点着色器和片段着色器的文件路径,函数内部调用了initWithFilenames函数,继续深入进去查看该函数执行细节,内容如下所示:
bool GLProgram::initWithFilenames(const std::string& vShaderFilename, const std::string& fShaderFilename, const std::string& compileTimeDefines) { auto fileUtils = FileUtils::getInstance(); std::string vertexSource = fileUtils->getStringFromFile(FileUtils::getInstance()->fullPathForFilename(vShaderFilename)); std::string fragmentSource = fileUtils->getStringFromFile(FileUtils::getInstance()->fullPathForFilename(fShaderFilename)); return initWithByteArrays(vertexSource.c_str(), fragmentSource.c_str(), compileTimeDefines); }
程序加载了顶点着色器和片段着色器,也就是将Shader文件加载到内存中,接下来开始编译Shader脚本了,编译Shader脚本的函数是initWithByteArrays,它的具体实现代码如下所示:
bool GLProgram::initWithByteArrays(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray, const std::string& compileTimeDefines) { _program = glCreateProgram(); CHECK_GL_ERROR_DEBUG(); std::string replacedDefines = ""; replaceDefines(compileTimeDefines, replacedDefines); _vertShader = _fragShader = 0; if (vShaderByteArray) { if(!compileShader(&_vertShader, GL_VERTEX_SHADER, vShaderByteArray, replacedDefines)) { CCLOG("cocos2d: ERROR: Failed to compile vertex shader"); return false; } } // 创建和编译片段着色器 if (fShaderByteArray) { if(!compileShader(&_fragShader, GL_FRAGMENT_SHADER, fShaderByteArray, replacedDefines)) { CCLOG("cocos2d: ERROR: Failed to compile fragment shader"); return false; } } if (_vertShader) { glAttachShader(_program, _vertShader); } CHECK_GL_ERROR_DEBUG(); if (_fragShader) { glAttachShader(_program, _fragShader); } _hashForUniforms.clear(); CHECK_GL_ERROR_DEBUG(); return true; }
程序加载了顶点着色器和片段着色器,也就是将Shader文件加载到内存中,接下来开始编译Shader脚本了,编译Shader脚本的函数是initWithByteArrays,它的具体实现如下所示:
bool GLProgram::compileShader(GLuint* shader, GLenum type, const GLchar* source, const std::string& convertedDefines) { GLint status; if(!source) { return false; } const GLchar *sources[] = { #if CC_TARGET_PLATFORM == CC_PLATFORM_WINRT (type == GL_VERTEX_SHADER ?"precision mediump float;\n precision mediump int;\n" : "precision mediump float;\n precision mediump int;\n"), #elif (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32 && CC_TARGET_PLATFORM != CC_PLATFORM_LINUX && CC_TARGET_PLATFORM != CC_PLATFORM_MAC) (type == GL_VERTEX_SHADER ?"precision highp float;\n precision highp int;\n" : "precision mediump float;\n precision mediump int;\n"), #endif COCOS2D_SHADER_UNIFORMS, convertedDefines.c_str(), source}; *shader = glCreateShader(type); glShaderSource(*shader, sizeof(sources)/sizeof(*sources), sources, nullptr); glCompileShader(*shader); glGetShaderiv(*shader, GL_COMPILE_STATUS, &status); if (! status) { GLsizei length; glGetShaderiv(*shader, GL_SHADER_SOURCE_LENGTH, &length); GLchar* src = (GLchar*)malloc(sizeof(GLchar) * length); glGetShaderSource(*shader, length, nullptr, src); CCLOG("cocos2d: ERROR: Failed to compile shader:\n%s", src); if (type == GL_VERTEX_SHADER) { CCLOG("cocos2d: %s", getVertexShaderLog().c_str()); } else { CCLOG("cocos2d: %s", getFragmentShaderLog().c_str()); } free(src); return false; } return (status == GL_TRUE); }
函数的主要作用是对编写的Shader代码进行逐行解释,整个Shader底层加载编译就完成了,在明白其运行原理后,开始编写逻辑,假设已经有了顶点着色器代码zerklo.vert和片段着色器zerklo.frag,需将其传入到函数的参数中,调用的代码片段如下:
auto glprogram_Zerkalo = GLProgram::createWithFilenames("astronaut/zerkalo.vert", "astronaut/zerkalo.frag"); auto _state_Zerkalo = GLProgramState::getOrCreateWithGLProgram(glprogram_Zerkalo); sprite_Zerkalo->setGLProgramState(_state_Zerkalo);
代码片段是加载Shader的顶点着色器和片段着色器,加载完成将其作用到材质上。当然加载Shader 的方式有很多种,也可以直接使用材质脚本加载,后面章节会有具体介绍,下面系列开始介绍顶点索引数据类。