废物程序员做梦都在写Shader

Posted Power__ll

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了废物程序员做梦都在写Shader相关的知识,希望对你有一定的参考价值。

废物程序员做梦都在写Shader(一)

学习用,编者死废物一个



一、渲染

1.渲染流水线

渲染流水线从CPU开始,大致可分为3个阶段:
(1)把数据加载到显存中
(2)设置渲染状态
(3)调用Draw Call

2.渲染状态

定义渲染方式

3.Draw Call

Draw Call是一个由CPU发起的命令,由GPU接收。这个命令仅仅会指向一个需要被渲染的图元列表,而不会包含任何材质信息。

注意,当Draw Call使用过多时,会造成CPU负载过大,导致CPU过载,所以Draw Call中造成性能问题是CPU的锅

4.GPU流水线

当GPU从CPU得到渲染命令后,就会进行一系列流水线操作,最终完成渲染。

5.顶点着色器

顶点着色器是流水线的第一个阶段,他的输入来自CPU。顶点着色器的处理单位是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身和顶点没有操作空间(指get不到信息,也没法操控)。但正是这样的相互独立性,GPU可以利用本身的特性并行化处理每个顶点,这意味着处理速度很快。

6.裁剪和屏幕映射

裁剪指摄像机视野范围外的场景物体的顶点不会被渲染,这可以有效减少不必要的负担。
屏幕映射是吧每个图元的x和y坐标转换到屏幕坐标系下。屏幕坐标系是一个二维坐标系,和屏幕的分辨率有很大关系。
OpenGL把屏幕左下角作为窗口的最小坐标值,但DirectX则是定义了窗口的左上角为最小的窗口坐标值

7.三角形设置,三角形遍历

三角形设置指的是计算光栅化一个三角形网格所需的信息。具体来说,上一个阶段输出的都是三角网格的定点,即我们得到的是三角形网格每条边的两个端点。而这个阶段就要得到三角形的完整数据。三角形遍历中会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元。
这个过程就是三角形遍历,也被称为扫描变换。
一个片元并不是真正意义上的像素,而是包含了很多状态的集合。

8.片元着色器

片元着色器是另一个非常重要的可编程着色器阶段。
前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角形网格是怎样覆盖每个像素的。而每个片元就负责储存这样一系列数据。
片元着色器的输入时上一个阶段对顶点信息插值得到的结果,是根据那些从顶点着色器中输出的数据插值得到的。而它的输出是一个或多个颜色值。
这一阶段可以完成很多重要的渲染技术,最重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标了。
片元着色器的局限在于,它仅可以影响单个片元。也就是说,当执行片元着色器时,它不可以将自己的任何结果直接发送给它的邻居们。

9.逐片元操作

这是渲染流水线的最后一步。逐片元操作时OpenGL说法,在DirectX中被称为输出合并阶段。这一阶段重在合并,而且是对每一个片元。
这一阶段的几个重要任务:
(1)决定每个片元的可见性。
(2)如果一个片元通过了所有的测试,就需要把这个偏远的颜色值和已经储存在颜色缓冲区中的颜色进行合并,或者说是混合。
逐片元操作阶段是高度可配置性的,我们可以自定义每一步的操作细节。
这个阶段首先要测试,测试对象是每个片元,来验证片元是否适格,适格者才配和GPU交流。如果不适格,就寄了,会被舍弃掉。

1.模板测试

与之相关的是模板缓冲。实际上,模板测试和我们经常听到的颜色缓冲、深度缓冲几乎是一类东西。如果开启了模板测试,GPU会首先读取(使用读取掩码)模板缓冲区中该片元位置的模板之,然后将该值和读取的参考值进行比较(开发者指定)。不管有没有通过模板测试,我们都可以根据模板测试和下面的深度测试结果来修改模板缓冲区,这个操作也是由开发者指定的。

2.深度测试

这个测试同样是高度配置的。如果开启了深度测试,GPU会将该片元的深度值和已经存在于深度缓冲区中的值进行比较。和模板测试不同的是,如果该片元没通过深度测试,就无法更改深度缓冲区中的值。而如果通过了,开发者还可以指定是否要用这个片元的深度值覆盖掉原有的深度值。

3.合并功能

渲染过程是一个接着一个物体画到屏幕上面的。而每个像素的颜色信息被存储在一个名为颜色缓冲的地方。因此,当我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,合并需要解决的问题就是这次渲染得到的颜色完全覆盖掉之前的结果,还是进行其他处理?这就是合并需要解决的问题。
对于不透明物体,开发者可以关闭混合操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就需要使用混合操作让这个物体看起来是透明的。

从中我们可以发现,混合操作也是可以高度配置的。

4.双重缓冲

当模型的图元经过了上面层层计算和测试后,就会显示到我们的屏幕上。我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到那些正在光栅化的图元,GPU会采用双重缓冲的策略。这意味着,对场景的渲染是在幕后发生的,即在后置缓冲中。一旦场景已经被渲染到了后置缓冲中,GPU就会交换后置缓冲区和前置缓冲中的内容,而前置缓冲区是之前显示在屏幕上的图像。由此,保证了我们看到的图像总是连续的。
这就很类似于表演时的台前幕后,化妆都是在幕后进行,观众看到的一定是最完美的。

10.总结

虽然渲染是个复杂的过程,但Unity将这一过程封装了,在Unity Shader中只需设置输入、编写顶点着色器和片元着色器、设置状态即可。
但封装会导致编程自由度降低,这往往会使很多初学者迷失方向,无法理解背后原理。

补充:OpenGL和DirectX的说明

OpenGL和DirectX都是API接口,为了方便用户使用,也都有自己的编译语言,在Shader中,我们一般使用的是Cg/HLSL语言,在之后会有说明。

二、如何创建Shader

在Assets栏中找到位置创建创建后会出现如下标识

初始命名一般都是NewSurfaceShader,点击可直接进入代码

代码如下:

Shader "Custom/NewSurfaceShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

而我们也可以将Shader添加进材质里,创建一个材质以后,在Inspector窗口中找到以下位置。

点进去后就可以发现我们刚刚创建的Shader。

选中想要添加的Shader即可。添加完以后,这个材质将具有Shader的特性。
一个单独的Shader是无法发挥作用的,必须与材质结合起来,才能发挥作用。
Unity Shader本质是一个文本文件。和许多外部文件一样,它也有导入设置面板。如图所示

在该面板上,我们可以在Default Map中指定该Unity Shader使用的默认纹理。当任何材质第一次使用该Unity Shader时,这些纹理就会自动被赋予到相应的属性上。
在下方的面板上,Unity会显示一些和该Unity Shader相关的信息。
还有一些信息是和我们在Unity Shader中的标签设置有关。
对于表面着色器而言,我们可以通过单机Show generated code按钮来打开一个新的文件,在文件里将显示Unity在背后为该表面着色器生成的顶点/片元着色器。
Compile and show code下拉列表可以让开发者检查该Unity Shader针对不同图像的编程接口最终编译成的Shader代码,如图中所示,直接单击该按钮可以查看生成的底层汇编指令。我们可以利用这些代码来分析和优化着色器。

三、ShaderLab

ShaderLab是Unity Shader的基础,使得开发者可以通过ShaderLab直接编写Unity Shader。
ShaderLab是编写Unity Shader的一种说明性语言。它使用了一些嵌套在花括号内的语义来描述一个Unity Shader文件的结构。这些结构包含了许多渲染需要的数据。
从设计上来说,ShaderLab定义了要显示一个材质所需的所有东西,而不仅仅是着色器代码

Shader "ShaderName"{
  properties {
  //属性
  }
  SubShader {
  //显卡A使用的子着色器
  }
  SubShader {
  //显卡B使用的子着色器
  }
  Fallback"VertexLit"
}

Unity在背后会根据使用的平台来把这些结构编译成真正的代码和Shader文件,而开发者只需要和Unity Shader打交道即可。


以上是关于废物程序员做梦都在写Shader的主要内容,如果未能解决你的问题,请参考以下文章

Cg入门20:Fragment shader - 片段级模型动态变色(实现汽车动态换漆)

删除项目工程里的未使用Shader

Cg入门19:Fragment shader - 片段级模型动态变色

Shader2.0的顶点着色器和片段着色器

Shader HLSL片段说明

给dubbo贡献源码,做梦都在修bug