图形管线基础(一)

图形管线基础(一)


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

本片文章总结以往对图形管线的学习


一、Opengl基础概念

OpenGL(英语:Open Graphics Library,译名:开放图形库或者“开放式图形库”)是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。Opengl起源于美国硅谷公司(Silicon Graphics Inc.,SGI)及其IRIS GL。GL表示“图形库”。
OpenGL规范由1992年成立的OpenGL架构评审委员会(ARB)维护。ARB由一些对创建一个统一的、普遍可用的API特别感兴趣的公司组成。根据OpenGL官方网站,2002年6月的ARB投票成员包括3Dlabs、Apple Computer、ATI Technologies、Dell Computer、Evans & Sutherland、Hewlett-Packard、IBM、Intel、Matrox、NVIDIA、SGI和Sun Microsystems,Microsoft曾是创立成员之一,但已于2003年3月退出。
Opengl的目的是在应用程序和底层图形的图形子系统之间提供一个抽象层,而该子系统通常有一个或多个自定义、高性能处理器组成的硬件加速器,具有专用内存、显示输出等。该抽象层可以使程序不必知道是谁制作了图形处理器。Opengl的设计原则是必须在抽象层过高和过低之间取得平衡。
目前的GPU由大量小型可编程处理器组成,这些处理器被称为光影核心(shader core),其运行的迷你程序被成为着色器(shader),以下展示了一个精简的图形管线示意图
在这里插入图片描述
经典永不过时,这张图详细的涵盖了opengl的图形管线,蓝色块表示各式各样的buffer来自于Opengl管线,绿色块表示固定函数不可编程阶段,黄色块表示可编程阶段。T,B分表表示贴图绑定以及Buffer绑定。这两者是Opengl主要的数据存储形式,分别对应这缓冲和纹理,后面会详细展示。本章节会主要讲中间这一块,右上角是 computer shader,后面单独开一个篇章说明。
在Opengl中,基本的渲染单元成为基元(primitive)。Opengl支持多种基元,但三种可渲染基元为点、线、和三角形。我们看到的屏幕上所渲染的所有东西都是线和三角形的集合。应用一般会把复杂的表面分解成许多三角形,然后发送给opengl,通过光栅器(rasterizer)的硬件加速器进行渲染。光栅器是专门用来将三维形式的三角形转换为一个系列需要在屏幕上进行渲染的像素硬件。对于三维坐标系来说,图形管线拆分为两个主要部分。第一部分是前端(front end)处理顶点和基元,最后把他们组成为点,线和三角形传递给光栅器。这个过程被称为基元组装。经过光栅器处理后,几何图形已经从本质上的向量被转换变成大量独立像素。这些都是交给后端(back end)处理的,包括深度测试,模板测试,片段着色,混合以及更新输出图像。
在这里插入图片描述

二、OpenGL管线流程

1.顶点着色器

顶点着色器是Opengl管线中的第一个可编程阶段,也是图形管线中唯一的必须阶段。在顶点着色器开始运行之前,先运行一个叫做顶点获取(vertex fetching)的固定函数阶段,也叫做顶点拉取(vertex pulling)。该阶段自动想顶点着色器提供输入。数据包括元素数组缓冲区(element array buffer),顶点缓冲对像(vertex buffer object),绘制间接缓冲区(draw indirect buffer)

2.细分曲面

细分曲面是将高阶基元(opengl中成为贴片[patch])分解为许多更小,更简单的基元进行渲染的过程。例如拆分为多个三角形。Opengl包含一个固定功能的、可配置的细分曲面引擎。可将多个四边形、三角形和线分解为大量更小的点,线或三角形,这些点、线或三角形可有管线中常规光栅器硬件直接使用。从逻辑上讲,细分曲面阶段位于Opengl管线中顶点着阶段之后,主要由三部分组成:细分曲面控制器(Tessellaction Control Shader)、固定函数细分曲面引擎(Tessellation Primitive Gen)和细分曲面评估着色器(Tessellation Eval Shader)

2-1.细分曲面控制着色器

三个细分曲面阶段中的第一个是细分曲面控制着色器。此着色器从顶点着色器获取输入数据,主要负责完成两项任务,确定即将发送到细分曲面引擎的细分曲面级别,以及生成数据发往细分曲面评估着色器,该着色器会在出现细分曲面时开始运行。
Opengl中细分曲面的工作原理是将高阶面,所谓的贴片[patch]分解成点、线和三角形。每个贴片都是由多个控制点组成。
开始细分曲面时,顶点着色器在每个控制点运行一次,而细分曲面控制着色器在控制点组上成批运行,每批运行量与每个贴片的顶点数量一致。也就是说顶点被用作控制点,顶点着色器的结果将成批传递到细分曲面控制着色器作为其输入数据。每个贴片的控制点数可以更改,每个贴片的默认控制点数量是3个,因此细分曲面控制着色器输出的控制点数量与其使用的控制点数量不同。

2-2.细分曲面引擎

细分曲面引擎是Opengl管线的固定功能部分,主要接收贴片为代表的高阶面并将这些曲面分解为更为简单的基元。细分曲面引擎接受贴片之前,区分曲面控制着色器会处理传入的控制点并设置分解贴片使用的细分曲面因子。细分曲面引擎生成输出基元后,细分曲面评估着色器会获取表示这些基元的顶点。细分曲面引擎负责生成调用细分曲面评估着色器所需的参数,然后该着色器会使用这些参数来转换生成的基元,并准备好对他们进行光栅化。

2-3.细分曲面评估着色器

固定功能细分曲面引擎开始运行后会产生大量输出顶点,表示其生成的基元。这些顶点将传递给细分曲面评估着色器。细分曲面评估着色器对细分曲面单元产生的每个顶点运行调用。如果细分曲面级别较高,细分曲面镶嵌评估着色器将运行很多次。其工作内容就像顶点着色器一样对生成的顶点进行赋值。

2-4.Unity Shader 中的应用

Unity Shader中引入 #include “Tessellation.cginc” 文件
Tessellation.cginc为我们提供了三个曲面着色函数
UnityDistanceBasedTess() 基于与相机的距离生成细分因子
UnityEdgeLengthBasedTess()基于边缘长度生成细分因子
UnityEdgeLengthBasedTessCull()基于边缘长度并带有剔除功能生成细分因子
具体原理操作建议跳转学习
https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/

3.几何着色器

理论上几何着色器是前端运行的最后一个着色器阶段,在顶点和细分曲面阶段之后,光栅化之前。几何着色器对每个基元运行一次,可以访问构成该处理基元的所有输入顶点数据。几何着色器在着色器各个阶段也较为独特。他能够以编程的方式增减流经管线的数据量。细分曲面着色器也能够增减管线工作量,但是只能通过设置贴片的细分曲面水平间接实现,相比之下几何着色器可间接生成顶点发送到基元装配和光栅化。
几何着色器以完整的图元(Primitive)作为输入数据,输出经过我们处理后的图元。我们可以在几何着色器里面去创建或销毁顶点,完全控制输出的图元个数与类型。几何着色器的输入图元和输出图元都可以为点、线、面任一种。

3-1.Unity Shader 中的应用

代码如下(示例):

#pragma geometry geom
[maxvertexcount(1)]
 void geom(point v2g input[1],inout PointStream<g2f> outstream)
 {
    
    
     g2f o = (g2f)0; 
     o.vertex = input[0].vertex;
     o.uv = input[0].uv;
     outstream.Append(o);
}

#pragma geometry geo
指定几何着色器的方法名
[maxvertexcount(num)]
几何着色器方法名上必须添加。其用来定义几何着色器中输出顶点的最大数量,输出顶点可以每次都不同,但是不能超过这个值。
void geo(triangle v2g p[3], inout LineStream stream) { }
几何着色器方法,返回类型为void。
triangle v2g p[3]为输入图元,triangle表示输入图元类型为三角形。输入图元类型如下。
在这里插入图片描述
在这里插入图片描述
inout LineStream stream为输出图元。inout为关键词,LineStream表示输出图元类型为线,g2f为我们自定义的几何着色器到片元着色器的结构体。
在这里插入图片描述
具体可跳转
https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-geometry-shader#return-value

举个例子(网格效果)

Shader "Unlit/Gemo3Shader"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        _Color ("Color",Color) = (1,1,1,1)       
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Opaque" }

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom

            #include "UnityCG.cginc"

            struct a2v
            {
    
    
                float4 vertex : POSITION;               
                float2 uv : TEXCOORD0;
            };

            struct v2g
            {
    
    
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct g2f
            {
    
    
                float2 uv : TEXCOORD0;              
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;         

            v2g vert (a2v v)
            {
    
    
                 v2g o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            [maxvertexcount(3)]//只有顶点输出,那就定为1
            void geom(triangle v2g input[3],inout LineStream<g2f> output)
            {
    
    
               g2f o = (g2f)0;
                o.vertex = input[0].vertex;
                o.uv = input[0].uv;
                output.Append(o);

                o.vertex = input[1].vertex;
                o.uv = input[1].uv;
                output.Append(o);

                o.vertex = input[2].vertex;
                o.uv = input[2].uv;
                output.Append(o);
                output.RestartStrip();

            }

            fixed4 frag (g2f i) : SV_Target
            {
    
    
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                col *= _Color;
                return col;
            }
            ENDCG
        }
    }
}

4.基元装配

管线前端运行后(包括顶点着色、曲面细分、几何着色),管线的固定功能部分会执行一系列任务,这些任务获取顶点表示的场景并将其转化为一系列像素,这些像素需要着色写入屏幕。此过程的第一步是基元装配,即将顶点集聚为线和三角形。点也会进行基元装配,但在该例中这一过程微不足道。最后基元中判定为潜在可见的个部分会发送到一个名为光栅器的固定功能子系统。该子系统会判断那些像素会被基元(点,线或三角形)覆盖并将此像素发送到下一阶段,即片段着色。

5.裁剪

一旦各顶点构成基元后,将会针对可显示区域进行裁剪,此时可显示区域通常是窗口或屏幕。
虽然前端的输出是四分量齐次坐标,但是裁剪发生在笛卡尔坐标系统中。因此,为了从齐次坐标转化到笛卡尔坐标,Opengl执行了透视分割,即将所有位置四分量用最后一个w分量分割。这样就可以将顶点从齐次坐标空间投影到笛卡尔坐标空间,保持w为1.0.投影分割后产生的位置在标准化空间中。Opengl中可见的标准化设备空间区域是x轴和y轴上从-1.0到1.0以及z轴上从0到1.0的体积。用户可以看到此区域内包含的任何几何图形,其范围外的一切应被丢弃。此体积的六面有三位空间的平面组成。因为一个平面将一个坐标空间分为两部分,该平面两侧的体积成为半空间。
在这里插入图片描述
在将基元传递到下一阶段前,Opengl通过判断各基元顶点在平面的哪一侧而执行裁剪。每个平面都有一个外侧和一个内侧。如果一个基元的所有顶点都在一个平面外侧,则这个基元就要被丢弃。如果基元的所有顶点都在平面内侧,则这个基元原封不动的传递下去。若基元只是部分可见则需要特殊处理。关于裁剪算法下面在屏幕裁剪中会介绍二维的,三维的裁剪可借此拓展,这里不展开了。
在这里插入图片描述

5.视口转换

裁剪后,几何图形的所有顶点坐标都在x轴和y轴的-1.0到1.0区域内。而在z轴上,该顶点位于0.0到1.0区域内,也就是已知的标准化设备。我们绘制的窗口坐标通常是坐下的(0,0)到(w- 1, h - 1),其中w和h分别表示窗口的像素宽度和高度。从标准化设备坐标到窗口坐标的过程叫视口变换。
在这里插入图片描述
转换形式如下
在这里插入图片描述
其中Xw,Yw, Zw是顶点在窗口空间内的结果坐标,Xd、Yd、Zd是顶点在标准化设备内的传入坐标,Px和Py是以像素为单位的视口宽度和高度,n和f是z坐标内的近平面距离和远距离平面。最后Ox, Oy, Oz表示视口原点。

6.剔除

进一步处理三角形之前,可选择使其经过一个成为剔除的阶段。该阶段可确定三角形是面向观察者还是背向观察者,并根据计算结果决定是否实际进行绘制。如果三角形面向观察者,则视为正面,否则是为背面。通常会抛弃背面三角形,因为当对象封闭时,任何背面三角形都会被其他三角形隐藏。
为了判定三角形是正面还是背面,Opengl将判定其在窗口空间内的有向面积。一种判定三角形面向的方法是取两边的叉积。如果其结果为+,则三角形视为正面反之为背面。

6.光栅化

光栅化是指判断那些片段可被线或三角形等基元覆盖。Opengl将在窗口坐标内为三角形设定界限框,并对其中所有片段进行测试以判断片段在三角形内还是三角形外。为了做到这一点,Opengl会将三角形的三条边作为一个半空间,将窗体分割为两部分。位于所有三条边内部的片段视为三角形内,而位于三边任一边外部的片段视为三角形外。
在这里插入图片描述

6.片段着色器

片段着色器是Opengl图形管线的最后一个可编程阶段。该阶段负责确定各片段的颜色。然后将片段发送到帧缓存,以便合成窗口。光栅器处理基元后,会产生一个需要着色的片段列表并将此清单传递到片段着色器。到这一步,管线的工作量会有一个爆炸性增长,每个三角形都可能产生几百、几千甚至几百万个片段。

总结

以上就是就是图形管线概念的第一部分分享。第二篇会分享下一步帧缓存运算的概念(即逐片元操作),涉及部分算法,篇幅较长,所以选择分两部分说明。

引用

OpenGL超级宝典
Unity Shader 入门精要

猜你喜欢

转载自blog.csdn.net/weixin_39289457/article/details/125355385