Unity_TA 养成记

烂笔头

顶点=>世界坐标

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

什么是ShaderLab?

在图形API中有:OpenGL,DX,Vulkan等。Unity为了处理不同平台使用的Shader语言的差异,就用ShaderLab封装起来,最后根据不同平台编译成对应的着色器语言。

UnityShader 结构图:

Shader
SubShader
SubShader
SubShader
Pass
Pass
Program:Vertex
Program:Fragment
SubProgram:Variant 1
SubProgram:Variant 2

Shader 变体

1.什么是变体

官网介绍
在上面的结构图最底下的Variant就是变体部分,如下所示frag中有三个判断分支,Unity就会产生多个Shader变体。如果变体数量太多就会增加内存消耗,一般ShaderLab占用内存在30-70M左右,如果超出太多就要考虑优化变体数量。

        fixed4 frag (v2f i) : SV_Target
            {
                #if _OVERLAY_1
                	col += secCol;
                #elif _OVERLAY_2
                	col *= secCol;
                #endif
                	col *= _OtherCol;
                return col;
            }

2.变体的产生

主要是在Shader中使用了:#pragma shader_feature#pragma multi_compile起到宏的作用。不一样的是 shader_feature 没有用到的不会被包含进去, 而 multi_compile全部版本都会被包含,因此 shader_feature 适用于那些会在材质面板中直接设置的情况,但在脚本里通过DisableKeyword 和 EnableKeyword 来开启或关闭 keyword 的话就要使用 multi_compile

3.变体数量计算

Case1
#pragma multi_compile A B C
#pragma multi_compile D E
如果shader中使用了以上两个multi_compile那么不管后面是否使用到宏(A,B,C,D,E)它都会产生变体,这样计算的方法是(A,B,C)(D,E) => 32=>6。
Case2
#pragma multi_compile A B C
#pragma shader_feature D E
这里有两个shader_feature,当他们启用才会产生变体因此有以下几种组合情况:
(A+D,B+D,C+D) ,(A+E,B+E,C+E),(A+D,B+D,C+D,A+E,B+E,C+E)
Case3
#pragma multi_compile A B C
#pragma shader_feature _ D E
这里的(’_’)表示空,当D或E启用时会产生:(A,B,C,A+D,B+D,C+D) 或 (A,B,C,A+E,B+E,C+E)

4.变体查看

Unity在编辑器里提供了变体查看的方法,我们在shader中添加#pragma shader_feature_local CS_BOOL为例演示如何查看:
在这里插入图片描述
1.选中shader,在inspector面板中有个Keywords,它里面包含了全局和本地的所有keword。
2.点击inspector上的Compiled code右边的倒三角,再点击弹出框内的Show按钮即可看到当前shader用到的Keyword。

5.减少变体

1.shader内剔除
我们可以在shader中使用#pragma skip_variants来剔除不想要的Keyword变体。如下所示在shader中添加#pragma skip_variants CS_BOOL后就可以屏蔽该Keyword。
在这里插入图片描述

2.Build时剔除(只能用于Global )
使用以下方法剔除时发现无法把Local类型的keyword剔除掉,如果有哪位晓得方法麻烦留言指导下哈,非常感谢。

using System.Collections;
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;

namespace ZYF
{
    /// <summary>
    /// 在Build时剔除shader keyword
    /// </summary>
    public class ZYF_Test_ShaderKeywordStripping : IPreprocessShaders
    {
        public int callbackOrder => 0;

        //要剔除的keyword
        ShaderKeyword cs_bool_keyword;
        public ZYF_Test_ShaderKeywordStripping()
        {
            cs_bool_keyword = new ShaderKeyword("CS_BOOL");
        }
        public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
        {
            List<ShaderCompilerData> strippingList = new List<ShaderCompilerData>();
            //收集要剔除的data
            for (int i = 0, length = data.Count; i < length; i++)
            {
                if (data[i].shaderKeywordSet.IsEnabled(cs_bool_keyword))
                {
                    strippingList.Add(data[i]);
                }
            }
            //剔除收集到的data
            strippingList.ForEach(d =>
            {
                data.Remove(d);
            });
        }
    }
}

6.变体在Editor中的生成过程

在这里插入图片描述

Shader Load

Shader预加载

使用ShaderVariantCollection预加载指定Shader
官方说明
ShaderVariantCollection里记录着每个shader里真正使用到的变体,它可以用来预加载shader。
1.首先进入运行模式并让摄像机看到整个场景,然后到ProjectSettings/Graphics的最下方点击Save to asset保存。
2.把保存的SVC文件放到Project Settings/Graphics 中的Preloaded Shaders中,这样就能预加载shader了,当然你可以按需求修改SVC内容。
3.还可以在代码中进行预加载(因为预加载比较耗时最好放在加载场景中执行):Shader.WarmupAllShaders(预加载所有shader) 和ShaderVariantCollection.Warmup(加载指定SVC中的shader)。

Unity Shader入门精要

建议先看图形学基础知识
书籍github
官方文档

渲染流水线

概念流水线

应用阶段
几何阶段
光栅化阶段
输出渲染图元
输出屏幕空间的顶点信息

每个阶段也是一个流水线系统。
应用阶段:输出渲染所需的几何信息即渲染图元(点线面)
几何阶段:把顶点坐标变换到屏幕空间中,输出屏幕空间二维顶点坐标、每个顶点对应的深度值、着色等相关信息;
光栅化阶段:在GPU上运行,决定哪些像素被绘制,对逐顶点数据(纹理坐标、顶点颜色等)进行插值,然后逐像素处理。

CPU–>GPU

应用阶段
从硬盘加载数据到显存
设置渲染状态
调用DrawCall
定义网格渲染用啥着色器材质贴图
告诉GPU开始渲染

GPU流水线

概念阶段的几何、光栅化两个阶段开发者无法拥有绝对的控制权,其实现的载体是GPU.

光栅化阶段
几何阶段
三角形设置
三角形遍历
片元着色器
逐片元操作
顶点着色器
曲面细分着色器
几何着色器
裁剪
屏幕映射
顶点数据
几何阶段

顶点着色器(Vertex Shader):每个顶点都会调用一次顶点着色器,顶点与顶点之间相互独立。主要工作是坐标变换逐顶点光照。坐标变换把顶点坐标从模型空间转换到齐次裁剪空间(通过MVP变换坐标到-1~1
裁剪:把不在摄像机视野范围的物体处理掉。一般图元与摄像机的视野关系有3种:完全在视野内部分在视野内完全在事业外。完全在视野内的直接进入下一个流水线阶段,完全在视野外不继续进行处理,对部分在视野内的那些图元的处理就是裁剪处理方式:在图元与视野边界的交点处用一个新的顶点替代在视野外部的顶点。可以自定义裁剪操作但不可编程!
屏幕映射(Sreenn Mapping):把-1~1坐标转换到屏幕坐标系下=>走你。注意屏幕映射时不会对z坐标做处理,屏幕坐标系和z坐标构成了窗口坐标系(Window Coordinates)。

光栅化阶段

光栅化两个重要的目标是计算图元覆盖的像素和计算这些像素颜色。

三角形设置(Triangle Setup):计算三角网格表示数据的过程。

三角形遍历(Triangle Traversal):遍历像素并找到那些被三角网格覆盖的过程。也叫扫描变换(Scan Conversion)。
它还有用三角网格的3个顶点的顶点信息对覆盖区域像素进行插值。输出得到一个片元序列,片元不是真正意义上的像素,它还包含了很多状态(屏幕坐标、深度信息…法线、纹理坐标…)。

片元着色器(Fragment Shader):片元着手之前的阶段并不会影响屏幕颜色值,而是会产生一系列的数据信息。此阶段的输入是那些从顶点着色器输出数据插值得到的。在片元着色器中进行纹理采样需要在顶点着色器输出每个顶点对应的纹理坐标,这样就可以获得插值后的片元纹理坐标了。

逐片元操作(Per-Fragment Operations):这一阶段主要任务是决定每个片元的可见性(深度测试、模板测试...),如果通过测试就要把颜色值与颜色缓冲区中的颜色进行混合。

片元
模板测试
深度测试
混合
颜色缓冲区

模板测试:通常用来限制渲染区域,还有渲染阴影、轮廓渲染等。
各种测试:测试顺序并不是唯一的,为了提高性能Unity把深度测试放到了片元着色器之前,这种技术称为Early-Z技术。但有时候提前测试会与片元着色器种的一些操作冲突。
混合(Blend)操作:把当前渲染的颜色与颜色缓冲中的颜色进行混合。不透明物体可以之间关闭混合操作,之间覆盖颜色缓冲区上的像素值。但是半透明物体需要混合操作让物体看起来是透明的。

Draw Call

Draw Call 就是CPU 调用图像编程接口以命令GPU进行渲染的操作。Draw Call造成性能问题的元凶是CPU,而不是GPU。

CPU和GUP并行工作

CPU向命令缓冲区(Command Buffer)添加命令,然后GPU从中读取命令,命令缓冲区中有很多种类的命令,DrawCall只是其中一种。

Draw Call ==》帧率

Draw Call 数量过多 CPU就会把大量的时间花费在提交Draw Call上,造成CPU过载性能下降。其中一个减少Draw Call的方法是批处理(Batching),在CPU的内存中合并网格,把很多小的Draw Call合并成一个大的Draw Call,合并是需要消耗时间的因此对那些静态的物体更适合,动态的每帧都需要重新合并。合并的网格将会使用同一种渲染状态,如果网格之间需要使用不同的渲染状态就无法使用批处理技术!

Shader

Shader所在的阶段就是渲染流水线的一部分,是GPU流水线上一些可高度编程的阶段。有特定类型的着色器:顶点着色器、片元着色器等。在Unity中我们可以方便的编写着色器并且又可以设置渲染状态。

Unity Shader

Unity 使用ShaderLab语言编写Unity Shader文件。

模板介绍

1.Standard Surface Shader:包含标准光照模型的表面着色器模板。
2.Unilit Shader:不包含光照的基本顶点/片元着色器。
3.Image Effect Shader:屏幕后处理效果模板。
4.Compute Shader:利用GPU并行性进行一些与常规渲染流水线无关的计算。

结构
命名

Shader "Custom/MyShader":由字符串定义,可以加斜杠“/”来控制在材质面板中出现的位置。示例位置为:Shader->Custom->MyShader

Properties
   Properties
    {
    	/*Name("显示名称",属性类型) = 默认值*/
        _MainTex ("Texture", 2D) = "white" {}
    }

属性可以让我们在材质面板中方便的调整,在Shader中通过属性的名字(Name)访问。这些属性的名字通常是由下划线开始。

属性类型默认值的定义语法例子
Intnumber_Int(“Int”,Int)=2
Floatnumber_Float(“Float”,Float) =1.5
Range(min,max)number_Range(“Range”,Range(0.0,5.0))=3.0
Color(number,number,number,number)_Color(“Color”,Color)=(1,1,1,1)
Vector(number,number,number,number)_Vector(“Vector”,Vector)=(2,3,4,5)
2D“defaulttexture”{}_2D(“2D”,2D)=""{}
Cube“defaulttexture”{}_Cube(“Cube”,Cube)=“white”{}
3D“defaulttexture”{}“black”{}
纹理类型

2D、Cube、3D这3种纹理类型的默认值是由字符串和花括号组成,字符串是纹理名称,要么是空要么是内置的纹理名称:white、black、gray、bump花括号是用于控制固定管线的纹理坐标生成时指定一些纹理属性,在Unity5.0后选项被移除了,如果需要类似的功能要在顶点着色器中编写计算相应的纹理坐标代码。

访问属性

为了在Shader中访问属性需要在CG代码片段中定义和这些属性类型相匹配的变量,Properties语义块中声明的属性只是为了让我们可以在材质面板看见修改而已,我们可以直接通过代码向Shader传递属性而不定义在Properties中。

赋值
赋值
赋值
Properties
CG变量
脚本代码
材质面板
SubShader
SubShader{
	//可选
	[Tags]
	//可选
	[RenderSetup]
	Pass{}
	Pass{}
	.
	.
	.
}

一个Unity Shader文件可以包含多个SubShader语义块(至少一个),因为不同的显卡支持的操作指令数目不同,Unity会扫描所有的SubShader找到一个能在目标平台运行的SubShader,在都不支持的情况下就会调用Fallback语义指定的UnityShader。
SubShader中定义了一系列的Pass以及可选的状态[RenderSetup]和标签[Tags]。
每个Pass定义了一次完整的渲染流程,所以Pass数量不能太多。
状态标签同样可以在Pass声明,但标签是不一样的。在SubShader设置了状态将会用于所有的Pass。

状态名称设置命令解释
CullCull (Back / Front / Off)设置剔除模式:剔除背面 /正面 /关闭剔除
ZTestZTest (Less Greater /LEqual /GEqual /Equal /NotEqual /Always)设置深度测试时使用的函数
ZWriteZWrite (On /Off)开启 /关闭深度写入
BlendBlend SrcFactor DstFactor开启并设置混合模式
SubShader标签类型说明例子
Queue控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染,可以自定义使用的渲染队列来控制物体的渲染顺序Tags{“Queue”=“Transparent”}
RenderType着色器分类,例如这是一个不透明的着色器,或是一个透明的着色器等。可以被用于着色器替换功能(ShaderReplacementTags{“RenderType”=“Opapue”}
DisableBatching一些SubShader在使用Unity的批处理功能时会出现问题,比如使用了模型空间下的坐标进行顶点动画,这时可以通过该标签直接禁用批处理功能Tags{“DisableBatching”=“True”}
ForceNoShadowCasting控制使用该SubShader的物体是否投射阴影Tags{“ForceNotShadowCasting”}
IgnoreProjector如果该标签值为”True“,那么使用该SubShader的物体将不会受Projector的影响,通常用于半透明物体Tags{“IgnoreProjector”=“True”}
CanUseSpriteAtlas设置CanUseSpriteAtlas标签为“False”,如果着色器是为精灵设计的,并且当他们被打包到图集时将不能工作Tags{“CanUseSpriteAtlas”=“False”}
PreviewType指明材质面板将如何预览该材质。默认为球形,可以设置为”Plane“ ”SkyBox“来改变预览类型Tags{“PreviewType”=“Plane”}
Pass
Pass{
	[Name]
	[Tags]
	[RenderSetup]
	//其它...
}

可以在Pass中定义该Pass名称:Name “PassName”,通过这个名字我们可以在其它Unity Shader中用UsePass命令直接引用该Pass: UsePass “xxxShader /PASSNAME”。因为Unity会自动把所有Pass名字转成大写字母,所以UsePass必须用大写形式!

Pass标签类型说明例子
LightMode定义该Pass在Unity的渲染流水线中的角色Tags{“LightMode”=“ForwardBase”}
RequireOptions用于指定当满足条件时才渲染该Pass,目前支持选项”SoftVegetation“Tags{“RequireOptions”=“SoftVegetation”}

除了UsePass外还有GrabPass:抓取屏幕并将结果存储在纹理中用于后续的Pass处理。

数学基础

参考笔记

坐标系

模型空间与世界空间Unity使用的是左手坐标系。观察空间(以摄像机为原点的坐标系,摄像机前向是z轴的负方向)使用的是右手坐标系。

矢量

矢量 * 标量:对矢量进行缩放。标量为负数会让矢量方向取反。
矢量加减法:对应分量相加减。
矢量的:矢量在空间中的长度,对每个分量的平方相加后的值开根号。
单位矢量:模长为1的矢量。

矢量点积:对应分量相乘后取和, 结果是一个标量。矢量 . 单位矢量 =在该单位矢量方向的投影
公式一: a . b =(ax,ay,az).(bx,by,bz) = ax bx + ay by + az bz.。
公式二:a.b=|a|b|cosθ。

矢量叉积:a x b = (ax ,ay ,az) x (bx, by, bz) =(ay bz - az by, az bx - ax bz,ax by - ay bx)。
叉积不满足交换律(axb =-(bxa)),也不满足结合律。
叉积的几何意义:结果会得到一个同时垂直于这两个矢量的新矢量。
叉积模长:|axb| = |a||b|sinθ = 平行四边形面积。
叉积方向:需要根据使用的坐标系判断方向。

矩阵

矩阵 * 标量:矩阵的每个元素与这个标量相乘,结果还是个矩阵。
矩阵 * 矩阵:要满足[n x m] * [m x k],不满足交换律,满足结合律。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牙膏上的小苏打2333

哟,哟,切克闹,煎饼果子来一套

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值