当前位置:首页 > 技术 > 正文内容

OpenGL着色器基础与Cocos2d-x 3.0灰度化Shader实现

访客 技术 2026年5月29日 1

着色器概述

要掌握OpenGL,理解其渲染管线至关重要。OpenGL渲染管线主要包含以下阶段(浅蓝色区域表示可编程阶段):

流程包括:

  1. 顶点数据准备:通过VBO、VAO和顶点属性向OpenGL传递数据
  2. 顶点处理:主要由顶点着色器(Vertex Shader)完成,可选的细分和几何着色器也在此阶段
  3. 顶点后处理:裁剪、顶点坐标归一化及视口变换
  4. 图元组装:例如三个点组合成一个三角形
  5. 光栅化:将图元转换为像素片元
  6. 片元着色器处理:对每个像素片元进行着色
  7. 逐片元测试:裁剪测试、深度测试、混合、模板测试等

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语法,但调试较复杂,需严格确保类型匹配。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。