精华内容
参与话题
问答
  • 渲染基础-渲染管线(Render-pipeline)

    万次阅读 多人点赞 2018-05-25 00:30:00
    一开始学习计算机图形学的小伙伴们肯定对于渲染管线有一点迷茫,至少当初我就有点迷茫,为了能对后来对计算机图形学感兴趣的萌新小伙伴起到一些帮助,在这里简单讲解一下渲染管线(Render-pipeline)。 ===========...

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


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


    2020/6/12 更新

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


    2020/6/12 更新

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

    在计算机图形学初期并没有管线之类的东西,当时连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 100
    
    		Pass
    		{
    			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 product
       return 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

     

    展开全文
  • 渲染管线流程【图示】

    千次阅读 2018-07-13 16:09:07
    最近老是有人问我渲染管线流程和shader的使用,转载记录下 ,备用了解管线之后,能让人了解3D物体从自身的数据传入开始到最后呈现在屏幕上的所有历程。在理解和解决渲染发生的问题的时候,往往有指导性作用。管线...

    最近老是有人问我渲染管线流程和shader的使用,转载记录下 ,备用

    了解管线之后,能让人了解3D物体从自身的数据传入开始到最后呈现在屏幕上的所有历程。在理解和解决渲染发生的问题的时候,往往有指导性作用。管线基本分为固定管线可编程管线,固定管线,暂时没有什么好讲的,今天主要谈下可编程管线。参考的是OpenGL的图。

    渲染管线流程如下图: 
    这里写图片描述 
    这种模式下, GPU 的 Vertex Shader(其中一橙色块)和 Fragment Shader(另一个橙色块),都是一段通过用户层可以设置的供GPU运行的代码。Vertex Shader接收从GPU送进来的顶点信息,并对每个顶点运行用户的顶点程序,即顶点着色程序; Fragment shader 接收 GPU送进来的像素信息,并对每个像素运行用户的像素程序,即像素着色程序(精确的说,fragment 并不等同于pixel,但这里简化这个说法)。通过这样的方式,用户就可以在这两段程序里放置自己的算法,对于金属,使用一种算法;对于塑料,又是另外一种算法。因为这些地方都是可以进行编程的,所以这个管线也就被叫做“可编程管线”。

    渲染管线具体可细分为:顶点处理、面处理、光栅化、像素处理。

    可编程管线工作流程如下图:

    顺着图中的箭头方向,就是数据一步一步被处理,最后写到显示缓冲区(framebuffer)的过程,每个单位都是一个一个的步骤走,类似工厂的流水线。

    下面具体说下上面的工作流程: 
    (1)用户给GPU传入顶点数据到流水线,顶点数据是一个属性,如:位置、法线、颜色等。 
    (2)上图中的Vertex Shader接手处理每个顶点,对Vertex Shader来说就是接收了在(1)当中的输入的顶点数据信息流。要输出什么,在可编程管线中,完全是看用户写的代码要做什么。在这个步骤中,输出的信息有一个约束,至少有一个位置(position)信息必须被输出,作为一个有效的顶点信息。 
    (3)元件组装( Primitive Assembly),对于 OpenGL 来说,元件有 3 种,点、线和三角形。从第(2)步出来的顶点数据,再结合用户传给流水线的其他数据(告知这些顶点之间的关系),会被这个过程组装成这三个类型中的一种。镶嵌/或者说是细分(Tessellation)会在此后运行,让前面的 primitive 能被细分成更小的元件以满足不同的精度需求。 
    (4)几何体着色器(Geometry Shader),该着色器得到第三步出来的 primitive,它的输出是0个或者更多的primitives。 
    (5)Transform Feedback,在 OpenGL 这里,几何体着色器后可以吐出相关的 primitive到所谓的缓冲物体(buffer object)中(其实就是定义好格式的一些数据结构),这些buffer object可以被保存用作其他的作用。如果在这里抛弃流水线后续的工作,那么整个流水线的产出就是这些东西。 
    (6)顺图中的实线,后续是裁剪(Clipping)。根据用户设置的视椎体的可视范围,primitives会被剔除(整体在可视范围外),部分在外的则被按照规则切割成几个primitives。在现在的GPU上,用户除了视椎体,也可以通过裁剪面(Clip plane)来指定额外的裁剪信息。在3D中,大多数是单面的primitive,这个时候,背面朝向相机的primitive也会被裁剪掉。 
    (7)能达到光栅化(Rasterization)这步的物体,会被这个步骤进行光栅化,举例来说,一个三角形primitive,会产生填充其内部的许多所谓的碎片(fragment)。fragment跟 pixel(像素)不同的地方在于,在专业角度,它是一系列信息,用来判断对应的像素最后的数据。 
    (8)碎片着色器(Fragment Shader得到上面处理出来的数据,然后运行用户给定的一段程序,计算出最后的像素的相关信息。它的输出是一系列的值:颜色和深度信息。 
    (9)fragment shader 的输出进行Fragment Tests 这个阶段,会首先进行Alpha 测试(alpha test),继而模板测试(stencil test),如果 fragment 没有经过测试,那么就不会进入到最后的缓冲区( framebuffer);然后进行深度测试( depth test),如果通不过,就不会进入 framebuffer。【注: OpenGL 的相关文档有提到,如果 fragment shader对 fragment 的深度不会修改,那么这个测试可以提到 fragment shader 之前,所谓的“Early Z Test”】 
    (10)Blending 是后续的工作,如果 fragment 可以被写入 framebuffer,则这个 fragment和已经在 framebuffer 中的值可以做一定的混合,实现不同的效果,举例,半透明效果。 
    (11)上一步出来的值,就准备开始往 framebuffer 中写了,写的时候可以通过 Writing Mask来控制哪些值要写到 framebuffer,可以控制到颜色、深度、模板值,也可以控制到颜⾊的某个通道。 
    (12)结束。

    转载自 https://blog.csdn.net/qq_24642743/article/details/70161745
    展开全文
  • 渲染管线的三个概念阶段

    千次阅读 2018-07-18 11:34:18
    渲染管线,也称为渲染流水线 渲染流水线可以分为三个概念性阶段:应用阶段,几何阶段,光栅化阶段。 游戏场景中的物体渲染都是基于可编程流水线实现的,其实就是把绘制的3D物体通过可编程流水线绘制在2D的屏幕上的...

    一.渲染管线,也称为渲染流水线

    渲染流水线可以分为三个概念性阶段:应用阶段,几何阶段,光栅化阶段。
    游戏场景中的物体渲染都是基于可编程流水线实现的,其实就是把绘制的3D物体通过可编程流水线绘制在2D的屏幕上的过程。
    什么是渲染(Rendering)
    渲染简单的理解可能可以是这样:就是将三维物体或三维场景的描述转化为一幅二维图像,生成的二维图像能很好的反应三维物体或三维场景

    二.光栅化相关概念和知识

    基于深度缓冲的三角形光栅化基础

    实时渲染引擎需要以最低每秒30帧显示渲染出来的图像,从而产生运动错觉,也就是要以最长33.3ms内产生每一帧,通常实际可用的时间更少,因为其它系统如动画、物理、音频等引擎系统都需要占用时间资源;

    三维场景渲染的本质---几个重要基本步骤

    1.场景描述

    在游戏中的场景渲染,主要着重于渲染物体的表面,对其内部的反射、折射、吸收等行为不关注;渲染导致的不准确性,通常由模拟来使其看起来真实,如烟使用粒子效果来表现,实际上是由大量的半透明矩形卡板所合成;

    2.场景表示法

    • 高端渲染软件使用面片建模,采用细分曲面来表示几何形状

    • 在游戏中使用三角形网格来为表面建模;镶嵌与多层次细节,用于效果和游戏性能优化;

    3.三角形网格

    • 缠绕顺序,根据缠绕顺序来进行背面剔除,可以提高性能;但是需要整个模型的缠绕顺序都是一致的,不一致是建模新手常犯错误;

    • 三角形表,定义网格的方法,每三个顶点构成一个三角形;

    • 索引化三角形表,每个顶点仅列举一次,然后使用顶点索引来定义组成三角形的三个顶点;

    • 三角形带及三角形扇,这两种特殊结构不需要索引缓冲,因为顶点出现的顺序都是被预定义的

    4.描述表面的视觉性质

    • 表面性质包括几何信息如法线信息,也包括描述光和表面交互作用的方式,包括漫反射颜色、高光颜色、纹理、透明度等等,表面性质也可能包含有表面随时间变化的描述,如动画的角色皮肤如何追踪关节、水面如何移动;

    • 这些表面性质都要存储在一个顶点属性中,包括位置矢量、法线矢量、切线矢量、漫反射颜色、镜面颜色、纹理坐标、蒙皮权重等等;

    5.纹理相关

    • 纹理贴图种类

    • 纹理坐标与纹理的寻址方式

    • 纹理格式

    • 纹理过滤以及mipmap,采用LOD的思想

    • 纹理压缩

    6.材质

    • 三维模型通常会使用多于一个材质,例如,一个人物模型可能有多个材质,供头发、皮肤、眼睛、牙齿、多种服饰等所用,因此,一个网格通常会切割成子网格,每个子网格对应一个材质;

    7.光照

    • 各种光照模型

    8.摄像机

    • 相机属性及其在顶点着色器内的矩阵计算

    三个阶段

    1,应用程序阶段。 (由CPU负责)

    这个阶段相对比较好理解,就比如我们在Unity里开发了一个游戏,其实很多底层的东西Unity都帮我们实现好了,例如碰撞检测、视锥剪裁等等,这个阶段主要是和CPU、内存打交道,在把该计算的都计算完以后,在这个阶段的末端,这些计算好的数据(顶点坐标、法向量、纹理坐标、纹理)就会通过数据总线传给图形硬件,作为我们进一步处理的源数据。 
    主要有三个任务:
    A.准备好场景数据,例如摄像机的位置,视锥体,场景中的模型以及使用的光源等
    B.为了提高渲染性能,通常需要做一个粗粒度剔除(culling)工作,把那些在场景中不可见的物体剔除出去,这样这些物体就不需要再移交给几何阶段处理;
    C.需要设置好每个模型的渲染状态,这些渲染状态包括但不限于它使用的材质纹理shader等。
    这一阶段的输出是渲染所需要的几何信息,即渲染图元。

    其中每个渲染图元里包含了该图元所对应的所有顶点数据。例如,在OpenGL中,如果我们绘制一个三角形,我们需要在程序中制定顶点位置,指定图元类型,这些都是需要CPU先准备好。

    2,几何阶段。  (由GPU负责)

    几何阶段用于处理所有和我们需要绘制的几何相关的工作。例如:决定需要绘制的图元是什么,怎样绘制,在哪绘制。
    主要负责顶点坐标变换、光照、裁剪、投影以及屏幕映射,该阶段基于GPU进行运算,在该阶段的末端得到了经过变换和投影之后的顶点坐标、颜色、以及纹理坐标。简而言之,几何阶段的主要工作就是“变换三维顶点坐标”和“光照计算”。 
    问题随之而来,为什么要变换顶点坐标?我是这么理解的,比如你有一个三维游戏场景,场景中的每个模型都可以用一个向量来确定它的位置,但如何让计算机根据这些坐标把模型正确的、有层次的画在屏幕上?这就是我们需要变换三维顶点坐标的原因,最终目的就是让GPU可以将这些三维数据绘制到二维屏幕上。 

    几何阶段最重要的部分就是进行坐标的转换,如果想了解这其中具体的转换步骤,可以参考:顶点着色器内的矩阵变换

    该阶段的重要任务就是:把顶点坐标变换到屏幕空间中,再交给光栅器进行处理,所以该阶段还是侧重与几何计算部分,而不是给物体“上色”。
    通过对输入的渲染图元进行多步处理后,这一阶段将会输出:屏幕空间的二维顶点坐标、每个坐标对应的深度值、着色等相关信息,并传递给下一阶段。

    更多细节部分可以参考:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/

    3.光栅化阶段。 (由GPU负责)

    具体的光栅化细节可以参考:渲染管线在GPU中的各个阶段

    这一阶段会使用上个阶段传递的数据来产生屏幕上的像素,并渲染出最终的图像。
    光栅化的任务:主要是决定每个渲染图元中的哪些像素应该被绘制到屏幕上。它需要对上一个阶段得到的逐顶点数据进行插值,然后再进行逐像素处理。

    光栅化其实是一种将几何图元变为二维图像的过程,该过程主要包含了两部分的工作,光栅化和片元着色。

    光栅化:决定窗口坐标中的哪些整型栅格区域被基本图元占用,这一部分主要对应着三角形设置和三角形遍历两个阶段通过插值计算完成,这一阶段输出一个片元序列,之后的片元着色器就会对该片元序列进行处理。

    片元着色:分配一个颜色值和一个深度值到各个区域。

    经过上面的步骤之后,我们得到了每个点的屏幕坐标值,和我们需要绘制的图元,但此时还有两个问题: 
    (1)屏幕坐标是浮点数,但像素是用整数来表示的,如何确定屏幕坐标值所对应的像素? 
    (2)如何根据已确定位置的点,在屏幕上画出线段或者三角形? 
    对于问题1,绘制的位置只能接近两指定端点间的实际线段位置,例如,一条线段的位置是(10.48, 20.51),转换为像素位置就是(10,21)。 
    问题2,我们所要解决的问题就是根据三角形的三个顶点来判断哪些像素在这个三角形内,这就转化为判断一个顶点是否在三角形内的问题,这也是OpenGL三角形遍历中一个重要的算法。


    这个过程结束后,顶点和图元已经对应到像素,之后的流程就是如何处理像素,即给像素赋予颜色值。 
    给像素赋予颜色的阶段称为Pixel Operation,也就是逐片元操作,是在更新帧缓存之前,执行最后一系列针对每个片段的操作,其目的是计算出每个像素的颜色值。

    在这个阶段,主要进行的就是一些逐片元操作,先后是模板测试,深度测试,混合,最后写入帧缓存。
    pixel operation包含下面这些流程: 
    (1)消除遮挡面,深度测试可以消除遮挡面; 
    (2)Texture operation,纹理操作也就是文理采样,根据像素的纹理坐标,查询对应的纹理值; 
    (3)Blending,通常称为alpha blending,根据目前已经画好的颜色,与正在计算的颜色的alpha值混合,形成新的颜色。 
    (4)Filtering,将正在计算的颜色经过某种滤镜后输出。 
    该阶段之后,像素的颜色值被写入帧缓存中。 


     

    展开全文
  • 详解Unity3D Shader开发之渲染管线

    万次阅读 多人点赞 2017-01-24 21:10:51
    笔者介绍:姜雪伟,IT公司技术合伙人,... Shader编程对于图形学渲染来说非常重要,为了让读者理解Shader编程的原理,文章会结合着可编程流水线原理与Shader编程一起给读者介绍,游戏场景中的物体渲染都是基于可编程流

    笔者介绍:姜雪伟IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

    Shader编程对于图形学渲染来说非常重要,为了让读者理解Shader编程的原理,文章会结合着可编程流水线原理与Shader编程一起给读者介绍,游戏场景中的物体渲染都是基于可编程流水线实现的,大家可以想象一下游戏开发使用的是美术制作的3D模型然后将其加入到游戏场景中进行渲染,其实就是把绘制的3D物体通过可编程流水线绘制在2D的屏幕上的过程,为了能让读者真正的了解其实现原理,下面文字会结合着图片一起讲解,首先介绍流程图如下所示:


    图中流程是将3D模型通过渲染绘图管线处理也就是中间显示的黑盒子,最后在屏幕上绘制出2D图片。接下来给读者介绍渲染管线流程,它可以细分为:顶点处理,面处理,光栅化,像素处理,图示如下所示:


    先介绍渲染管线的中的顶点处理,顶点处理就是通过一系列的坐标系转换,将模型的顶点在摄像机前进行位移,并最终将模型投影到摄像机的屏幕上。坐标系的流程图如下所示:


    这一系列变换会涉及到各个坐标系中的矩阵变换,再将顶点变换给读者完整的展现一次,效果如下所示:


    在这一阶段包括顶点的坐标变换、逐顶点雾化、材质属性和光照属性处理。接下来介绍面处理,面处理主要包括:

    面的组装,面截取,面剔除,整个流程如下所示:


    以3D模型为例,3D模型就是有点组成的,然后程序将点连成线,然后对其进行裁剪处理。在3D中模型的组装方式有很多种,效果如下所示:


    这些点和线的绘制在DX中都有现成的接口调用。点线绘制完成后接下来开始对面进行剔除操作,在使用Unity开发中使用摄像机使可以很容易观察到物体的裁剪,效果如下所示:


    面剔除完成后,接下来就需要进行光栅化操作了,先把效果图给读者展示一下:


    其实在进行光栅化操作时,我们显示的物体都是有材质渲染的,这就涉及到像素处理,像素处理主要包括:对每个像素区域进行着色,对像素贴上贴图,最后形成最终的画面。


    讲了这么多,我们需要知道面剔除操作是在渲染管线的哪个部分进行的,将渲染管线中的处理细化一下,效果如下所示:


    渲染管线的流程是在GPU中进行的,展示效果如下所示:


    如果读者使用DirectX开发过Demo,对3D调用接口应该比较熟悉,下面结合着图片把在CPU中调用的接口对应到GPU使用的接口,展示效果如下所示:


    渲染管线主要分为四个步骤:顶点变换,图元装配,光栅化,像素处理,再结合着图片给读者介绍如下:


    Shader编程主要是分为两部分:一部分是顶点处理,一部分是像素处理。

    顶点处理:

    顶点渲染的作用是对三维图元的顶点进行坐标变换和光照计算,生成可用于渲染到投影空间的顶点坐标、颜色和纹理坐标。顶点渲染就是定义了一系列针对顶点的渲染指令或渲染语句,当Direct3D处理图元顶点时,自动使用这些渲染指令或者渲染语句对每一个顶点逐一进行处理,完成顶点数据的处理工作。

    再说说像素处理:

    对每个像素的颜色斤西瓜混合纹理采样,包括迭代颜色和纹理坐标、纹理采样以及将纹理采样与灯光和材质的颜色进行混合。比如:Alpha测试、深度测试、模版测试、计算每个像素的雾化值、Alpha混合等。

    下面把DirectX的完整代码给读者展示一下,使用的都是DX的接口,完整代码如下所示:

    //============================================================================
    // Desc: 纹理阶段混合状态
    //============================================================================
    #include <d3dx9.h>
    
    
    //-----------------------------------------------------------------------------
    // Desc: 全局变量
    //-----------------------------------------------------------------------------
    LPDIRECT3D9               g_pD3D        = NULL;  //Direct3D对象
    LPDIRECT3DDEVICE9         g_pd3dDevice  = NULL;  //Direct3D设备对象
    LPDIRECT3DVERTEXBUFFER9   g_pVB         = NULL;  //顶点缓冲区对象
    LPDIRECT3DTEXTURE9        g_pTexture    = NULL;  //纹理对象
    
    
    //-----------------------------------------------------------------------------
    // Desc: 顶点结构
    //-----------------------------------------------------------------------------
    struct CUSTOMVERTEX
    {
        D3DXVECTOR3 position;   //顶点位置
        D3DXVECTOR3 normal;     //顶点法线
    	FLOAT       tu, tv;     //顶点纹理坐标
    };
    #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)
    
    
    //-----------------------------------------------------------------------------
    // Desc: 设置变换矩阵
    //-----------------------------------------------------------------------------
    VOID SetupMatrices()
    {
        //建立并设置世界矩阵
        D3DXMATRIXA16 matWorld;
        D3DXMatrixIdentity( &matWorld );
        g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
    
    	//建立并设置观察矩阵
        D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
        D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
        D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
        D3DXMATRIXA16 matView;
        D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
        g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
    
        //建立并设置投影矩阵
        D3DXMATRIXA16 matProj;
        D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f );
        g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
    }
    
    
    //-----------------------------------------------------------------------------
    // Desc: 初始化Direct3D
    //-----------------------------------------------------------------------------
    HRESULT InitD3D( HWND hWnd )
    {
        //创建Direct3D对象, 该对象用于创建Direct3D设备对象
        if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
            return E_FAIL;
    
    	//设置D3DPRESENT_PARAMETERS结构, 准备创建Direct3D设备对象
        D3DPRESENT_PARAMETERS d3dpp;
        ZeroMemory( &d3dpp, sizeof(d3dpp) );
        d3dpp.Windowed = TRUE;
        d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
        d3dpp.EnableAutoDepthStencil = TRUE;
        d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
    
        //创建Direct3D设备对象
        if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                          D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                          &d3dpp, &g_pd3dDevice ) ) )
        {
            return E_FAIL;
        }
    
    	//设置渲染状态
        g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); //剔除模式设置
        g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );          //启用深度测试
    	g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);     //启用镜面反射光照模型
    
    	//设置纹理渲染状态
    	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE );  //默认设置
        g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
        g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
    	
        g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_DISABLE );
    
    	//设置纹理过滤方式
    	g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
    	g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
    
    	//设置变换矩阵
    	SetupMatrices();
    
        return S_OK;
    }
    
    
    //-----------------------------------------------------------------------------
    // Desc: 设置材质和灯光
    //-----------------------------------------------------------------------------
    VOID SetupLight()
    {
    	 //设置材质
        D3DMATERIAL9 mtrl;
        ZeroMemory( &mtrl, sizeof(D3DMATERIAL9) );
        mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
        mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
        mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f;
        mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
        g_pd3dDevice->SetMaterial( &mtrl );
    
        //设置灯光
        D3DXVECTOR3 vecDir;
        D3DLIGHT9 light;
        ZeroMemory( &light, sizeof(D3DLIGHT9) );
        light.Type       = D3DLIGHT_DIRECTIONAL;
        light.Diffuse.r  = 1.0f;
        light.Diffuse.g  = 1.0f;
        light.Diffuse.b  = 1.0f;
        vecDir = D3DXVECTOR3(cosf(timeGetTime()/350.0f),
                             1.0f,
                             sinf(timeGetTime()/350.0f) );
        D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &vecDir );
        light.Range       = 1000.0f;
        g_pd3dDevice->SetLight( 0, &light );
        g_pd3dDevice->LightEnable( 0, TRUE );
        g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE ); //默认设置
    
        //设置环境光
        g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x00808080 );
    }
    
    
    //-----------------------------------------------------------------------------
    // Desc: 创建场景图形(纹理和顶点缓冲区)
    //-----------------------------------------------------------------------------
    HRESULT InitGeometry()
    {
    	//创建纹理对象
    	if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, L"texture.jpg", &g_pTexture ) ) )
        {
           MessageBox(NULL, L"创建纹理失败", L"Texture.exe", MB_OK);
           return E_FAIL;
        }
    
        //创建顶点缓冲区
        if( FAILED( g_pd3dDevice->CreateVertexBuffer( 50*2*sizeof(CUSTOMVERTEX),
                                                      0, D3DFVF_CUSTOMVERTEX,
                                                      D3DPOOL_DEFAULT, &g_pVB, NULL ) ) )
        {
            return E_FAIL;
        }
    
        //填充顶点缓冲区
        CUSTOMVERTEX* pVertices;
        if( FAILED( g_pVB->Lock( 0, 0, (void**)&pVertices, 0 ) ) )
            return E_FAIL;
        for( DWORD i=0; i<50; i++ )
        {
            FLOAT theta = (2*D3DX_PI*i)/(50-1);
            pVertices[2*i+0].position = D3DXVECTOR3( sinf(theta),-1.0f, cosf(theta) );
            pVertices[2*i+0].normal   = D3DXVECTOR3( sinf(theta), 0.0f, cosf(theta) );
    		pVertices[2*i+0].tu       = ((FLOAT)i)/(50-1);
            pVertices[2*i+0].tv       = 1.0f;
    
            pVertices[2*i+1].position = D3DXVECTOR3( sinf(theta), 1.0f, cosf(theta) );
            pVertices[2*i+1].normal   = D3DXVECTOR3( sinf(theta), 0.0f, cosf(theta) );
    		pVertices[2*i+1].tu       = ((FLOAT)i)/(50-1);
            pVertices[2*i+1].tv       = 0.0f;
        }
        g_pVB->Unlock();
    
        return S_OK;
    }
    
    
    //-----------------------------------------------------------------------------
    // Name: 释放创建的对象
    //-----------------------------------------------------------------------------
    VOID Cleanup()
    {
    	//释放纹理对象
    	 if( g_pTexture != NULL )
            g_pTexture->Release();
    
    	//释放顶点缓冲区对象
        if( g_pVB != NULL )
            g_pVB->Release();
    
    	//释放Direct3D对象
        if( g_pd3dDevice != NULL )
            g_pd3dDevice->Release();
    
    	//释放Direct3D对象
        if( g_pD3D != NULL )
            g_pD3D->Release();
    }
    
    
    //-----------------------------------------------------------------------------
    // Desc: 渲染图形 
    //-----------------------------------------------------------------------------
    VOID Render()
    {
        //清除后缓冲区和深度缓冲区
        g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                             D3DCOLOR_XRGB(45, 50, 170), 1.0f, 0 );
    
        //开始在后台缓冲区绘制图形
        if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
        {
    		//设置材料和灯光, 因为灯光属性不断变化,所以在此设置
    		SetupLight(); 
    
    		//在后台缓冲区绘制图形
    		g_pd3dDevice->SetTexture( 0, g_pTexture );  //设置纹理
            g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(CUSTOMVERTEX) );
            g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
            g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2*50-2 );
    
            //结束在后台缓冲区绘制图形
            g_pd3dDevice->EndScene();
        }
    
    	//将在后台缓冲区绘制的图形提交到前台缓冲区显示
        g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
    }
    
    
    //-----------------------------------------------------------------------------
    // Desc: 消息处理
    //-----------------------------------------------------------------------------
    LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
    {
    	switch( msg )
    	{
    	case WM_DESTROY:
    		Cleanup();
    		PostQuitMessage( 0 );
    		return 0;
    	}
    
        return DefWindowProc( hWnd, msg, wParam, lParam );
    }
    
    
    //--------------------------------------------------------
    // Desc: 入口函数
    //--------------------------------------------------------
    INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
    {
        //注册窗口类
        WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
                          GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
                          L"ClassName", NULL };
        RegisterClassEx( &wc );
    
        //创建窗口
        HWND hWnd = CreateWindow( L"ClassName", L"纹理阶段混合状态",
                                  WS_OVERLAPPEDWINDOW, 200, 100, 500, 500,
                                  GetDesktopWindow(), NULL, wc.hInstance, NULL );
    
        //初始化Direct3D
        if( SUCCEEDED( InitD3D( hWnd ) ) )
        {
            //创建场景图形
            if( SUCCEEDED( InitGeometry() ) )
            {
                //显示窗口
                ShowWindow( hWnd, SW_SHOWDEFAULT );
                UpdateWindow( hWnd );
    
                //进入消息循环
                MSG msg;
                ZeroMemory( &msg, sizeof(msg) );
                while( msg.message!=WM_QUIT )
                {
                    if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
                    {
                        TranslateMessage( &msg );
                        DispatchMessage( &msg );
                    }
                    else
    				{
                        Render();  //渲染图形
    				}
                }
            }
        }
    
        UnregisterClass( L"ClassName", wc.hInstance );
        return 0;
    }
    
    这些操作都是在CPU中进行的,下面对应的是在Unity3D shader中的代码,Shader代码如下所示:

    Shader "Custom/RenderPipeline" {
    	
    	Properties {
    		_Color ("Main Color", Color) = (1, 1, 1, 1)
    		_MainTex ("Base (RGB)", 2D) = "white" {}
    	}
    	SubShader {
    
    		Pass{
    		// Dont write to the depth buffer
    		ZWrite off
    
    		// Set up alpha blending
    		Blend SrcAlpha OneMinusSrcAlpha
    
    		CGPROGRAM
    		#pragma vertex vert
    		#pragma fragment frag
    		#include "UnityCG.cginc"
    
    		sampler2D _MainTex;
    		float4 _Color;
    
    		struct v2f{
    			float4 pos:SV_POSITION;
    			float4 texcoord : TEXCOORD0;
    		};
    
    		v2f vert(appdata_base v)
    		{
    			v2f o;
    			o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    			o.texcoord = v.texcoord;
    			return o;
    		}
    
    		half4 frag(v2f i):COLOR0
    		{
    			half4 col = _Color * tex2D(_MainTex, i.texcoord.xy);
    			return col;
    		}
    
    		ENDCG
    		}
    	} 
    	FallBack "Diffuse"
    }

    关于渲染管线的介绍,就讲到这里,主要是通过图片的描述让读者知道渲染管线是如何工作的。结合着DirectX和Unity3D Shader也是告诉读者CPU和GPU之间的对应关系,文中展示的Unity3D Shader相对来说比较简单,Shader中的模型视图投影矩阵的表示为UNITY_MATRIX_MVP,mul表示的是将模型顶点转换到投影矩阵中。。。。。。



    
    
    展开全文
  • 2、渲染管线也称为渲染流水线,是显示芯片内部处理图形信号相互独立的的并行处理单元。一个流水线是一序列可以并行和按照固定顺序进行的阶段。每个阶段都从它的前一阶段接收输入,然后把输出发给随后的阶段。 渲染...
  • 本系列主要翻译和参考《Real-Time...DirectX11 渲染管线一般计算机中共有两个处理器是你可能会对其进行编程的,一个是central processing unit(CPU),一个是GPU。这两个组件有着截然不同的硬件结构和指令集。在图形编程
  • 渲染管线简介

    2018-08-07 21:45:49
    渲染管线入门    渲染管线中一些技术名词大部分是英文直译,光看中文很容易被误导。所以我们必须在了解一个技术的具体作用之后,才能了解它到底是什么。这篇博客我主要翻译自Minh Tri Do Dinh的GPUs - Graphics ...
  • 渲染管线

    2020-11-14 22:47:09
    渲染管线是实时渲染的重要工具,实时渲染离不开渲染管线。图形渲染管线主要包括两个功能:一是将物体3D坐标转变为屏幕空间2D坐标,二是为屏幕每个像素点进行着色。渲染管线的一般流程如下图所示。分别是:顶点数据的...
  • 渲染管线 因为很多英文强行翻译过来有点怪怪的(比如Normalized Device Coordinates翻译成归一化的设备坐标)或者翻译过来可能会有点不规范,所以以后有些部分直接英文写了。 渲染管线的示意图如下 Input ...
  • 渲染管线入门

    万次阅读 2018-04-01 11:58:00
    渲染管线中一些技术名词大部分是英文直译,光看中文很容易被误导。所以我们必须在了解一个技术的具体作用之后,才能了解它到底是什么。这篇博客我主要翻译自Minh Tri Do Dinh的GPUs - Graphics Processing Units一文...
  • 至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二个渲染管线。当然也支持自定义渲染管线。与高清晰渲染管线相比,...
  • GPU渲染管线

    千次阅读 2016-02-01 00:17:27
    所谓GPU的渲染管线,听起来好像很高深的样子,其实我们可以把它理解为一个流程,就是我们告诉GPU一堆数据,最后得出来一副二维图像,而这些数据就包括了”视点、三维物体、光源、照明模型、纹理”等元素。...
  • 图形渲染管线是实时渲染的核心组件。渲染管线的功能是通过给定虚拟相机、3D场景物体 以及光源等场景要素来产生或者渲染一副2D的图像。如上图所示,场景中的3D物体通过管 线转变为屏幕上的2D图像。渲染管线是实时渲染...
  • 固定渲染管线与可编程渲染管线

    千次阅读 2013-12-03 17:40:33
    1.固定渲染管线与可编程渲染管线的区别:  1)、固定渲染管线 ——这是标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&L管线可以被渲染状态控制,矩阵,光照和...
  • WebGL 渲染管线

    千次阅读 2018-10-21 22:29:15
    早期的渲染管线是不可编程的,叫做固定渲染管线,现代的GPU所包含的渲染管线为可编程渲染管线。本节所述皆基于可渲染管线 两种渲染管线的区别简单可以理解为前者工作的细节流程已经固定,只需要调整一些参数,后者...
  • Unity 渲染管线

    2020-02-12 10:17:43
    渲染管线1.1 选择一种渲染管线2. 默认管线 Built-in Render Pipeline2.1 渲染通路 Rendering paths2.1.0.1 前向渲染 Forward Rendering2.1.0.2 延迟着色 Deferred Shading2.1.0.3 老版本的延迟着色 Legacy Deferred...
  • 从图形渲染管线谈性能优化
  • 固定渲染管线

    千次阅读 2017-04-11 14:36:08
    浅谈 GPU图形固定渲染管线 阅读目录 1. 应用程序阶段(CPU) 1.1 视锥裁剪 1.2 场景图 1.3 四叉树与八叉树 2. 几何阶段(GPU)  2.1 坐标系统局部坐标系世界坐标系观察坐标系视口坐标系(屏幕坐标系...
  • 1.固定渲染管线与可编程渲染管线的区别:  1)、固定渲染管线 ——这是标准的几何&amp;光照(T&amp;L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&amp;L管线可以被...
  • Unity HDRP渲染管线基础指南

    万次阅读 2019-04-18 11:09:09
    HDRP和LWRP简介 ...至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二种SRP渲染管线。当然也支持自定义渲染管线。与...
  • 渲染管线浅析

    2016-10-30 20:28:41
    这篇文章是描述GPU渲染管线的大致工作流程。 渲染管线 ,也称渲染流水线,是显示芯片内部处理图形信号相互独立的并行处理单元。渲染管线就是要把一系列的顶点数据,纹理等信息,最终转换成一张人眼可以看到的图像...
  • 渲染管线简单梳理

    2019-03-11 14:28:56
    渲染管线图形渲染管线GPU渲染管线 图形渲染管线 GPU渲染管线 绿色的阶段是完全可编程的 黄色的阶段可配置,但不可编程 蓝色的阶段完全固定 《Real-Time Rendering 3rd》提炼总结 ...
  • OpenGL渲染管线

    万次阅读 2013-01-01 20:54:39
    OpenGL渲染管线 绝大数OpenGL实现都有相似的操作顺序,一系列相关的处理阶段称为OpenGL渲染管线。图1-2显示了这些顺序,虽然并没有严格规定OpenGL必须采用这样的实现,但它提供了一个可靠的指南,可以预测OpenGL将...
  • 3D图形渲染管线

    万次阅读 2010-03-25 21:34:00
    对图形渲染管线的各个阶段到底做了些什么,总是不甚清楚,每次翻书查阅,但是过了一阵遇到还是翻书,这次看Cg教程里写的不错,特地摘下来,以备查阅。 -by shenzi/2010.3.25 3D图形渲染管线 什么是渲染...
  • URP渲染管线初步解析

    千次阅读 2020-01-12 13:46:40
    URP渲染管线初步解析 LWRP现在在unity2019已经成为默认的管线了,并更名为URP(通用渲染管线),替代了原来的builtin管线,以下是应用阶段的渲染流程顺序。 步骤分析 URP整个渲染的主入口在UniversalRenderPipeline...
  • Unity_内置渲染管线 Unity 的内置渲染管线是通用渲染管线。· 内置渲染管线在自定义扩展方面比可编程渲染管线受到的限制更多,但是您可以在不同的渲染路径之间进行选择,并通过命令缓冲区和回调来扩展其功能。 ...
  • “你对渲染管线了解多少?” 当我听到这个面试题的时候,我是懵逼的。很长时间以前学的图形学知识,都还给老师了。虽然看了蓝宝书,但是我一直以来没有精通一门图形语言,非常惭愧。不知死活的去面试引擎工程师在这...
  • 该篇是对Catlike Coding这篇文章的概要总结,本人能力有限,如果有不正确的地方欢迎指正https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/custom-pipeline/ ... 创建一个渲染管线资源和...
  • 通用渲染管线概述 通用渲染管线 (Universal Render Pipeline, URP) 是由 Unity 制作的预构建可编程渲染管线 (Scriptable Render Pipeline)。URP 提供了对美术师友好的工作流程,可让您在移动平台、高端游戏主机和 ...

空空如也

1 2 3 4 5 ... 20
收藏数 685,269
精华内容 274,107
关键字:

渲染管线