首页 > 代码库 > Cocos2d-x 3.x 图形学渲染系列十六

Cocos2d-x 3.x 图形学渲染系列十六

笔者介绍:姜雪伟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 = http://www.mamicode.com/(GLchar*)malloc(sizeof(GLchar) * length);>

函数的主要作用是对编写的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 的方式有很多种,也可以直接使用材质脚本加载,后面章节会有具体介绍,下面系列开始介绍顶点索引数据类。



Cocos2d-x 3.x 图形学渲染系列十六