查看: 178|回复: 0

[简易教程] Unity_Shader,作用流程入门程精讲

[复制链接]

724

主题

76

听众

7698

积分

头头

Rank: 12Rank: 12Rank: 12

发表于 2021-1-22 14:50 |显示全部楼层
Unity_Shader

写在前面
    虽说已经入门(至少我觉得入门了?)shader有些时间了,不过由于慢慢的对入门级的东西有了新的感悟,再者组织要喊我给学弟学妹们培训一手,遂准备记下这篇内容作为教材。 在我入门shader那会,啃过学长给的书,摸过siki老师的课,看过知乎大佬的专栏,无一例外,大家都是底层开篇,上手开始GPU渲染流程,渲染流水线。很专业的开篇,但对于没有底层基础和图形学基础的人来说,可能有点难以接受,更难融会贯通真正的用上。 所以今天我写的这篇入门呢,主要是给没什么底层基础,图形学基础的小白入门参考。 本文主要讲述图形渲染流水线大概是个什么样子(参考知乎大佬王子饼干的shader魔法书专栏),shader在这个流水线中怎样作用,CPU和GPU在后台工作时的区别,用shader处理图像和用c#处理图像有什么区别,一个最简单的shader。 注意:本篇涉及到的绝大部分思维上的例子,举例所用的数字,纯属个人理解上的虚构,并非真正的底层知识,所以只能做一个领进门的作用,底层,还是要找专业的资料好好学一学的。哦对了,这篇文章实践性内容不多,所以可能会很枯燥,但如果你学的下去,小白入门进度会快很多。
渲染流水线

    :本部分极大参照了知乎王子饼干的专栏,UnityShader魔法书第一节,贴个蓝链
,有一说一,这专栏讲的很通俗易懂了,大爱。
什么是渲染流水线

    大概就是说,你做游戏的时候这个游戏物体,本身也是一堆数据,他要经过“渲染”,这才会出现在屏幕上。而渲染流水线,就是将一团数据层层包装迭代筛选,最终呈现在屏幕上的过程。
渲染流水线的各大阶段

渲染流水线主要有三个阶段,应用阶段,几何阶段,光栅化阶段。
    应用阶段:在这个阶段,程序会拿到之后所用到的很多很多信息,诸如:场景中的物体模型,场景中的光源信息,以及我们所为其输入的一些数据。在这个阶段最后,会输出渲染所需要的几何信息到下一个工位。几何阶段:该阶段通常处理的是几何空间下的很多事情,诸如对模型的顶点信息进行空间变换,去取到一些信息来决定要绘制的图像,最后呢,这个阶段会将顶点信息输出到二维屏幕坐标下,然后传递给下一个工位。 光栅化阶段:该阶段是讲之前的内容拿过来,并合并计算一个像素点的信息,最终决定屏幕上显示内容的部分。 - 接下来看一张图,其中我只写入了本节需要我们关注的三部分,这三部分我会详细说一说。其他部分全部省略了,日后需要自行找找资料详细学习,其底层流程。


应用阶段_程序与数据的读入

    首先关于这部分呢,是系统要做的事,而不是我们要做的,但需要简单知道系统做了什么,知道在什么情况下做什么东西用c#做什么东西用shader。Shader代码是执行在GPU上的,和我们通常写的C#代码不同,其仅仅支持一些简单的逻辑操作。下面看一张图来了解下这俩个执行在CPU与GPU上的程序各自的优缺点。


    如上图,蓝色部分是存储单元,其为CPU和GPU提供了直接访问权限,故而将信息读入这些存储单元再进行使用将会提升速度,这就是执行前读入数据的过程。而红色部分是今天的重点,其是运算单元,CPU的运算单元更加强大,但量少,强大在其可以执行很复杂的逻辑运算,但量少导致其面对大量简单问题时显得乏力。而GPU的运算单元数量很庞大,但本身能力较弱,不支持很复杂的逻辑,但其数量决定了其可以快速处理海量的简单问题。曾见过一个网友举例:CPU就像几个大学生,你让他算什么都可以,但你让他算一万个二位数加法,他得算几个小时。而GPU像一万个小学生,你让他们一起算一个微积分,他们算不得,但你给他们分配下去一万个俩位数加分,在一人一题的情况下,不出一分钟就解决了。也正是这种特点,CPU更适合我们后台的逻辑脚本,GPU更适合做渲染显示方面的工作。拿一张图的后处理来说,这张图有1万个像素点,你要把所有点都变深一点,用CPU可以用循环做很多很多次遍历,最终遍历完整张图,而GPU可以直接给每一个运算单元分配一块像素,各自开工,瞬间解决。也正是因为GPU和CPU的这种区别,使得我们写的shader代码中诸如顶点着色器,片元着色器效率能很高,顶点着色器会分给很多个运算单元各自计算各自负责的顶点,片元着色器分给很多个运算单元各自计算各自负责的片元。
几何阶段_顶点着色器

    这个阶段就是我们需要编写进shader中的第一个阶段了,主要负责从应用阶段拿到我们需要的一些参数,然后根据这些东西计算出剪裁空间下的顶点坐标,然后传给后面的工位(当然你也可以做一些其他的事情)。取参数:到这里就不得不提一下shader中参数从何而来,在系统渲染时会自动调用我们写好的顶点着色器,并按照我们规定自动传入参数,所以我们需要告诉系统,我们需要哪些参数,这个告知方式固然不是自己想怎么说就怎么说,要使用和系统约好的“暗号”,专业点的术语叫做语义绑定。 下面举个小例子:
  1. struct VertexInput
  2.             {
  3.                 float4 Pos:POSITION;            //语义绑定
  4.                 float2 uv:TEXCOORD0;         //语义绑定
  5.             };
  6. [list]如上定义了一个结构体其中包含的参数都绑定了语义,这些语义做什么用的参照
  7. [/list],这样一来将这个结构体作为定点函数的参数传递过去即可。
  8. [list][b]函数绑定[/b]和取参数一样,都是需要指明哪个函数是顶点函数,这样系统才不会调用错。 举例如下
  9. [/list]<div class="highlight">#pragma vertex _Vert
复制代码
    如上绑定了顶点函数,名字为_Vert。      #pragma为绑定函数的关键词,vertex为指明绑定的函数为顶点函数。
光栅化阶段_片元着色器

    这个阶段与顶点着色器基础思想山很相似,不过作用主体是光栅化后的片元,什么是片元,你可以直接理解成一块块的像素点,详细的可以自己搜索下。而其最终需要返回一个颜色值,就是当前片元的颜色了。取参数:与之前顶点着色器很相似,都是定义一个结构体,然后让这个结构体内的参数绑定语义,在当做参数传进去。不过需要注意的是,其中涉及到需要顶点着色器传给片元着色器的参数,因此顶点着色器需要返回一个这样的结构体,以便后面片元着色器直接取到这些参数。 举例如下:
  1. struct VertexOutput
  2.             {
  3.                 float4 Pos:SV_POSITION;
  4.                 float2 uv:TEXCOORD0;
  5.             };
复制代码
    函数绑定这部分与顶点部分差不多,只是vertex换成了fragment
  1. #pragma fragment Pixel
复制代码
    如上便绑定了片元着色器函数,函数名为Pixel
简单例子

    举个最简单的渲染例子,我们写一个shader封装成材质给胶囊体上色。右键资源管理器,create->shader->Standard Surface Shader   创建一个新的表面着色器。删除其中内容开始写,首先是整体结构搭好如下:
  1. Shader "Custom/test"          //指明其为shader代码,后面的字符串是其在材质选择渲染器时的路径
  2. {
  3.     Properties                        //公开型变量,在材质的属性面板可以修改
  4.     {
  5.     }
  6.     SubShader                      //语法之一写就完事了,之后在理解。
  7.     {
  8.     }
  9.     FallBack "Diffuse"            //默认声明,表示如果我们写的SubShader在当前设备上不支持,就用默认渲染
  10. }
复制代码
    然后向变量区写入一个color类型的,表示最终渲染成什么颜色。
  1. Properties                        //公开型变量,在材质的属性面板可以修改
  2.     {
  3.                 _Color ("Color", Color) = (1,1,1,1)
  4.     }
复制代码
    接着在SubShader中写入一个新的pass块(相互独立,按序执行渲染的具体渲染块),在其中写入CG语法应用范围(使用CGPROGRAM关键词表示开始应用cg语法,使用ENDCG关键词表示结束),并在范围内写入头文件声明,函数绑定。
  1. SubShader
  2.     {
  3.         pass
  4.         {
  5.                 CGPROGRAM
  6.                 #pragma vertex _Vert
  7.                 #pragma fragment Pixel
  8.                 #include "UnityCG.cginc"
  9.                 ENDCG
  10.         }
  11.     }
复制代码
    接着定义输入输入结构体,声明要用到的变量(当声明变量名字和Properties块中一样的时候,就表示你这个变量是那个外界可以调的)。
  1. struct VertexInput
  2.                 {
  3.                     float4 Pos:POSITION;
  4.                 };
  5.                 struct VertexOutput
  6.                 {
  7.                     float4 Pos:SV_POSITION;
  8.                 };
  9.                 fixed4 _Color;
复制代码
    然后是顶点着色器和偏远着色器的编写
  1. VertexOutput _Vert(VertexInput v)
  2.                 {
  3.                     VertexOutput r;
  4.                     //将顶点转换到剪裁空间
  5.                     r.Pos = UnityObjectToClipPos(v.Pos);
  6.                     return r;
  7.                 }
  8.                 fixed4 Pixel(VertexOutput v):SV_Target
  9.                 {
  10.                     //直接将定义好的颜色返回为该片元的颜色
  11.                     return _Color;
  12.                 }
复制代码
    到这里就编写完了,完整代码如下:
  1. Shader "Custom/test"
  2. {
  3.     Properties
  4.     {
  5.         _Color ("Color", Color) = (1,1,1,1)
  6.     }
  7.     SubShader
  8.     {
  9.         pass
  10.         {
  11.                 CGPROGRAM
  12.                 #pragma vertex _Vert
  13.                 #pragma fragment Pixel
  14.                 #include "UnityCG.cginc"
  15.                 struct VertexInput
  16.                 {
  17.                     float4 Pos:POSITION;
  18.                 };
  19.                 struct VertexOutput
  20.                 {
  21.                     float4 Pos:SV_POSITION;
  22.                 };
  23.                 fixed4 _Color;
  24.                 VertexOutput _Vert(VertexInput v)
  25.                 {
  26.                     VertexOutput r;
  27.                     //将顶点转换到剪裁空间
  28.                     r.Pos = UnityObjectToClipPos(v.Pos);
  29.                     return r;
  30.                 }
  31.                 fixed4 Pixel(VertexOutput v):SV_Target
  32.                 {
  33.                     //直接将定义好的颜色返回为该片元的颜色
  34.                     return _Color;
  35.                 }
  36.                 ENDCG
  37.         }
  38.     }
  39.     FallBack "Diffuse"
  40. }
复制代码
    然后我们创建一个材质,如图红色箭头按照代码中编写的路径选好我们写的shader,然后蓝色箭头就是我们设定的颜色。


    拖到胶囊体上,效果如下
写在后面:到这里这篇要说的内容就结束了,不过并没有详细讲解shader代码中的一个常用标准格式,大家可以参照以下我以前的笔记

结束,感谢阅读

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

楼主热帖
人人为我 我为人人 互相分享 互相学习 互相进步 一带一路
温馨提示:求助请到“Unity技术讨论”版块中发帖,便于集中解决!
您需要登录后才可以回帖 登录 | 立即注册

Unity游戏引擎开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2021-2-28 08:01 , Processed in 0.100187 second(s), 37 queries .