以前 Simple2D 的渲染方法是先设置 Pass,然后添加顶点数据,相同 Pass 的顶点数据会合并在一起。当设置新的 Pass 时,将旧的 Pass 和对应的顶点数据添加到渲染数组中。最后在帧结束时遍历渲染数组,根据 Pass 设置 OpenGL 状态,绘制对应的顶点数据。

  这次改为更加简单的方法,类似状态机。设置混合状态(Blend)、着色程序(Shader Program)。渲染部分由 Graphics ContextRendererShader Program 组成:

  Graphics Context:图形上下文,设置渲染的混合状态(Blend)和使用的着色程序(Shader Program)。所有顶点数据的渲染都会使用 Graphics Context 当前使用的 Blend 和 Shader Program,直到 Graphics Context 设置新的 Blend 或 Shader Program。当 Blend 或 Shader Program 改变时会将上一个状态的顶点数据立即进行渲染,这是和以前 Simple2D 渲染方法的区别。

  这次去掉了裁剪测试、深度测试、模板测试,这些是非必须使用的功能。保留了混合是因为要渲染透明纹理。

  Renderer:渲染器,对顶点数据进行合并和管理。需要渲染顶点数据时,会将数据传递给 Graphics Context,Graphics Context 然后根据当前设置的 Blend 和 Program 绘制数据。

  Shader Program:着色程序,使用着色器可以实现炫酷的效果。这次重构的 Simple2D 可以使用标准的 Program 和自定义的 Program 渲染顶点数据,通过自定义 Program 实现标准 Program 不能实现的效果。

  因此 Simple2D 则可以去掉 Pass 类和 BlockAllocator 类,以前使用 Simple2D 渲染顶点数据时要为顶点分配空间,渲染后又要释放空间,中间的步骤十分麻烦。为什么要使用如此麻烦的方法,纯粹是我脑袋瓦特了。

  实现


  Renderer

  Renderer 内部有两个一定大小缓冲区,用于存储顶点数据和索引数据:

        static const int vertex_buffer_size =  * ;
static const int index_buffer_size = ;
char vVertexBuffer[vertex_buffer_size]; /* 用于合并顶点的缓冲区 */
uint32 vIndexBuffer[index_buffer_size]; /* 用于合并索引的缓冲区 */

  渲染一个正方形,需要 4 个顶点和 6 个索引。通过 AppendRenderData( ) 函数将顶点数据和索引数据传递给 Renderer,然后 Renderer 将顶点数据和索引数据拷贝到缓冲区中,当缓冲区的空间不足时,会调用 Flush( ) 函数将渲染数据提交给 Graphics Context 进行渲染。AppendRenderData( ) 是一个模板函数,通过模板的特性可以知道顶点结构的大小,从而进行拷贝操作:

     template<class Type>
void AppendRenderData(Type* vertex_data, int vertex_count, uint32* index_data, int index_count, PrimType type)
{
int total_vertex_count = vertex_buffer_size / sizeof(Type);
if ( total_vertex_count - nVertexCount < vertex_count || index_buffer_size - nIndexCount < index_count ) {
this->Flush();
} for ( int i = ; i < index_count; i++ ) {
vIndexBuffer[nIndexCount + i] = nVertexCount + index_data[i];
} char* data_header = vVertexBuffer + nVertexCount * sizeof(Type);
memcpy(data_header, ( char* ) vertex_data, vertex_count * sizeof(Type)); nVertexCount += vertex_count;
nIndexCount += index_count;
primType = type;
}

  当然也可以通过函数参数传递顶点结构的大小,但这样太麻烦了。

  如果要渲染纹理,同时希望减少 drawcall。因为当时局限于一个着色程序绑定一张纹理的想法,所以以前 Simple2D 通过合并相同纹理的顶点数据以达到一张纹理一个 drawcall,可以减小切换纹理而带来的开销。但是着色程序是可以绑定多张纹理的,可以在顶点数据中添加一个索引的数据,指定使用哪一个绑定的纹理,这样可以达到多张纹理一个 drawcall 了。下面是 Simple2D 定义的纹理渲染标准着色程序:

    const char* Sprite_Vertex = R"(
#version core layout(location = ) in vec3 Position;
layout(location = ) in vec2 Texcoord;
layout(location = ) in vec4 Color;
layout(location = ) in float Texindex; uniform mat4x4 MVPMatrix; out vec2 texcoord;
out vec4 color;
flat out int texindex; void main()
{
gl_Position = MVPMatrix * vec4(Position, 1.0f);
color = Color;
texcoord = Texcoord;
texindex = int(Texindex);
}
)";
    const char* Sprite_Fragment = R"(
#version core in vec2 texcoord;
in vec4 color;
flat in int texindex; uniform sampler2D Texture0;
uniform sampler2D Texture1;
uniform sampler2D Texture2;
uniform sampler2D Texture3;
uniform sampler2D Texture4;
uniform sampler2D Texture5;
uniform sampler2D Texture6;
uniform sampler2D Texture7;
uniform sampler2D Texture8;
uniform sampler2D Texture9;
uniform sampler2D Texture10;
uniform sampler2D Texture11;
uniform sampler2D Texture12;
uniform sampler2D Texture13;
uniform sampler2D Texture14;
uniform sampler2D Texture15; vec4 SampleTexture(int index)
{
switch( index )
{
case : return texture(Texture0, texcoord);
case : return texture(Texture1, texcoord);
case : return texture(Texture2, texcoord);
case : return texture(Texture3, texcoord);
case : return texture(Texture4, texcoord);
case : return texture(Texture5, texcoord);
case : return texture(Texture6, texcoord);
case : return texture(Texture7, texcoord);
case : return texture(Texture8, texcoord);
case : return texture(Texture9, texcoord);
case : return texture(Texture10, texcoord);
case : return texture(Texture11, texcoord);
case : return texture(Texture12, texcoord);
case : return texture(Texture13, texcoord);
case : return texture(Texture14, texcoord);
case : return texture(Texture15, texcoord);
default: return vec4(1.0, 1.0, 1.0, 1.0);
}
} void main()
{
gl_FragColor = SampleTexture(texindex) * color;
}
)";

  这个着色程序一次可以绑定 16 张纹理,这意味着你可以一个 drawcall 渲染 16 张纹理。所以在 Renderer 中设置一个纹理数组:

        int nCurrentTextureCount;
static const int nMaxNumberOfTexture = ;
GLuint vTextures[nMaxNumberOfTexture];

  储存渲染纹理,在渲染前绑定纹理到着色程序即可:

    void Renderer::BindTexture()
{
for ( int i = ; i < nCurrentTextureCount; i++ ) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, vTextures[i]);
}
}

  其中纹理存储在数组中的位置就是纹理在着色程序中的纹理索引。

  Shader Program

  Simple2D 内置有两个标准着色程序  Standard Program,分别用于渲染纹理和几何图形。如果有特别需要的话,可以自定义着色程序,为此需要处理 Uniform 数据,所以定义一个类 ProgramEffect 来管理 Uniform 数据。

  Graphics Context

  Graphics Context 用于设置 Blend 和 Shader Program 以及渲染顶点数据,实现较为简单。

  源码下载:Simple2D-20.rar

Simple2D-21(重构)渲染部分的更多相关文章

  1. 基于OpenGL编写一个简易的2D渲染框架-10 重构渲染器-Pass

    Pass,渲染通路,一个渲染通路指的是一次像素处理和一次顶点处理,也就是指的是一次绘制.简单来说就是顶点数据在渲染管线中走一遍最后绘制. 渲染粒子系统的粒子时,需要开启 OpenGL 的混合模式,并使 ...

  2. 基于OpenGL编写一个简易的2D渲染框架-09 重构渲染器-Shader

    Shader 只是进行一些简单的封装,主要功能: 1.编译着色程序 2.绑定 Uniform 数据 3.根据着色程序的顶点属性传递顶点数据到 GPU 着色程序的编译 GLuint Shader::cr ...

  3. 基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer

    假如要渲染一个纯色矩形在窗口上,应该怎么做? 先确定顶点的格式,一个顶点应该包含位置信息 vec3 以及颜色信息 vec4,所以顶点的结构体定义可以这样: struct Vertex { Vec3 p ...

  4. 基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构

    事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒 ...

  5. 基于OpenGL编写一个简易的2D渲染框架-12 重构渲染器-BlockAllocator

    BlockAllocator 的内存管理情况可以用下图表示 整体思路是,先分配一大块内存 Chunk,然后将 Chunk 分割成小块 Block.由于 Block 是链表的一个结点,所以可以通过链表的 ...

  6. Arnold+Shave 渲染毛发

    Arnold是一款基于真实物理光照算法和光线追踪算法的照片级渲染器,参与过多部好莱坞大片的制作,公司官网是:www.solidangle.com,官网上有很多效果图: 这里自己用一个球体测试了一下效果 ...

  7. 基于OpenGL编写一个简易的2D渲染框架-13 使用例子

    这是重构渲染器的最后一部分了,将会给出一个 demo,测试模板测试.裁剪测试.半透明排序等等: 上图是本次 demo 的效果图,中间的绿色图形展现的是模板测试. 模板测试 void init(Pass ...

  8. 学习opengl第一步

    有两个地址一个是学习opengl基础知识的网站, 一个是博客园大牛分享的特别好的文章. 记录一下希望向坚持做俯卧撑一样坚持下去. 学习网站:http://learnopengl-cn.readthed ...

  9. Zend_Framework_1 框架是如何被启动的?

    Zend Framework 1 是一个十年前的老框架了,我接触它也有两年了,现在来写这篇文章,主要原因是最近要写入职培训教程.公司项目基本上都是基于Zend1框架,即使现在要转 Laravel 也肯 ...

  10. Repaints and Reflows 重绘和重排版

    当浏览器下载完所有页面HTML标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据 一棵DOM树 表示页面结构 Normal 0 7.8 磅 0 2 false false fa ...

随机推荐

  1. ballerina 学习 三十一 扩展开发(二)

    上篇说了使用ballerina 语言开发扩展模块,对于注解类型的我们是需要使用java 语言进行 开发的 官方提供了一个hello 的demo可以参考 https://github.com/balle ...

  2. smarty学习——基本概念

    学习一种框架,我们最基本的就是掌握框架的思想,同时了解框架的基本语法. 1.对于定界符的了解 有的smarty模板标签都被加上了定界符. 默认情况下是 { 和},但它们是可被改变的.例如,我们假定你在 ...

  3. php 备份和恢复数据库

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. Codelf 变量取名

    Codelf 变量取名 可以看到别的变量是怎么命名的,站在巨人的肩膀上.

  5. hive 安装、知识点

    hive 查询语句: 语句 含义 show database; 或 show schemas; 查看数据库 show tables; hive显示所有表 set; 查看hive配置信息 问题:load ...

  6. socket、WebSocket

    WebSocket 协议本质上是一个基于TCP的协议,它由通信协议和编程API组成,WebSocket能够在浏览器和服务器之间建立双向连接,以基于事件的方式,赋予浏览器实时通信能力. socket本质 ...

  7. Jupyter和IPython

    Jupyter内核就是IPython(Interactive Python):你看到的按tab键能够自动提示/补齐都是IPython实现的. IPython其实不只限于IPython,其实你看到的ID ...

  8. bzoj4247挂饰——压缩的动态规划

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4247 1.dp之前要先按挂钩个数从大到小排序,不然挂钩一度用成负的也可能是正确的,不仅脚标难 ...

  9. JWT 基础教程

    原文地址:JWT 基础教程 博客地址:http://www.extlight.com 一.前言 针对前后端分离的项目,大多是通过 token 进行身份认证来进行交互,今天将介绍一种简单的创建 toke ...

  10. gcc gdb调试 (二)

    GDB的命令概貌——————— 启动gdb后,就你被带入gdb的调试环境中,就可以使用gdb的命令开始调试程序了,gdb的命令可以使用help命令来查看,如下所示: /home/hchen> g ...