Sprite渲染流程-纹理绑定

261 阅读2分钟

Sprite使用的shader

  • vertexes
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;

void main()
{
    gl_Position = CC_PMatrix * a_position;
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
}
  • fragment
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;

void main()
{
    gl_FragColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
}

CCRenderer.cpp

drawBatchedTriangles(){

  for (int i=0; i<batchesTotal; ++i)
    {
        _triBatchesToDraw[i].cmd->useMaterial();
        glDrawElements(GL_TRIANGLES, (GLsizei) _triBatchesToDraw[i].indicesToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (_triBatchesToDraw[i].offset*sizeof(_indices[0])) );   
    }
}

在draw之前,会将材质同步进行设置。

Sprite使用的TrianglesCommand,TrianglesCommand在useMaterial中会绑定纹理

void TrianglesCommand::useMaterial() const
{
    //Set texture
    GL::bindTexture2D(_textureID);
    
    if (_alphaTextureID > 0)
    { // ANDROID ETC1 ALPHA supports.
        GL::bindTexture2DN(1, _alphaTextureID);
    }
    //set blend mode
    GL::blendFunc(_blendType.src, _blendType.dst);
    
    // 将uniform,attribute同步到shader
    _glProgramState->apply(_mv); 
}

GL::bindTexture2D(_textureID)逻辑,里面做了cache,如果当前的纹理单元已经绑定了相同的纹理对象,就不再进行额外的active,bind逻辑

void bindTexture2D(GLuint textureId)
{
    GL::bindTexture2DN(0, textureId);
}
void bindTexture2DN(GLuint textureUnit, GLuint textureId)
{
#if CC_ENABLE_GL_STATE_CACHE
	CCASSERT(textureUnit < MAX_ACTIVE_TEXTURE, "textureUnit is too big");
	if (s_currentBoundTexture[textureUnit] != textureId)
	{
		s_currentBoundTexture[textureUnit] = textureId;
		activeTexture(GL_TEXTURE0 + textureUnit);
		glBindTexture(GL_TEXTURE_2D, textureId);
	}
#else
	glActiveTexture(GL_TEXTURE0 + textureUnit);
	glBindTexture(GL_TEXTURE_2D, textureId);
#endif
}

最后的_glProgramState->apply()会再次同步shader变量

void GLProgramState::apply(const Mat4& modelView)
{
    applyGLProgram(modelView);

    applyAttributes();

    applyUniforms(); // 同步sampler的重点逻辑
}
void GLProgramState::applyUniforms()
{
    // set uniforms
    updateUniformsAndAttributes();
    for(auto& uniform : _uniforms) {
        uniform.second.apply();
    }
}

uniform的同步

void UniformValue::apply()
{
  if (_type == Type::CALLBACK_FN) {
     switch (_uniform->type) {
            case GL_SAMPLER_2D:
                // 这里最终就是调用到了glUniform1i
                _glprogram->setUniformLocationWith1i(_uniform->location, _value.tex.textureUnit);
                // 再次确认绑定好纹理单元
                GL::bindTexture2DN(_value.tex.textureUnit, _value.tex.textureId);
                break;
    }
  }
}

总结纹理绑定的流程

在fragment中需要定义一个sampler

uniform sampler2D texture;

void bindTextureUnit(int textureUnit, GLunit textureId){
    // 先激活对应的纹理单元
    glActiveTexture(GL_TEXTURE0+textureUnit);
    // 将创建的纹理对象绑定到已经激活的纹理单元上
    glBindTexture(GL_TEXTURE_2D, textureId);
    
    // -----------上下2部分逻辑可以分离---------
    
    // glLinkProgram的结果,对shader的所有操作都依赖这个id
    GLuint shderProgram;
    // 获取shader fragment中的texture
    GLuint location = glGetUniformLocation(shderProgram, "texture");
    // 将sampler2D指定纹理单元
    glUniform1i(location, textureUnit);
}


  • textureUnit

纹理单元,从0开始

  • textureId:本质上都是有glGenTextures生成的

从cocos2dx来说,它就是

texture->getName()

从OpenGL的角度来说,

GLuint textureId;
glGenTextures(1, &textureId);

因为OpenGL是个状态机,所以textureId会记录所有的操作痕迹。

知识点

为啥这么写:GL_TEXTURE0+textureUnit

#define GL_TEXTURE0 0x84C0
#define GL_TEXTURE1 0x84C1
#define GL_TEXTURE2 0x84C2
#define GL_TEXTURE3 0x84C3
#define GL_TEXTURE4 0x84C4
#define GL_TEXTURE5 0x84C5
#define GL_TEXTURE6 0x84C6
#define GL_TEXTURE7 0x84C7
#define GL_TEXTURE8 0x84C8
#define GL_TEXTURE9 0x84C9

每个TEXTURE的id都是相邻的,对于glActiveTexture来说,只要value正确就能正常工作。