OpenGL着色器基础与Cocos2d-x 3.0灰度化Shader实现
着色器概述
要掌握OpenGL,理解其渲染管线至关重要。OpenGL渲染管线主要包含以下阶段(浅蓝色区域表示可编程阶段):
流程包括:
- 顶点数据准备:通过VBO、VAO和顶点属性向OpenGL传递数据
- 顶点处理:主要由顶点着色器(Vertex Shader)完成,可选的细分和几何着色器也在此阶段
- 顶点后处理:裁剪、顶点坐标归一化及视口变换
- 图元组装:例如三个点组合成一个三角形
- 光栅化:将图元转换为像素片元
- 片元着色器处理:对每个像素片元进行着色
- 逐片元测试:裁剪测试、深度测试、混合、模板测试等
GLSL(OpenGL Shader Language)是专门为GPU设计的类C语言,支持并行执行。OpenGL ES的着色器分为.vsh(顶点着色器)和.fsh(片元着色器)两类,经编译链接后生成可执行程序与GPU通信。
顶点着色器(.vsh)
顶点着色器负责顶点计算,控制顶点位置,通常接收顶点位置和纹理坐标作为输入。示例:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
precision mediump float;
uniform float overTurn;
void main()
{
gl_Position = position;
if (overTurn > 0.0) {
textureCoordinate = vec2(inputTextureCoordinate.x, overTurn - inputTextureCoordinate.y);
} else {
textureCoordinate = vec2(inputTextureCoordinate.x, inputTextureCoordinate.y);
}
}
关键点解析:
- 每个着色器程序都有main函数(类似C语言)
attribute:外部传入的顶点属性,每个顶点独立拥有,变化频率高varying:在顶点着色器和片元着色器间传递数据,类型必须一致uniform:外部传入的常量,变化率低,在渲染过程中保持不变precision mediump float:声明中等精度浮点数- 变量命名规则与C语言一致,但
gl_开头的为系统内置变量,应避免使用 - CC_MVPMatrix是Cocos2d-x内部设置的mat4类型uniform
- 顶点着色器对每个顶点执行一次,三个顶点则执行三次
示例中,当overTurn > 0.0时,纹理y轴被反转。
片元着色器(.fsh)
片元着色器处理每个像素,可重新计算像素颜色:
varying highp vec2 textureCoordinate;
precision mediump float;
uniform sampler2D videoFrame;
vec4 memoryRender(vec4 color)
{
float gray;
gray = color.r * 0.3 + color.g * 0.59 + color.b * 0.11;
color.r = gray;
color.g = gray;
color.b = gray;
color.r += color.r * 1.5;
color.g = color.g * 2.0;
if (color.r > 255.0) color.r = 255.0;
if (color.g > 255.0) color.g = 255.0;
return color;
}
void main()
{
vec4 pixelColor;
pixelColor = texture2D(videoFrame, textureCoordinate);
gl_FragColor = memoryRender(pixelColor);
}
关键点:
varying highp vec2 textureCoordinate:从顶点着色器传递的纹理坐标uniform sampler2D videoFrame:纹理贴图texture2D(videoFrame, textureCoordinate):从纹理中采样像素颜色gl_FragColor:系统内置变量,定义最终屏幕像素颜色- 片元着色器中的main函数与顶点着色器的varying变量必须类型一致(如vec2 vs vec2),否则编译错误
超简总结
- 顶点着色器负责计算像素位置(填写
gl_Position) - 片元着色器负责计算像素外观(填写
gl_FragColor) - 两者均为逐像素运行,可能执行多次
实战:Cocos2d-x 3.0灰度化着色器
以下代码展示如何在Cocos2d-x 3.0中应用自定义着色器实现精灵灰度化。
头文件:GLTestingLayer.h
#ifndef __GLTesting_H_
#define __GLTesting_H_
#include "cocos2d.h"
USING_NS_CC;
class GLTestingLayer : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
void menuCloseCallback(cocos2d::Ref* pSender);
CREATE_FUNC(GLTestingLayer);
void graySprite(Sprite * sprite);
virtual void visit(Renderer* renderer, const kmMat4 &parentTransform, bool parentTransformUpdated) override;
void onDraw();
private:
CustomCommand _command;
};
#endif // __GLTesting_H_
实现文件:GLTestingLayer.cpp
#include "GLTestingLayer.h"
cocos2d::Scene* GLTestingLayer::createScene()
{
auto scene = Scene::create();
auto layer = GLTestingLayer::create();
scene->addChild(layer);
return scene;
}
bool GLTestingLayer::init()
{
if (!Layer::init())
{
return false;
}
this->setPosition(0, 0);
Size visibleSize = Director::getInstance()->getVisibleSize();
auto sprite = Sprite::create("HelloWorld.png");
sprite->setAnchorPoint(Point(0.5, 0.5));
sprite->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2));
this->addChild(sprite);
graySprite(sprite);
this->setShaderProgram(ShaderCache::getInstance()->getProgram(GLProgram::SHADER_NAME_POSITION_COLOR));
return true;
}
void GLTestingLayer::graySprite(Sprite * sprite)
{
if (sprite)
{
GLProgram * p = new GLProgram();
p->initWithFilenames("gray.vsh", "gray.fsh");
p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
p->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORDS);
p->link();
p->updateUniforms();
sprite->setShaderProgram(p);
}
}
void GLTestingLayer::visit(Renderer* renderer, const kmMat4 &parentTransform, bool parentTransformUpdated)
{
Layer::visit(renderer, parentTransform, parentTransformUpdated);
_command.init(_globalZOrder);
_command.func = CC_CALLBACK_0(GLTestingLayer::onDraw, this);
Director::getInstance()->getRenderer()->addCommand(&_command);
}
void GLTestingLayer::onDraw()
{
auto glProgram = this->getShaderProgram();
glProgram->use();
glProgram->setUniformsForBuiltins();
glPointSize(10.0f);
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
auto size = Director::getInstance()->getWinSize();
float vertercies[] = { 100, 100,
200, 200,
300, 100 };
float color[] = { 1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1 };
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_COLOR);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertercies);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_FLOAT, GL_TRUE, 0, color);
glDrawArrays(GL_TRIANGLES, 0, 3);
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 3);
CHECK_GL_ERROR_DEBUG();
}
灰度化顶点着色器:gray.vsh
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;
}
灰度化片元着色器:gray.fsh
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform sampler2D CC_Texture0;
void main()
{
vec4 v_orColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
float gray = dot(v_orColor.rgb, vec3(0.299, 0.587, 0.114));
gl_FragColor = vec4(gray, gray, gray, v_orColor.a);
}
至此,通过自定义着色器实现了Cocos2d-x 3.0中精灵的灰度化效果。着色器编程基于C语法,但调试较复杂,需严格确保类型匹配。