Shader architecture of Cocos2dx
Shader of Cocos2dx is composed of GLProgram, GLProgramState, GLProgramCache, and GLProgramStateCache.
GLProgram is Cocos2dx’s encapsulation of Program, and generally provides some static methods, which are reused by multiple nodes. And GLProgramState is an encapsulation of GLProgram, which provides a more convenient operation interface. The Uniform and Attribute are recorded internally, which can be regarded as a dynamic object. GLProgram and GLProgramState can compare and understand the difference between class and object.
Their corresponding xxCache classes are used for buffering as the name suggests. For example, the names of all built-in Shaders in Cocos2dx are created and cached in the loadDefaultGLProgram() method of GLProgramCache.
Cocos2dx built-in shader rules
Let’s analyze the source code first and see how Cocos2dx encapsulates the Shader. It is written with general OpenGL The Shader script is different.
First of all, in the compileShader() method of GLProgram, the precision keyword is used to set the precision of the value before compiling the Shader. There are a total of low-precision lowp, medium-precision mediump, and high-precision highp to choose from. The precision can be specified for floating-point numbers or integers. The higher the precision, the better the effect, but the greater the consumption. In addition, the vertex shader supports higher precision than the fragment shader, and the highp precision support of the fragment shader is optional for the graphics card.
Then, it will define a series of Uniform variables before the Shader script. These are all built-in Uniforms of Cocos2dx, which can be used directly in Shader. The following is the source code cut:
bool GLProgram::compileShader(GLuint* shader, GLenum type, const GLchar* source, const std::string& convertedDefines){ GLint status; if (!source) {return false;} const GLchar *sources< /span>[] = {#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 mediump float;\n precision medium p int;\n"),#endif COCOS2D_SHADER_UNIFORMS, convertedDefines.c_str(), source}; * shader = glCreateShader(type); glShaderSource(*shader, sizeof(sources)/sizeof(*sources< /span>), sources, nullptr); glCompileShader(*shader); glGetShaderiv(*shader, GL_COMPILE_STATUS , &status); if (! status) {GLsizei length; glGetShaderiv(*shader, GL_SHADER_SOURCE_LENGTH, &length); GLchar* src = (GLchar *) span>malloc(sizeof(GLchar) * length); glGetShaderSource(*shader, length, nullptr, src); CCLOG("cocos2d: ERROR: Failed to compile shader:\n%s"< /span>, 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);}
static const char * COCOS2D_SHADER_UNIFORMS = "uniform mat4 CC_PMatrix;\n" "u niform mat4 CC_MVMatrix;\n" "uniform mat4 CC_MVPMatrix;\n" "uniform mat3 CC_NormalMatrix;\n " "uniform vec4 CC_Time;\n" "uniform vec4 CC_SinTime;\n" < span class="hljs-string">"uniform vec4 CC_CosTime;\n" "uniform vec4 CC_Random01;\n" "uniform sampler2D CC_Texture0;\n" "uniform sampler2D CC_Texture1;\n" "uniform sampler2D CC_Texture2;\n" "uniform sampler2D CC_Texture3;\n" "//CC INCLUDES END\n\ n";
When initializing GLProgram, Cocos2dx will automatically initialize the corresponding attributes of the _flags member variable according to whether the Shader uses the corresponding Uniform variable. When compiling the Shader, the GLSL compiler will automatically remove the unused Uniforms (use glGetUniformLocation() to judge, return -1 means unused).
void GLProgram::updateUniforms(){ _builtInUniforms[UNIFORM_AMBIENT_COLOR] = glGetUniformLocation(_program, UNIFORM_NAME_AMBIENT_COLOR); _builtInUniforms[UNIFORM_P_MATRIX] = glGetUniformLocation(_program, UNIFORM_NAME_P_MATRIX); _builtInUniforms[UNIFORM_MV_MATRIX] = glGetUniformLocation( _program, UNIFORM_NAME_MV_MATRIX); _builtInUniforms[UNIFORM_MVP_MATRIX] = glGetUniformLocation(_program, UNIFORM_NAME_MVP_MATRIX); _builtInUniforms[UNIFORM_NORMAL_MATRIX] = glGetUniformLocation(_program, UNIFORM_NAME_NORMAL_MATRIX); _builtInUniforms[UNIFORM_TIME] = glGetUniformLocation(_program, UNIFORM_NAME_TIME); _builtInUniforms[ UNIFORM_SIN_TIME] = glGetUniformLocation(_program, UNIFORM_NAME_SIN_TIME ); _builtInUniforms[UNIFORM_COS_T IME] = glGetUniformLocation(_program, UNIFORM_NAME_COS_TIME); _builtInUniforms[UNIFORM_RANDOM01] = glGetUniformLocation(_program, UNIFORM_NAME_RANDOM01); _builtInUniforms[UNIFORM_SAMPLER0] = glGetUniformLocation(_program, UNIFORM_NAME_SAMPLER0); _builtInUniforms[UNIFORM_SAMPLER1] = glGetUniformLocation(_program, UNIFORM_NAME_SAMPLER1); _built InUniforms[UNIFORM_SAMPLER2] = glGetUniformLocation(_program, UNIFORM_NAME_SAMPLER2); _builtInUniforms[UNIFORM_SAMPLER3] = glGetUniformLocation(_program, UNIFORM_NAME_SAMPLER3); _flags .usesP = _builtInUniforms[UNIFORM_P_MATRIX] != -1; _flags.usesMV = _builtInUniforms[UNIFORM_MV_MATRIX] != -1; _flags.usesMVP = _builtInUniforms[UNIFORM_MVP_MATRIX] != -1; _flags.usesNormal = _builtInUniforms[UNIFORM_NORMAL_MATRIX] != -1; _flags.usesTime = (_builtInUniforms[UNIFORM_TIME] != -1 || _builtInUniforms[UNIFORM_SIN_TIME] != -1 || _builtInUniforms[UNIFORM_COS_TIME] != -1 ); _flags.usesRandom = _builtInUniforms[UNIFORM_RANDOM01] != -1; this->use(); // Since sample most probably won't change, set it to span> 0,1,2,3 now. if(_builtInUniforms[UNIFORM_SAMPLER0] != -1) setUniformLocationWith1i(_builtInUniforms[UNIFORM_SAMPLER0], 0); if(_builtInUniforms[UNIFORM_SAMPLER1] != -1) setUniformLocationWith1i(_builtInUniforms[UNIFORM_SAMPLER1], 1); if(_builtInUniforms[UNIFORM_SAMPLER2] != -1) setUniformLocationWith1i(_builtInUniforms[UNIFORM_SAMPLER2], 2); if(_builtInUniforms[UNIFORM_SAMPLER3] != -1) setUniformLocationWith1i(_builtInUniforms[UNIFORM_SAMPLER3], 3);}
Above we analyzed that the Shader of Cocos2dx will have many built-in Uniform variables, and we also know whether these Uniform variables are used. So the next question, if used, when were these uniforms set up? What are their values?
If it is a texture uniform, it will be set to a fixed value at the beginning. It can also be seen in the source code above. 4 textures can be used at the same time. When rendering, Cocos2dx will bind the texture to the specified texture. In the sampler, we can access these samplers in the Shader script, and the texture corresponding to the sampler is bound by Cocos2dx calling OpenGL’s glBindTexture() and other methods, so the value of this Uniform does not need to be set by us.
Other Uniforms, such as matrix, time, normal, random number, etc., are dynamically set at runtime. These properties change in real time. When the program is running, Cocos2dx will call setUniformsForBuiltins() to automatically set it every frame they.
void GLProgram::setUniformsForBuiltins(const Mat4 &matrixMV){ const auto& matrixP = _director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); if< /span> (_flags.usesP) setUniformLocationWithMatrix4fv(_builtInUniforms[UNIFORM_P_MATRIX], matrixP.m, 1); if (_flags.usesMV) setUniformLocationWithMatrix4fv(_builtInUniforms[UNIFORM_MV_MATRIX], matrixMV.m, 1); if (_flags.usesMVP) {Mat4 matrixMVP = matrixP * matrixMV; setUniformLocationWithMatrix4fv(_builtInUniforms[UNIFORM_MVP_MATRIX], matrixMVP.< span class="hljs-keyword">m, 1);} if (_flags .usesNormal) {Mat4 mvInverse = matrixMV; mvInverse.m[12] = mvInverse.m[ 13] = mvInverse.m[14 ] = 0.0f; mvInverse.inverse(); mvInverse.transpose(); GLfloat normalMat[9 ]; normalMat[0] = mvInverse.m[0 ];normalMat[1] = mvInverse.m[1];normalMat[2] = mvInverse.m[2]; normalMat[3] = mvInverse.m [4];normalMat[4] = mvInverse.m[5];normalMat[5] = mvInverse.m[6]; normalMat[6 span>] = mvInverse.m[8];normalMat[7] = mvInverse.m[9];normalMat[8] = mvInverse.m[10]; setUniformLocationWithMatrix3fv(_builtInUniforms [UNIFORM_NORMAL_MATRIX], normalMat, 1);} if (_flags.usesTime) {// This doesn't give the most accurate global time value. // Cocos2D doesn't store a high precision time value, so this will have to do. // Getting Mach time per frame per shader using time could be extremely expensive. float time = _director->getTotalFrames() * _director->getAnimationInterval(); setUniformLocationWith4f (_builtInUniforms[GLProgram::UNIFORM_TIME], time/10.0, time, time*2, time *4); setUniformLocationWith4f(_builtInUniforms[GLProgram::UNIFORM_SIN_TIME], time/8.0, time/4.0, time/2.0, sinf(time)); setUniformLocationWith4f(_builtInUniforms[GLProgram::UNIFORM_COS_TIME], time/8.0, time/4.0, time/2.0, cosf(time));} < span class="hljs-keyword">if (_flags.usesRandom) setUniformLocationWith4f(_builtInUniforms[GLProgram::UNIFORM_RANDOM01], CCRANDOM_0_1(), CCRANDOM_0_1(), CCRANDOM_0_1(), CCRANDOM_0_1());}
Of course, in addition to the above built-in Uniform, Cocos2dx also has some built-in Attributes, but these variables need to be manually entered in the Shader script, mainly colors, coordinates, texture coordinates, methods Common attributes such as lines. They are defined in CCGLProgram.cpp, and when Renderer performs rendering, glVertexAttribPointer() is called to set them.
// Attribute namesconst char* GLProgram::ATTRIBUTE_NAME_COLOR = "a_color";const < span class="hljs-keyword">char* GLProgram::ATTRIBUTE_NAME_POSITION = "a_position";const< /span> char* GLProgram::ATTRIBUTE_NAME_TEX_COORD = "a_texCoord";const char* GLProgram::ATTRIBUTE_NAME_TEX_COORD1 = "a_texCoord1";const char* GLProgram::ATTRIBUTE_NAME_TEX_COORD2 = "a_texCoord2"; const char* GLProgram::ATTRIBUTE_N AME_TEX_COORD3 = "a_texCoord3";const char * GLProgram::ATTRIBUTE_NAME_NORMAL = "a_normal";const char * GLProgram::ATTRIBUTE_NAME_BLEND_WEIGHT = "a_blendWeight";const char* GLProgram::ATTRIBUTE_NAME_BLEND_INDEX = "a_blendIndex";const char* GLProgram::ATTRIBUTE_NAME_TANGENT = "a_tangent";const char* GLProgram::ATTRIBUTE_NAME_BINORMAL = "a_binormal";
The entire Cocos2dx The different rules of Shader are roughly like this, mainly because a series of built-in variables are automatically set by the engine, and we can use them directly in our Shader.
Shader example in Cocos2dx
We will write a commonly used gray shader to demonstrate the process of using Shader in Cocos2dx.
Principle of graying: The characteristic of gray-white pictures is that the values of the three RGB components of each pixel are equal. The higher the value, the whiter the color, and vice versa. There are three commonly used algorithms, average, maximum and weighted average.
First, we have to write vertex Shader and fragment Shader separately:
//Vertex Shader< span class="hljs-keyword">attribute vec4 a_position; //Location (built-in)attribute vec2 a_texCoord; //Texture coordinates (built-in) varying vec2 v_texCoord; //custom Variable variable (write value)void main(){ gl_Position = CC_PMatrix * a_position ; v_texCoord = a_texCoord;}
//Fragment shader//Use The weighted average is used to calculate the RGB components, that is, a ratio is assigned to each component. The specified ratio is not hard-coded and is set by a Uniform variable.varying vec2 v_texCoord; //Receive the value passed by the vertex shader (receive value)uniform vec4 u_grayParam; //Specify the proportion of rgb color components< /span>void main(){ vec4 texColor = texture2D (CC_Texture0, v_texCoord); texColor.rgb = texColor.r * u_grayParam.r + texColor.g * u_grayParam.g + texColor.b * u_grayParam.b; gl_FragColor< /span> = texColor;}
Name them as gray.vert and gray.frag and save them to disk.
Okay, the script is finished, let’s see how to use it:
#include "renderer/CCGLProgramStateCache.h"//Gray the interfacevoid grayNode (Node* node){ //corresponding to the use of xxCache, first find it in the cache, no need to recreate it every time GLProgram* program = GLProgramCache::getInstance ()->getGLProgram("MyGrayShader"); if(nullptr == program) {//local disk load shader to create program, return one The form of a string is more efficient createWithByteArrays() program = GLProgram::createWithFilenames( "gray.vert", "gray.fra g"); //Cache the GLProgram object in the Cache GLProgramCache::getInstance( )->addGLProgram(program, "MyGrayShader");} //Create programState by program and use it to set the value of the custom Uniform variable above GLProgramState* programState = GLProgramState::getOrCreateWithGLProgram(program); programState->setUniformVec4("u_grayParam", Vec4(0.2f,0.3f ,0.5f,1.0f)); //call setGLProgramState of the node node, this shader will be used in the future rendering of this node node->setGLProgramState(programState);}
//Use auto sprite = Sprite::create(xx.png);grayNode(sprite);
So far, the Shader process used in Cocos2dx is finished~ Finally, how to restore the grayed out picture? OpenGL is the operating mechanism of a state machine, so you only need to restore the set Shader back to reset it. The default Shader of the Sprite object here is GLProgram::SHADER_NAME_POSITION_TEXTRUE_COLOR_NO_MVP. You can pass in the above string constant through GLProgramState::getOrCreateWithGLProgramName() to get its GLProgramState.