Cocos2dx-OpenGL ES2.0教程:编写自己的shader(2)

2023-11-18

上篇文章中,我给大家介绍了如何在cocos2d-x里面绘制一个三角形,当时我们使用的是cocos2d-x引擎自带的shader和一些辅助函数。在本文中,我将演示一下如何编写自己的shader,同时,我们还会介绍VBO(顶点缓冲区对象)和VAO(顶点数组对象)的基本用法。

在编写自己的shader之前,我觉得有必要提一下OpenGL渲染管线。
理解OpenGL渲染管线,对于学习OpenGL非常重要。下面是OpenGL渲染管线的示意图:(图中淡蓝色区域是可以编程的阶段)

pipelinepipeline

此图是从wiki中拿过来的,OpenGL的渲染管线主要包括:

  1. 准备顶点数据(通过VBO、VAO和Vertex attribute来传递数据给OpenGL)

  2. 顶点处理(这里主要由Vertex Shader来完成,从上图中可以看出,它还包括可选的Tessellation和Geometry shader阶段)

  3. 顶点后处理(主要包括Clipping,顶点坐标归一化和viewport变换)

  4. Primitive组装(比如3点组装成一个3角形)

  5. 光栅化成一个个像素

  6. 使用Fragment shader来处理这些像素

  7. 采样处理(主要包括Scissor Test, Depth Test, Blending, Stencil Test等)

更详细的信息可以参考本网站推荐的阅读材料和Wiki。

编写你的第一个Vertex Shader

首先是创建一个文件,把它命名为myVertextShader.vert, 并输入下列代码:

attribute vec4 a_position;
attribute vec4 a_color;

varying vec4 v_fragmentColor;

void main()
{
    gl_Position = CC_MVPMatrix * a_position;
    v_fragmentColor = a_color;
}

OpenGL Shader Language,简称GLSL,它是一种类似于C语言的专门为GPU设计的语言,它可以放在GPU里面被并行运行。下面我们来简单解释一下这一小段代码。

首先,每一个Shader程序都有一个main函数,这一点和c语言是一样的。然后这里面有两种类型的变量,一种是attribute,另一种是varying. attribute是从外部传进来的,每一个顶点都会有这两个属性,所以它也叫做vertex attribute(顶点属性)。而varying类型的变量是在vertex shader和fragment shader之间传递数据用的。这里的变量命名规则保持跟c一样就行了,注意gl开头的变量名是系统内置的变量,所以大家在定义自己的变量名时,请不要以gl开头。而CC_MVPMatrix是一个mat4类型的变量,它是在cocos2d-x内部设置进来的。这一点,我们后面再谈。

vertex shader是作用于每一个顶点的,我们本例中有三个点,所以这个vertex shader会被执行三次。

编写你的第一个Fragment Shader

首先,新建一个myFragmentShader.frag并输入下列代码:

varying vec4 v_fragmentColor;

void main()
{
    gl_FragColor = v_fragmentColor;
}

fragment shader中也有一个main函数,同时我们看到这里也声明了一个与vertex shader相同的变量v_fragmentColor。前面我们讲过,这个变量是用来在vertex shader和fragment shader之间传递数据用的。所以,它们的参数类型必须完全相同。如果一个是vec3,一个是vec4,shader编译的时候是会报错的。而gl_FragColor我们知道它肯定是一个系统内置变量了,它的作用是定义最终画在屏幕上面的像素点的颜色。我们回过头去看上一篇文章中画出来的三角形,我们指定的是三个顶点的颜色,分别为Red,Green和Blue,但是最后的三角形的颜色是通过这三个点的颜色插值出来的。因为最终三角形的像素点可不只有三个,理解这一点非常重要。

最后,让我们修改一下shader progam的创建代码:

//create my own program
 auto program = new GLProgram;
 program->initWithFilenames("myVertextShader.vert", "myFragmentShader.frag");
 program->link();
 //set uniform locations
 program->updateUniforms();

编译并运行,此时你会得到和之前效果一样的三角形。

下图解释了我们的顶点数据是如何渲染成最终屏幕上面的像素的:

graphics_piplinegraphics_pipline

VAO和VBO初探

VBO,全名Vertex Buffer Object。它是GPU里面的一块缓冲区,当我们需要传递数据的时候,可以先向GPU申请一块内存,然后往里面填充数据。最后,再通过调用glVertexAttribPointer把数据传递给Vertex Shader。而VAO,全名为Vertex Array Object,它的作用主要是记录当前有哪些VBO,每个VBO里面绑定的是什么数据,还有每一个vertex attribute绑定的是哪一个VBO。关于VBO和VAO更详细的介绍,请参考此文

使用VBO和VAO的步骤都差不多,步骤如下:

  1. glGenXXX
  2. glBindXXX

让我们修改之前的代码:

//创建和绑定vao

    uint32_t vao;

    glGenVertexArrays(1, &vao);

    glBindVertexArray(vao);

    

    //创建和绑定vbo

    uint32_t vertexVBO;

    glGenBuffers(1, &vertexVBO);

    glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);

auto size = Director::getInstance()->getVisibleSize(); float vertercies[] = { 0,0, //第一个点的坐标 size.width, 0, //第二个点的坐标 size.width / 2, size.height}; //第三个点的坐标 float color[] = { 0, 1,0, 1, 1,0,0, 1, 0, 0, 1, 1}; glBufferData(GL_ARRAY_BUFFER, sizeof(vertercies), vertercies, GL_STATIC_DRAW); //获取vertex attribute "a_position"的入口点 GLuint positionLocation = glGetAttribLocation(program->getProgram(), "a_position"); //打开入a_position入口点 glEnableVertexAttribArray(positionLocation); //传递顶点数据给a_position,注意最后一个参数是数组的偏移了。 glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); //set for color uint32_t colorVBO; glGenBuffers(1, &colorVBO); glBindBuffer(GL_ARRAY_BUFFER, colorVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW); GLuint colorLocation = glGetAttribLocation(program->getProgram(), "a_color"); glEnableVertexAttribArray(colorLocation); glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); //for safty glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0);

这里glBufferData把我们定义好的顶点和颜色数据传给VBO,此时,注意glVertexAttribPointer的最后一个参数,这里传递的都是(GLvoid)0。而不像之前一样传的是vertex和color的数组地址。*这一点是使用VBO和不使用VBO时要特别注意的。

顶点数据是怎么传递的

要弄明白程序里面定义的数组是怎么传递到vertex shader的,我们需要先弄清楚vertex attribute。

attribute vec4 a_position;
attribute vec4 a_color;

varying vec4 v_fragmentColor;

void main()
{
    gl_Position = CC_MVPMatrix * a_position;
    v_fragmentColor = a_color;
}

每一个attribute在vertex shader里面有一个location,它是用来传递数据的入口。我们可以通过下列代码获取这个入口值:

GLuint positionLocation = glGetAttribLocation(program->getProgram(), "a_position");
glEnableVertexAttribArray(positionLocation);

glGetAttribLocation是用来获得vertex attribute的入口的,在我们要传递数据之前,首先要告诉OpenGL,所以要调用glEnableVertexAttribArray。最后的数据通过glVertexAttribPointer传进来。它的第一个参数就是glGetAttribLocation返回的值。

重用VAO

最后,为了不让这些生成和绑定VBO和VAO的操作在每一帧都被执行,我们需要把它放在初始化函数里面。最终我们的draw函数如下:

auto glProgram = getGLProgram();

   glProgram->use();

   //set uniform values, the order of the line is very important
   glProgram->setUniformsForBuiltins();

   auto size = Director::getInstance()->getWinSize();

   //use vao,因为vao记录了每一个顶点属性和缓冲区的状态,所以只需要绑定就可以使用了
   glBindVertexArray(vao);

   glDrawArrays(GL_TRIANGLES, 0, 3);

   glBindVertexArray(0);

   CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 3);
   CHECK_GL_ERROR_DEBUG();

这里可以看出,VAO对于简化程序作用是很大的。

好了,编译并运行,还是原来的三角形。

triangletriangle

下一篇文章,我们将讲一下OpenGL各种坐标系及其变换。当然,最重要的是World-to-Model变换,Model-to-View变换和View-to-Projection变换。

参考资料

  1. http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Chapter-1:-The-Graphics-Pipeline.html

  2. http://opengl.zilongshanren.com/opengl-tutorial/tut02/zh.html

  3. http://www.opengl.org/wiki/Rendering_Pipeline_Overview

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Cocos2dx-OpenGL ES2.0教程:编写自己的shader(2) 的相关文章

  • Windows 10 Mobile (10.0.14393) 地理围栏后台任务 (LocationTrigger)

    自从10 0 14393 周年纪念更新 LocationTrigger似乎不起作用 我有 Windows Phone 8 1 应用程序 也适用于 UWP 应用程序 输出到的便携式库Windows Runtime Component图书馆 w
  • 为什么存在 async 关键字

    浏览 msdn 9 频道视频时 我发现以下未答复的评论 希望有人能解释一下 我不明白 async 关键字的意义 为什么不直接允许 任何时候方法返回任务时都会使用await关键字 就像迭代器一样 可以在任何返回 IEnumerable 的方法
  • clang 格式换行符在错误的位置

    给出以下代码行 get abc manager get platform status abc platform status sw update status fill update status actions allowed stat
  • 在 VS2017 下使用 Conan 和 CMake 项目进行依赖管理

    我正在尝试使用 CMake 与 VS2017 集成为 C 设置一个开发环境 以便在 Linux x64 下进行编译 为了更好地管理依赖关系 我选择使用 Conan 但我对这个软件还很陌生 我想知道让 VS2017 识别项目依赖关系的最佳方法
  • 如何使用 zlib 制作 .zip 文件

    我正在阅读zlib的文档 它相当详细 但我读到了这一行 输出数据将位于zlib格式 与 gzip 或zip formats http www zlib net zlib how html http www zlib net zlib how
  • 分段错误(核心转储)错误

    我的程序编译罚款 但在输入文件时出现 分段错误 核心转储 错误 我没有正确处理 ostream 吗 include
  • Subversion 和 Visual Studio 项目的最佳实践

    我最近开始在 Visual Studio 中处理各种 C 项目 作为大型系统计划的一部分 该系统将用于替换我们当前的系统 该系统是由用 C 和 Perl 编写的各种程序和脚本拼凑而成的 我现在正在进行的项目已经达到了颠覆的临界点 我想知道什
  • 在 C# 中生成 HMAC-SHA1

    我正在尝试使用 C 来使用 REST API API 创建者提供了以下用于 hmac 创建的伪代码 var key1 sha1 body var key2 key1 SECRET KEY var key3 sha1 key2 var sig
  • C++中delete和delete[]的区别[重复]

    这个问题在这里已经有答案了 可能的重复 C 中的删除与删除 运算符 https stackoverflow com questions 2425728 delete vs delete operators in c 我写了一个包含两个指针的
  • 如何将带有自定义分配器的 std::vector 传递给需要带有 std::allocator 的函数?

    我正在使用外部库 pcl 因此我需要一个不会更改现有函数原型的解决方案 我正在使用的一个函数生成一个std vector
  • HttpWebRequest vs Webclient(特殊场景)

    我知道这个问题之前已经回答过thread https stackoverflow com questions 1694388 webclient vs httpwebrequest httpwebresponse 但我似乎找不到详细信息 在
  • 无法解析远程名称 - webclient

    我面临这个错误 The remote name could not be resolved russgates85 001 site1 smarterasp net 当我请求使用 Web 客户端读取 html 内容时 出现错误 下面是我的代
  • 从成员函数指针类型生成函子

    我正在尝试简化 通过make fn 预处理参数的函子的生成 通过wrap 对于 arity 的成员函数n 生成函子基本上可以工作 但到目前为止只能通过显式指定成员函数的参数类型来实现 现在我想从它处理的成员函数类型生成正确的函子 struc
  • C# 委托责任链

    为了我的理解目的 我实现了责任链模式 Abstract Base Type public abstract class CustomerServiceDesk protected CustomerServiceDesk nextHandle
  • 2D morton 码编码/解码 64 位

    如何将给定 x y 的莫顿代码 z 顺序 编码 解码为 32 位无符号整数 生成 64 位莫顿代码 反之亦然 我确实有 xy2d 和 d2xy 但仅适用于 16 位宽的坐标 产生 32 位莫顿数 在网上查了很多 但没有找到 请帮忙 如果您可
  • 预处理后解析 C++ 源文件

    我正在尝试分析c 使用我定制的解析器的文件 写在c 在开始解析之前 我想摆脱所有 define 我希望源文件在预处理后可以编译 所以最好的方法是运行C Preprocessor在文件上 cpp myfile cpp temp cpp or
  • 从 Delphi 调用 C# dll

    我用单一方法编写了 Net 3 5 dll 由Delphi exe调用 不幸的是它不起作用 步骤 1 使用以下代码创建 C 3 5 dll public class MyDllClass public static int MyDllMet
  • C语言声明数组没有初始大小

    编写一个程序来操纵温度详细信息 如下所示 输入要计算的天数 主功能 输入摄氏度温度 输入功能 将温度从摄氏度转换为华氏度 独立功能 查找华氏度的平均温度 我怎样才能在没有数组初始大小的情况下制作这个程序 include
  • 带有私有设置器的 EFCore Base 实体模型属性 - 迁移奇怪的行为

    实体模型继承的类内的私有设置器似乎会导致 EFCore 迁移出现奇怪的问题 考虑以下示例 其中有多个类 Bar and Baz 继承自Foo 跑步时Add Migration多次命令 添加 删除private修饰符 生成的模式在多个方面都是
  • 服务器响应 PASV 命令返回的地址与建立 FTP 连接的地址不同

    System Net WebException 服务器响应 PASV 命令返回的地址与建立 FTP 连接的地址不同 在 System Net FtpWebRequest CheckError 在 System Net FtpWebReque

随机推荐

  • ctfshow web171-174

    Web 171 打开发现以下内容 看到了一个查询语句 于是认真看了一下 拼接sql语句查找指定ID用户 sql select username password from user where username flag and id GE
  • shell脚本中$#、$*、$@、$?、$0-n等含义一次性搞明白!!!

    一 Shell脚本变量的含义 1 表示执行脚本传入参数的个数 2 表示执行脚本传入参数的列表 不包括 0 3 表示进程的id Shell本身的PID ProcessID 即脚本运行的当前 进程ID号 4 Shell最后运行的后台Proces
  • 了解少儿编程和机器人编程的区别

    最近少儿编程已经成了炽手可热的培训 不过很多家长也在网上看到有很多网站也提到了机器人编程 这就让很多家长纳闷了 都是编程少儿编程和机器人编程有什么区别呢 现在我们就一起和南京小码王少儿编程培训机构来看下吧 一 少儿编程和机器人编程是什么 机
  • 看天气WeatherCan V1.0 ---气象数据分析系统web版

    版权声明 本文为CSDN博主 老郭1 的原创文章 遵循CC 4 0 BY SA版权协议 转载请附上原文出处链接及本声明 原文链接 https blog csdn net HZGJF article details 104772394 Wea
  • 电脑睡眠,休眠,关闭硬盘的区别

    最近在设置电源选项是对电脑睡眠 休眠 关闭硬盘不太了解 就上网查了一下 睡眠是一种节能状态 睡眠可保存所有打开的文档和程序 当您希望再次开始工作时 可使计算机快速恢复全功率工作 通常在几秒钟之内 使计算机进入睡眠状态就像是暂停DVD 播放器
  • 支付宝转账有“后悔药”了 遇诈骗这样做可冻结资金

    在支付宝上转完账猛然意识到可能是骗子 怎么办 今后 可以 一键 撤回了 昨天 在2018网络安全生态峰会上 蚂蚁金服集团副总裁芮雄文宣布支付宝的延时到账升级为2 0 如果遭遇诈骗 只要延时转账还未到账 及时报警就能冻结交易 一旦警方下达止付
  • C++ primer Plus 第十三章复习题

    1 派生类从基类哪里继承了什么 成员数据 还有一半的成员函数 公有成员和保护成员是可见得 私有成员不可见 2 派生类不能从基类哪里继承什么 构造函数 析构函数 赋值运算符 还有友元函数 都不能继承 3 假设baseDMA operator
  • 大数据毕业设计 深度学习股票预测系统 - python lstm

    文章目录 0 前言 1 课题意义 1 1 股票预测主流方法 2 什么是LSTM 2 1 循环神经网络 2 1 LSTM诞生 2 如何用LSTM做股票预测 2 1 算法构建流程 2 2 部分代码 3 实现效果 3 1 数据 3 2 预测结果
  • 什么是高内聚,低耦合?

    高内聚 低耦合是一个老生常谈的话题 所以拿出来说一下 我们在看Linux的一些资料 或者是在面试 又或者跟一个比较牛的大佬讨论技术的时候 可能会听到这个概念 所以 什么是高内聚 低耦合呢 高内聚 我们指的是认识的一群人或者一些东西分成的一类
  • 第一次从零到有开发项目

    在达内培训中 我们有项目峰会的活动 身为项目组长我组织了我的组员们从零到有进行了一个类CSDN的博客网站开发 目录 项目介绍 项目页面 总结 项目介绍 Kun吧 Kun吧是一个类csdn的学习技术社区 在这里可以交流学习技术 分享学习日常
  • 第十一届蓝桥杯C/C++回文日期

    include
  • 程序员转行做什么工作比较好?

    作为程序员来说 其实程序员的发展之路有多种 你可以结合自己的实际情况去做出相应的选择 程序员最基本的发展路线就是 从普通程序员做到熟练的开发者 接下来就会有多种选择 你可以选择做高级开发工程师 也可以成为一个一线熟练的开发人员 或者你也可以
  • Yolo v7的最简TensorFlow实现

    Yolo v7去年推出之后 取得了很好的性能 作者也公布了基于Pytorch实现的源代码 在我之前的几篇博客当中 对代码进行了深入的解析 了解了Yolo v7的技术细节和实现机制 因为我一直是用的Tensorflow 因此也想尝试把代码移植
  • c#数据结构转c++指针

    C string转c char C 使用IntPtr类型接受Marshal StringToHGlobalAnsi分配的内存 string sno 12345 IntPtr strsno Marshal StringToHGlobalAns
  • c++基础:循环练习案例展示

    1 猜数字 题目 系统随机生成一个1到100的数字 玩家进行猜测 如果猜错 提示玩家数字过大或过小 如果猜对恭喜玩家胜利 并且退出游戏 代码 include
  • Portainer -- Docker可视化管理工具

    http blog csdn net A632189007 article details 78779920 https portainer io install html https portainer readthedocs io en
  • vue3 computed

    前言 import computed from vue let aa computed gt 传回调函数 let aa computed 传对象 返回的类似一个ref包装的响应式对象 如果值是基本数据类型 需要 value进行拆箱 一 常规
  • Kaggle猫狗分类Pytorch CNN

    介绍 猫狗分类来源于Kaggle上的一个入门竞赛 https www kaggle com competitions dogs vs cats redux kernels edition overview 代码及解释 首先 导入一系列的库
  • python如何输出文字和变量_python中print怎么输出文字和变量

    本帖最后由 ButcherRabbit 于 2017 6 27 13 06 编辑 你说的知识点是 字符串拼接 n 3 print 还有多少 str n 机会 用加号拼接的话 记得拼接的元素必须同一个类型 如 还有多少 和 机会 属于字符串类
  • Cocos2dx-OpenGL ES2.0教程:编写自己的shader(2)

    在上篇文章中 我给大家介绍了如何在cocos2d x里面绘制一个三角形 当时我们使用的是cocos2d x引擎自带的shader和一些辅助函数 在本文中 我将演示一下如何编写自己的shader 同时 我们还会介绍VBO 顶点缓冲区对象 和V