读书笔记(五十四) 《游戏引擎架构》#4 低阶渲染器(5)

已发布在微信公众号上,点击跳转

背景:

作为游戏开发从业者,从业务到语言到框架到引擎,积累了一些知识和经验,特别是在看了几遍《游戏引擎架构》后对引擎架构的理解又深入了些。

近段时间有对引擎剖析的想法,正好借这书本对游戏引擎架构做一个完整分析。

此书用简明、清楚的方式覆盖了游戏引擎架构的庞大领域,巧妙地平衡了广度与深度,并且提供了足够的细节。

借助《游戏引擎架构》这本书、结合引擎源码和自己的经验,深入分析游戏引擎的历史、架构、模块,最后通过实践简单引擎开发来完成对引擎知识的掌握。

游戏引擎知识面深而广,所以对这系列的文章书编写范围做个保护,即不对细节进行过多的阐述,重点剖析的是架构、流程以及模块的运作原理。

同时《游戏引擎架构》中部分知识太过陈旧的部分,会重新深挖后总结出自己的观点。

概述:

本系列文章对引擎中的重要的模块和库进行详细的分析,我挑选了十五个库和模块来分析:

1.时间库 2.自定义容器库 3.字符串散列库 4.内存管理框架 5.RTTI与反射模块 6.图形计算库 7.资产管理模块 8.低阶渲染器 9.剔除与合批模块 10.动画模块 11.物理模块 12.UI底层框架 13.性能剖析器的核心部分 14.脚本系统 15.视觉效果模块

本篇内容为列表中的第8个部分的第5、6节。

正文:

简单回顾下前文

前文我们聊了下显卡在计算机硬件主板中的位置与结构,知道了CPU、GPU的通信介质,并简单介绍了手机上的主板结构。本篇开头对上一篇做一些内容补充,PC和手机的不同硬件组织,以及CPU与其他芯片的通信过程。

下面我们开始这篇内容

本次内容会围绕GPU来写,从硬件架构到软件驱动再到引擎架构,目标是帮大家理解GPU硬件的运作原理,理解图形接口的架构,理解引擎低阶渲染器的架构。

目录:

主板结构中的显卡

GPU功能发展史

GPU与CPU的差异

GPU硬件特点

图形驱动程序架构

引擎低阶渲染架构

前面我们说了关于GPU硬件上的原理和运作机制,下面我们来讲一讲软件上的架构,尤其是渲染架构。

指令和数据从CPU到GPU最终到帧缓冲的这个过程中,有三种类型的架构,第一种是图形驱动程序的架构,第二种是引擎上的低阶渲染器架构,第三种是GPU上的软件架构。

这三种架构在互相配合,同时也是三个模块互相调用的过程。这也是为什么前面要提到这么多CPU与GPU硬件交互过程的原因。

为了更好的了解这三者,我又研读了一遍《OpenGL ES3.0 编程指南》,通过对OpenGL接口的解读和分析,逐步剖析图形驱动程序架构、引擎低阶渲染器架构、以及GPU软件架构,我认为这种方式是最合适的。

一、图形驱动程序架构

我们使用的图形接口如OpenGL、Metal、DX的原理是基于驱动程序接口做的封装。因此,图形驱动调用接口有三步骤: 检查调用参数、检查硬件是否支持 接着调用硬件驱动程序接口 将数据推入缓冲Buffer

数据被推入缓冲,后续的详细路线如下图:

(GPU中的数据流向图)

GPU有线程来执行程序,线程通过Core来执行指令,每个线程的程序都是一样的,只是数据不同。 因此很好理解,GPU中有许多线程,每个线程执行的指令是一样的,从三角形处理到光栅化以及片元处理,都是顺序执行的指令。因此数据流也跟着顺序的指令走。最后到达帧缓存。

图形API适配了许多GPU驱动程序的接口,驱动程序先检查参数,再将指令推入队列,再刷新时将队列地址发送给GPU,GPU开始处理队列,由线程处理队列中的每个指令,每个线程拥有同样的程序,数据不断被处理,最后到达帧缓存。

在最上层的图形接口之上,引擎低阶渲染器会调用很多不同类型的图形接口,将数据塞入到缓冲中。下面就来详细描述下,引擎低阶渲染器的架构是怎样的。

二、引擎低阶渲染器架构

前面我们说,图形API封装了硬件驱动程序,会先检查再调用。

实际上,低阶渲染器也是一个封装图形API接口的程序,不同的是它封装的更适合渲染对象。

其封装的目的是让散乱的图形API变得更方便使用,同时还能够优化掉重复的计算。

这里首先我们来看下OpenGL ES图形API中的接口类型,通过了解图形API接口,能够想象出如果是我们自己来做低阶渲染器架构我们应该如何封装。所以,了解熟悉图形API对于低阶渲染器的架构非常重要。

下面是一些OpenGL的接口描述和统计,有些枯燥,如已熟悉可以直接跳到描述架构部分。

OpengGL接口描述与统计:

状态开启和关闭,包括开启三角绘制、开启混合、开启模板等
glEnableXXX、glDisableXXX
获取或查询某数据,包括获取数据,获取日志,获取结果等
glGetXXX
绑定数据,包括绑定数据到着色器等
glBindXXX
开始和结束某处理,包括开始变换反馈,查询数据等
glBeginXXX
glEndXXX
清除缓冲
glClearXXX
创建某对象,包括缓冲,数组,纹理,采样器对象等等
glGenXXX,glCreateXXX
删除某对象,包括着色器对象,数组,纹理对象,采样器对象等等
glDeleteXXX
还有其他的如:
glDrawXXX,绘制相关
glCopyXXX,拷贝复制相关
glCheckXXX,检查相关
glIsXXX,判断是否有效
......

下面按相关性划分为:

(着色器相关接口图)

着色器相关:
创建着色器:glCreateShader
加载着色器代码:glShaderSource
编译着色器:glCompileShader
创建着色器程序:glCreateProgram
绑定着色器:glAttachShader
解绑着色器:glDetachShader
链接着色器程序:glLinkProgram
使用着色器程序:glUseProgram
删除着色器程序:glDeleteProgram

(视口相关接口图)

视口相关:
设置视口:glViewport
设置视口裁剪深度:glDepthRangef
清除颜色缓冲:glClear
置换缓冲区:eglSwapBuffers

(顶点相关接口图)

顶点相关:
通过创建缓冲区,可以把顶点数据加载到缓冲区,从而绘制顶点。

加载顶点属性:glVertexAttribXXX
绘制三角形:glDrawArrays,glDrawElements
启用/禁用顶点属性数组:glEnable/DisableVertexAttribArray
申请缓冲:glGenBuffers
指定缓冲:glBindBuffer
向缓冲填入数据:glBufferData,glBufferSubData
删除缓冲:glDeleteBuffers
创建顶点数组对象:glGenVertexArrays
绑定顶点数组:glBindVertexArray
删除顶点数组:glDeleteVertexArrays
映射并返回缓冲区数据:glMapBufferRange
取消映射缓冲区:glUnmapBuffer
刷新映射缓冲区:glFlushMappeBufferRange
复制缓冲区数据:glCopyBufferSubData

(图元绘制相关接口图)

图元绘制相关:
绘制几何形状对象:glDrawArrays, glDrawElements, glDrawRangeElements,
绘制几何Instance:glDrawArraysInstanced, glDrawElementsInstanced
绘制线段:glLineWidth

(光栅化相关接口图)

光栅化相关:
指定正面顺序:glFrontFace
剔除反面:glCullFace
多边形偏移:glPolygonOffset
遮挡查询:glBeginQuery, glEndQuery, glGenQueries, glDeleteQueries, glGetQueryObjectuiv
遮挡查询用查询对象来跟踪通过深度测试的片段或样本。

顶点着色器相关:

内建特殊变量:
gl_VertexID,当前顶点整数索引
gl_InstanceID,当前图元实例编号
gl_Position,输出顶点
gl_PointSize,点精灵尺寸
gl_FrontFacing,是否正面的布尔值
内建常量:
gl_MaxVertexAttribs,顶点属性最大数量
gl_MaxVertexUniformVectors,使用vec4统一变量的最大数量
gl_MaxVertexOutputVectors,输出向量的最大数量
gl_MaxVertexTextureImageUnits,可用纹理单元的最大数量
gl_MaxCombinedTextureImageUnits,顶点和片元着色器中可用纹理单元的最大数量总和

(变换反馈相关接口图)

变换反馈TransformFeedback相关:
glTransformFeedbackVaryings,指定变换反馈时捕捉的顶点属性
glBeginTransformFeedback,开始变换反馈(需先创建变换反馈缓冲区,再绑定到顶点索引)
glEndTransformFeedback,结束变换反馈

(纹理相关接口图)

纹理相关:
4种纹理类型:2D纹理、2D纹理数组、3D纹理、立方图纹理
其中2D纹理数组和3D纹理有点相似,2D纹理数组常用于帧动画。两者区别为过滤和mipmap不同。

glGenTextures,创建纹理对象
glDeleteTextures,删除纹理对象
glBindTexture,绑定到一个特定的纹理目标
glTexImage2D,加载2D和立方图的纹理数据
glTexSubImageXXX,加载部分纹理图像数据
glTexImage3D,加载3D纹理数据
glPixelStorei,设置解包对齐
glTexParameteri,设置贴图的过滤模式
glGenerateMipmap,自动生成mip贴图
glActiveTexture,设置当前的纹理单元,以便后续将纹理绑定该单元
glCompressedTexImageXXX,加载2D、立方图、3D等纹理的压缩图像数据(ETC、ASTC等压缩格式)
glCompressedTexSubImageXXX,加载部分压缩纹理图像

glReadBuffer,设置拷贝图像数据来源的颜色缓冲区
glCopyTexImageXXX,从颜色缓冲区拷贝数据到纹理
glCopyTexSubImageXXX,拷贝部分颜色缓冲区的数据到纹理

(采样器相关接口图)

采样器相关:
glGenSamplers,生成采样器对象
glDeleteSamplers,删除采样器对象
glBindSampler,绑定纹理到采样器对象
glSamplerParameterXXX,设置采样器对象参数
glTexStorageXXX,分配纹理内存

片段着色器相关:

很久以前在固定功能管线中,使用3种输入:顶点颜色插值、纹理颜色、常量颜色,再使用一些公式的组合实现有趣的特效,包括:AB、A+B、A+B-0.5、AC+B*(1-C)、A-B等等。

支持可编程管线后,我们可以通过可编程管线来实现固定功能管线的效果。

现在的可编程管线的输入由四部分构成:
顶点属性插值(顶点上的颜色、uv、法线等)
统一变量(全局常量)
采样纹理
代码常量
内建特殊变量:
gl_FragCoord,片段的窗口相对坐标(可用于噪声贴图计算)
gl_FrontFacing,是否正面朝向
gl_PointCoord,点精灵的纹理坐标
gl_FragDepth,输出变量,覆盖片段的固定功能深度值(会导致深度测试优化失效)
内建常量:
gl_MaxFragmentInputVectors,输入的最大数量
gl_MaxTextureImageUnits,可用纹理图像单元的最大数量
gl_MaxFragmentUniformVectors,使用vec4统一变量项目的最大数量
gl_MaxDrawBuffers,多重渲染目标(MRT)的最大支持数量
gl_(Min/Max)ProgramTexelOffset,通过内建ESSL函数texture*Offset()偏移参数支持的最大和最小偏移量

(缓冲区相关接口图)

缓冲区相关:

缓冲区有三种,颜色缓冲区,深度缓冲区,模板缓冲区。

缓冲清除相关:
glClear,清除指定缓冲区
glClearColor,清除颜色缓冲区中的指定颜色
glClearDepth,清除深度缓冲区中的指定深度
glClearStencil,清除模板缓冲区中的指定掩码
glClearBufferXXX,清除指定缓冲区中的部分区域
缓冲写入相关:
glColorMask,像素写入颜色缓冲区时,哪些分量会被更新
glDepthMask,深度写入深度缓冲区时,哪些深度可以修改
glStencilMask,掩码写入模板缓冲区时,哪些掩码可以被修改
glStencilMaskSeparate,根据正面和背面的图元使用不同的掩码
glDrawBuffers,渲染指定颜色数组到多重渲染目标中
(多重渲染目标允许一次渲染多个颜色缓冲区,从而实现高级渲染算法,如延迟渲染)

(裁剪与测试相关图)

模板缓冲裁剪与测试:
glScissor,指定裁剪矩形区域
glStencilFuncXXX,用指定公式指定值指定掩码测试比较模板缓冲
glStencilOpXXX,将测试结果用于深度缓冲区的操作

深度缓冲测试:glDepthFunc,设置深度测试的运算公式

(混合相关接口图)

混合相关:
glBlendFuncXXX,设置混合系数
glBlendColor,设置常量颜色
glBlendEquationXXX,设置运算公式
颜色缓冲区中读写像素:
glReadPixels,从颜色缓冲区中取出数据返回到指定数组中,
数据传输时会启动DMA传输,此时CPU会空出来。

(帧缓冲区对象相关接口图)

帧缓冲区对象(FBO)相关:
可用于颜色、深度、模板纹理或渲染目标。
glGenRenderbuffers,分配n个渲染缓冲区对象,返回到指针中。
glBindRenderbuffer,绑定渲染缓冲区对象
glRenderbufferStorageXXX,指定渲染缓冲区对象大小和格式
glBindFramebuffer,设置当前帧缓冲区对象(渲染目标)
glFramebufferRenderbuffer,将一个渲染缓冲区对象连接到帧缓冲区附着点
glFramebufferTextureXXX,将纹理的某个mip级别连接到帧缓冲附着点
glCheckFramebufferStatus,验证帧缓冲区对象是否完整

glBlitFramebuffer,高效的将矩形区域的像素从一个帧缓冲区复制到另一个
glInvalidate(Sub)Framebuffer,让整个帧缓冲区或子区域失效
glDeleteRenderbuffers,删除指定渲染缓冲区对象,先断开缓冲区对象才能删除
glDeleteFramebuffers,删除指定帧缓冲对象

未完,最近忙新书和工作以及演讲训练,后续继续写……

参考资料:

《OpenGL ES3.0 编程指南》DanGinsburg等著

《GPU 引擎》 https://docs.microsoft.com/zh-tw/windows/win32/direct3d12/user-mode-heap-synchronization 《CPU体系结构》 https://my.oschina.net/fileoptions/blog/1633021

· 读书笔记, 前端技术

感谢您的耐心阅读

Thanks for your reading

  • 版权申明

    本文为博主原创文章,未经允许不得转载:

    读书笔记(五十四) 《游戏引擎架构》#4 低阶渲染器(5)

    Copyright attention

    Please don't reprint without authorize.

  • 微信公众号,文章同步推送,致力于分享一个资深程序员在北上广深拼搏中对世界的理解

    QQ交流群: 777859752 (高级程序书友会)