一开始学习计算机图形学的小伙伴们肯定对于渲染管线有一点迷茫,至少当初我就有点迷茫,为了能对后来对计算机图形学感兴趣的萌新小伙伴起到一些帮助,在这里简单讲解一下渲染管线(Render-pipeline)


该文章还有很多不足有待以后完善。如果大家有什么修改意见或问题,欢迎留言。我会定期解答。谢谢大家的关照。


2020/6/12 更新

最近翻看了一下该篇文章。首先严格意义上该篇文章并没有覆盖所有的管线阶段(细分着色器、光栅化等并没有讲解)。只是将其中最常用,最核心的坐标变换拿了出来(其他部分可以说是不可编程的)。你可以理解该篇文章讲解的是模型等数据是如何通过管线并最终渲染到屏幕上的过程讲解。


2020/6/12 更新

为什么会有渲染管线?渲染管线是用来做什么的?

NVIDIA现场形象展示CPU和GPU工作原理上的区别

在计算机图形学初期并没有管线之类的东西,当时连GPU都没有,绘图是通过CPU进行的。当你需要画一个模型时需要CPU每次访问模型上的一个顶点数据,之后挨个访问一遍即可。可以看出这样做效率会非常低。由于工厂中的生产管线效率理想,从此得到启发出现了以管线图形绘制(因为每一个点其实是独立的可以单独运算)的方法。GPU也是基于此设计的(GPU拥有非常多的小处理器核心,数量远远超过CPU的核心数)。

你要问我渲染管线最基本的用途是什么?我会说:是用于定义GPU上的计算方法和流程的。一个比较直观的例子就是 当我们在一个坐标系中任意定一个点A,任意定一个相机坐标B,现在我想将该点A变换到以B代表的屏幕上(屏幕可能只能识别坐标值为0到1之间的点)该点为C,但是A点可能会有任意位置(也许相机跟本就看不到该点)。那么问题来了:我如何才能通过A和B将A映射到C呢?换句话就是:世界中任意一点如何才能知道该点在我眼睛视平面上的坐标?这就需要坐标变换了,就如该篇文章之后要讲的内容。


2020/9/19 更新

最近再次翻阅了该文章,发现有些矩阵是直接给出的,没有说明前因后果,在这里简要说明一下。

图形学中的大将:矩阵

图形学中为什么要使用矩阵?矩阵是用来做什么的?

其实数学意义上的矩阵用途很多,大学时书上讲可以用来解多元方程组,但是在图形学中矩阵最核心的用法是用来做空间变换(当时得知矩阵还能这么用的我当局拍手叫绝!矩阵还能这么用!?妙!妙!妙!)。

那么问题来了:图形学中一定要使用矩阵来做空间变换吗?

说实话,不用矩阵而使用最常见的列方程也能做到空间变换来达到同样的目的。

那为什么都用矩阵呢?

在某些架构中矩阵能够提高计算效率,特别是在GPU上的矩阵运算会比CPU快很多。(比如使用计算着色器或者CUDA等并行计算)。还有就是矩阵说起来高大上,吹牛逼用的。

什么是空间变换?

空间变换就是在两个不同空间中表示同一个点的各种状态进并通过某些手段修改各种状态。举例说明:三个人并排坐,从左到右分别是A,B,C。现在从A的视角来描述B就是B在A的右边,现在从C的视角来描述B就是B在C的左边。B的位置并没有改变但是从A和C的各自角度看B,一个看到的是右一个看到的是左,发生了不一致现象。其实这就是最核心的坐标变换。这就是我后文说的:计算机图形学的坐标系系统中各个系统之间都是相对的!!!(都是一个点,只是不同的描述方式)

图形学中矩阵一般怎么用?

图形学中矩阵一般是做乘法。这里简单讲解一下平移矩阵和缩放矩阵的原理。

对于平移,假如说点A的位置为(x,y,z),我现在想在x轴平移a个单位,y轴平移b个单位,z轴平移c个单位。最简单的做法是(x+a,y+b,z+c)。聪明的先贤们想出了一个牛逼做法:使用矩阵来平移。用矩阵来表示平移如下:

V_{xyz}=\begin{bmatrix} 1& 0& 0& a\\ 0& 1& 0& b\\ 0& 0& 1& c\\ 0& 0& 0& 1 \end{bmatrix} * \begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}

注:像这种4x4的矩阵在图形学中很常见,叫做齐次坐标矩阵。

V_{xyz}为变换后的坐标位置,将该式展开为:

\begin{Bmatrix} V_{x}=x\cdot 1+y\cdot 0+z\cdot 0+1*a=x+a\\ V_{y}=x\cdot 0+y\cdot 1+z\cdot 0+1*b=y+b\\ V_{z}=x\cdot 0+y\cdot 0+z\cdot 1+1*c=z+c \end{Bmatrix}

即可得到变换后的坐标(x+a,y+b,z+c)。

对于缩放,假如说点A的位置为(x,y,z),我现在想在x轴缩放a个单位,y轴缩放b个单位,z轴缩放c个单位。最简单的做法是(x*a,y*b,z*c)。使用矩阵表示缩放变换即为:

S_{xyz}=\begin{bmatrix} a& 0& 0& 0\\ 0& b& 0& 0\\ 0& 0& c& 0\\ 0& 0& 0& 0 \end{bmatrix} * \begin{bmatrix} x\\ y\\ z\\ 1 \end{bmatrix}

S_{xyz}为变换后的坐标位置,将该式展开为:

\begin{Bmatrix} S_{x}=x\cdot a+y\cdot 0+z\cdot 0+1*0=ax\\ S_{y}=x\cdot 0+y\cdot b+z\cdot 0+1*0=by\\ S_{z}=x\cdot 0+y\cdot 0+z\cdot c+1*0=cz \end{Bmatrix}

即可得到变换后的坐标(x*a,y*b,z*c)。

如果平移之后又缩放了的话,只需要将平移和缩放矩阵相乘即可得到想要的变换矩阵。

平移和缩放只是变换的一部分,还有很多复杂的变换,比如投影变换,旋转变换,错切变换,圆幕变换等。

顺带一提:平移矩阵*缩放矩阵*旋转矩阵是物体空间到世界空间变换的核心。其实图形学中矩阵还有很多细节没有说明比如矩阵相乘的顺序不同变换结果也会不同,网上有很多不错的文章。在这里就不一一讲解了。


物体空间->世界空间->观察空间->裁剪空间->屏幕空间


注意:计算机图形学的坐标系系统中各个系统之间都是相对的!!!(都是一个点,只是不同的描述方式)

1.物体坐标和物体模型(object space<物体空间>)

什么是物体坐标呢?讲理论不如举个栗子!!!嘿嘿~~

一个单独物体的物体坐标

如图:这是3dsMax下建模的一个长度为1的正方体(单位立方体),轴心在物体重心,也就是正中心,右手坐标系(图中的这种坐标系专业一点的名字)。

                              

不难想象A,B,C三点的坐标为A(-0.5,0.5,0.5)B(0.5,0.5,0.5) C(-0.5,-0.5,-0.5),这些A,B,C点和所有该物体的点的集合就是我们说的物体坐标(严格上说是物体上的点相对于自身原点的坐标)。

两个至多个物体的物体坐标

轴心,轴向不变,两个一样的单位正方体,A和A'的物体坐标不难想象A(0.5,0.5,0.5)  A'(0.5,0.5,0.5),是的 A点和A'点坐标是一样的,也就是说物体坐标并不会因为轴心和轴的位置不同而发生变化。虽然两个完全一样的物体,物体坐标只是相当于其自身的坐标原点和轴向都在各自的坐标系下,也就是说A点只相对于O点,A'点只相对于O'点。这个概念在游戏里经常会有,你打的怪兽是不是很多都长一个鬼样子?那他们都有各自的物体坐标。这样的坐标系统构成了物体空间

2.世界坐标(World Space<世界空间>)

好了现在我们有了两个正方体,我们只知道这两个正方体的各个对应顶点坐标是一样的(比如前面说到的A和A'点),也就是说画出来这两个正方体是重合的,那我想让其中一个偏离不重合并且有自己的大小,位置和旋转,那么好了,欢迎进入世界空间(World Space)。

好了,什么是世界空间?同样举个栗子(Unity中)!

让我们来看看这两个物体各自的轴心坐标

  1号立方体的轴心(原点)位置 (0,0,0)---Cube1

  2号立方体的轴心(原点)位置 (0 ,0,2)---Cube2

Cube1的轴心位于(0,0,0),Cube2是我复制Cube1向Z轴平移两个单位轴心变成(0,0,2),现在我们看看A点和A'点的坐标。

A点:Cube1的轴心位于(0,0,0)。Cube1为单位正方体,各轴向见图右上角,所以A点坐标为(-0.5,0.5,-0.5)。

A'点:Cube2的轴心位于(0,0,0)。Cube2为单位正方体,各轴向见图右上角,所以A'点坐标为(-0.5,0.5,2-0.5)=(-0.5,0.5,1.5)。

你会发现A点坐标不等于A'点坐标,对,你没看错就是不相等,因为这两个物体放到了同一个坐标系下世界坐标系,在该坐标系下的所有点的坐标都相对于世界坐标系的原点(0,0,0),也就是说Cube1的轴心坐标和世界坐标系的轴心重合了,但是这两个物体的自身的物体坐标并没有改变(这是相对于自身轴心,也就是物体空间

3.观察空间(View Space)

  

总览图                                                                                   俯视图

如图是一个摄像机的观察范围(四棱锥)类似人的眼睛。观察空间就是将世界空间中的坐标变换到以摄像机为轴心计算各个顶点的位置。这个四棱锥我们叫视锥体(view frustum)

4.裁剪空间(clip space)<CVV(canonical view volume)>

什么是裁剪?!

首先假如场景是这样的:

摄像机的视锥体范围:

游戏真正能看到的画面:

你会发现在视锥体外面的东西都被剔除了,这就是裁剪,不渲染看不见的东西。

(1)摄像机有两种:

1>透视摄像机(Perspective)

 

推导过程:(供参考)

nearClipPalneHeight(近截平面的高)=2*Near*tan(FOV/2)

farClipPlaneHeight(远截平面)=2*Far*tan(FOV/2)

Aspect(摄像机的纵横比)=nearClipPlaneWidth/nearClipPalneHeight=farClipPlaneWidth/farClipPlaneHeight

   

2>正交摄像机(Orthographic)

 

推导过程:(供参考)

nearClipPlaneHeight=2*Size

farClipPlaneHeight=nearClipPlaneHeight

Aspect=nearClipPlaneHeight/farClipPlaneWidth=nearClipPlaneHeight/nearClipPalneWidth

5.归一化设备坐标---NDC(Normalized Device Corrdinate)

在这一步会进行一个叫齐次除法的步骤,说白了就是各个点(x,y,z,w)会除以w的值(注:计算机图形学中经常使用四元数代表一个点(叫齐次空间,齐次点等 就是一个名字而已),w没什么特别之处,就是计算矩阵乘法时方便)。

透视裁剪空间到DNC:

正交裁剪空间到DNC:

重点:这样在OpenGL中所有能被摄像机看到的点将会被转换成(-1,1),在DirectX中所有能被摄像机看到的点将会被转换成(0,1)中。为什么要这样做?---为了方便投影到显示屏上!!!

6.屏幕空间(Screen Space)

pixelWidth:屏幕横向分辨率

pixelHeight:屏幕纵向分辨率

OpenGL规范:

DirectX规范:

这个过程就是一个缩放的过程:

screenx={clipx*pixelWidth/(2*clipw)}+pixelWidth/2

    screeny={clipy*pixelHeight/(2*clipw)}+pixelHeight/2

上式更加形象的描述:

第一步: -1<clipx/clipw<1--->这是之前的齐次除法的

   第二步: 0<{(clipx/clipw)+1}/2<1--->对其加1再除以2化成0到1区间

第三步:对于pixelWidth:{{(clipx/clipw)+1}/2}*pixelWidth= screenx

对于pixelHeight:{{(clipy/clipw)+1}/2}*Height=screeny

和之前推导一样!!!

至此你所想要的东西被绘制在了屏幕上!!!

扩展:

(1)顶点着色器(vertex shader)

将物体从物体空间->世界空间->观察空间->裁剪空间就是顶点着色器的工作。

这之中会转换各种点的坐标,我们在哪运算呢?就在顶点着色器中!!!

顶点着色器

1>将物体空间的数据(点)作为顶点着色器的输入

2>将所有在自己范围中的点全部遍历一遍,就是每个点都会算进行加工

3>高度可编程配置!!!(这点绝了!!!太棒啦!!!),也就是说这东西绘制成啥样我们可以自定义了!!!

(2)片元着色器(fragment shader)

将裁剪空间中的点从裁剪空间->屏幕空间就是片元着色器的工作。

屏幕映射,就是之前说的第六步不是我们做,是显卡固定好了的算法。片元着色器是计算每个像素的颜色的。如果看过相关代码,你会发现片元着色器会返回一个四元数-(r,g,b,a)->分别为(red<红>,green<绿>,blue<蓝>,alpha<透明度>).

什么是片元(fragment)?片元是一种状态,刚开始显示器上的像素点是不知道自己的颜色的,每个像素点就像一个空白的格子等着我们上颜色,类似于还没有装蜂蜜的蜂巢。系统中,我们之所以会看到桌面是因为系统已经给显卡初始化了像素颜色。在任务管理器中能找到。

片元着色器

1>将裁减空间的数据(点)作为片元着色器的输入

2>将所有在自己范围中的像素全部遍历一遍(三角遍历<Rasterizer>---Triangle Traversal),就是每个片元(像素)都会运算进行加工。

3>高度可编程配置!!!(太棒啦!!!)

===============================================

让我们看看一个简单的栗子!Unity中是怎么做的:

Shader "Test/Shader"
{SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert//告诉编译器 顶点着色器叫什么名字#pragma fragment frag//告诉编译器 片元着色器叫什么名字#include "UnityCG.cginc"//包含内置文件,方便写代码struct appdata{float4 vertex : POSITION;//物体坐标};struct v2f{float4 vertex : SV_POSITION;//裁剪空间坐标};v2f vert (appdata v)//顶点着色器,以物体坐标为输入(appdata下的vertex){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);//将物体坐标变换到裁剪空间return o;//返回裁剪空间的数据}fixed4 frag (v2f i) : SV_Target//片元着色器,以裁剪空间数据作为输入(上面顶点着色器的输出){  fixed4 col = fixed4(1,1,1,1);//定义一个白色return col;//返回白色}ENDCG}}
}

注意32行:其调用了内置函数:

UnityObjectToClipPos()

其定义如下:(在Unity/Editor/Data/CGInclude/UnityShaderUtilities.cginc)

inline float4 UnityObjectToClipPos(in float3 pos)
{// More efficient than computing M*VP matrix productreturn mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
}
inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{return UnityObjectToClipPos(pos.xyz);
}

return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)))中mul(unity_ObjectToWorld, float4(pos, 1.0))将从物体空间变换到世界空间,unity_ObjectToWorld是物体空间到世界空间的转换矩阵,mul()是矩阵乘法内置函数。之后再乘以UNITY_MATRIX_VP(观察空间和裁剪空间合一起了),乘完后将从世界空间变换到裁剪空间。

完全手动自定义计算的话,这么写:

float4 UnityObjectToClipPos(in float3 pos)
{float4 objectSpaceData = float4(pos, 1.0f);float4 worldSpaceData = mul(unity_ObjectToWorld, objectSpaceData);float4 viewSpaceData = mul(UNITY_MATRIX_V, worldSpaceData);float4 clipSpaceData = mul(UNITY_MATRIX_P,viewSpaceData );return clipSpaceData;
}

参考文献:1.《Unity Shader 入门精要》冯乐乐---人民邮电出版社---2017年6月第一版

2.《Real-Time Rendering third edition》Tomas Akenine-Moller,Eric Haines,Naty HoffMan

渲染基础-渲染管线(Render-pipeline)相关推荐

  1. 3D图形的概念和渲染管线(Render Pipeline)

    3D图形的概念和渲染管线(Render Pipeline) 前面介绍了3D图形历史,接下来要解说的是3D图形的处理流程. 3D图形管线的流程图 图1是3D图形的流程模型.这个虽然是对应DirectX ...

  2. 【西川善司的3D图形技术连载】3D图形的概念和渲染管线(Render Pipeline)(9~13回)

    3D图形的概念和渲染管线(Render Pipeline) 前面介绍了3D图形历史,接下来要解说的是3D图形的处理流程. 3D图形管线的流程图 图1是3D图形的流程模型.这个虽然是对应DirectX ...

  3. Unity基础笔记(5)—— Unity渲染基础与动画系统

    Unity渲染基础与动画系统 Unity渲染基础 一.摄像机 1. 摄像机概念 和现实中的摄像机很接近,Unity 中 Camera 组件负责将游戏画面拍摄然后投放到画面上 Camera 拍摄到的画面 ...

  4. 1.3:Render Pipeline and GPU Pipeline

    文章著作权归作者所有.转载请联系作者,并在文中注明出处,给出原文链接. 本系列原更新于作者的github博客,这里给出链接. 在学习SubShader之前,我们有必要对 Render Pipeline ...

  5. Lightweight Render Pipeline

    (翻译) Lightweight Render Pipeline (LWRP),轻量级渲染管线,是一个Unity预制的Scriptable Render Pipeline (SRP).LWRP可以为移 ...

  6. DirectX11 With Windows SDK--36 延迟渲染基础

    前言 随着图形硬件变得越来越通用和可编程化,采用实时3D图形渲染的应用程序已经开始探索传统渲染管线的替代方案,以避免其缺点.其中一项最流行的技术就是所谓的延迟渲染.这项技术主要是为了支持大量的动态灯光 ...

  7. UE4实时渲染基础及深入探究

    实时渲染基础:link 实时渲染深入探究:link 目录 实时渲染基础 目标帧率与毫秒 帧时间与GPU/CPU 最常见的四大性能问题 实时渲染深入探究 延迟渲染与前向渲染 渲染之前和遮挡 CPU和GP ...

  8. DirectX12(D3D12)基础教程(七)——渲染到纹理、正交投影、UI渲染基础

    目录 1.前言 2.渲染到纹理 3.调试支持 4.正交投影 5.UI渲染基础 6.本章完整代码链接 1.前言 记得那是在差不多10多年前,我在工作中认识了一位好兄弟小杨.那时他刚毕业,跟我是同一所大学 ...

  9. Unity-TA 成长之路(三)URP-Universal Render Pipeline

    因为Unity版本为2020.3.20,推荐使用Universal RP 10.6.0,所以去看了这个版本的文档. 而unity.cn也未对此部分做中文解释: 所以只能去看外文网站了,蹩脚的英文水平, ...

最新文章

  1. 2017 .NET 開發者須知
  2. 基因组重复序列注释-RepeatMasker安装和使用
  3. Bicolor的使用
  4. cocos2dx 实现搓牌(翻牌)效果,包括铺平动画
  5. 网页开发需要先学java吗_先学java还是javascript?
  6. JPEG图片扩展信息读取与改动
  7. php接口返回错误码,laravel 错误处理,接口错误返回json代码
  8. cefsharp.wpf离线安装包下载_在vscode里编写c++程序(解决gdb下载失败问题)
  9. jpa映射json_如何使用JPA和Hibernate映射JSON集合
  10. 一个通用纯C队列的实现
  11. “睡服”面试官系列第八篇之iterator(建议收藏学习)
  12. 【操作系统】进程与程序的比较
  13. c++多线程——简单线程池
  14. linux 硬链接 软链接_Linux中的软链接–完整参考
  15. 模型推理速度与硬件算力
  16. python合并大量ts文件_python爬取基于m3u8协议的ts文件并合并
  17. android六边形控件,Android自定义六边形控件
  18. 使用 opencv 画 五角星
  19. jQuery 第二篇
  20. 系统架构师论文-论软件设计模式的应用

热门文章

  1. 线上中国美食图鉴:舌尖上的家乡味道
  2. 钟汉良日记:不会读书,就不要怪命运
  3. 最小长度路线板排列问题
  4. ibm 小型机hmc地址——P5 P6 差别
  5. 显卡,GPU,显卡驱动,CUDA ,CUDA Toolkit之间的关系
  6. 海底磁异常条带研究综述及南海重建
  7. 工程流体力学笔记暂记37 (物体在流体中运动的阻力)
  8. windows mobile 和 symbian 平台上彩信(MMS)收发方案
  9. 代号SSR如何在电脑上玩 代号SSR手游模拟器教程
  10. Conflux生态基金代付规则调整公告(20211111)