• c#语言

    2019-07-14 21:02:53
    从游戏脚本语言说起,剖析Mono所搭建的脚本基础 ...最后会通过模拟Unity3D游戏引擎中的脚本功能,将Mono运行时嵌入到一个非托管(C/C++)程序中,实现脚本语言和“引擎”之间的分离。阅读全文 ...
    摘要: 在日常的工作中,偶尔能遇到这样的问题:“为何游戏脚本在现在的游戏开发中变得不可或缺?”。那么这周我就写篇文章从游戏脚本聊起,分析一下游戏脚本因何出现,而mono又能提供怎样的脚本基础。最后会通过模拟Unity3D游戏引擎中的脚本功能,将Mono运行时嵌入到一个非托管(C/C++)程序中,实现脚本语言和“引擎”之间的分离。阅读全文
    posted @ 2015-06-07 02:18 慕容小匹夫 阅读(2509) | 评论 (21) 编辑
     
    摘要: 0x00 分类C#语言规范主要有两个来源,即我们熟知的ECMA规范和微软的规范。尽管C#的ECMA规范已经前后修订4次,但其内容仅仅到C# 2.0为止。所以慕容为了方便自己和各位方便查询,在此将常见的C#规范总结一下。0x01 微软标准这里提供的内容,都来自于微软官网。C# 5.0 (2012年,W...阅读全文
    posted @ 2015-05-27 08:50 慕容小匹夫 阅读(183) | 评论 (0) 编辑
     
    摘要: 写篇小文来聊聊在Mac上如何使用Visual Studio Code编译和调试C#代码,最后匹夫也会尝试把Visual Studio Code嵌入Unity3D中,作为一个准“IDE”使用。阅读全文
    posted @ 2015-05-09 01:09 慕容小匹夫 阅读(4764) | 评论 (41) 编辑
     
    摘要: 0x00 前言在匹夫的上一篇文章《匹夫细说C#:不是“栈类型”的值类型,从生命周期聊存储位置》的最后,匹夫以总结和后记的方式涉及到一部分迭代器的知识。但是觉得还是不够过瘾,很多需要说清楚的内容还是含糊不清,所以这周就专门写一下c#中的迭代器吧。0x01 你好,迭代器首先思考一下,在什么情景下我们需要...阅读全文
    posted @ 2015-04-21 23:59 慕容小匹夫 阅读(6333) | 评论 (21) 编辑
     
    摘要: 0x00 前言:匹夫在日常和别人交流的时候,常常会发现一旦讨论涉及到“类型”,话题的热度就会立马升温,因为很多似是而非、或者片面的概念常常被人们当做是全面和正确的答案。加之最近在园子看到有人翻译的《C#堆vs栈》系列,觉得也挺有趣,挺不错的,所以匹夫今天也想从存储位置的角度聊聊所谓的值类型,同时也想...阅读全文
    posted @ 2015-04-12 19:31 慕容小匹夫 阅读(3313) | 评论 (62) 编辑
     
    摘要: 前言上一篇文章匹夫通过CIL代码简析了一下C#函数调用的话题。虽然点击进来的童鞋并不如匹夫预料的那么多,但也还是有一些挺有质量的来自园友的回复。这不,就有一个园友提出了这样一个代码,这段代码如果被编译成CIL代码的话,对虚函数的调用会使用call而非callvirt:override string ...阅读全文
    posted @ 2015-04-04 05:59 慕容小匹夫 阅读(2271) | 评论 (11) 编辑
     
    摘要: 匹夫细说CIL0x01.《用CIL写程序:你好,沃尔德》0x02.《用CIL写程序:写个函数做加法》0x03.《用CIL写程序:这个叫“慕容小匹夫”的类》0x04.《用CIL写程序:从“call vs callvirt”看方法调用》0x05.《Mono为何能跨平台?聊聊CIL(MSIL)》匹夫细说C...阅读全文
    posted @ 2015-02-22 23:40 慕容小匹夫 阅读(430) | 评论 (0) 编辑
     
    摘要: 以上一篇文章中的那个CIL实现的Hello Wolrd程序为基础,继续通过写CIL代码实现一些功能的方式来和各位探讨交流,同时也加深自己对CIL的掌握和印象。会涉及:函数的实现,局部变量的声明,cil是如何依托堆栈执行命令的,以及装箱~~~阅读全文
    posted @ 2015-02-04 23:57 慕容小匹夫 阅读(1491) | 评论 (5) 编辑
     
    摘要: 前言:首先,小匹夫要祝各位看官圣诞快乐,新年愉快~。上一篇文章《自己动手,实现一种类似List的数据结构(一)》 介绍了一下不依靠List实现的各种接口,仿造一个轻量级数据结构的过程。可能有的看官会有一些疑问,例如一些功能可以通过Linq提供的拓展来实现呀。此言不虚但也不全对,为了我们在工作中能方便...阅读全文
    posted @ 2014-12-27 17:07 慕容小匹夫 阅读(693) | 评论 (0) 编辑
     
    摘要: 前言上一篇文章《Unity3D中常用的数据结构总结与分析》简单总结了一下小匹夫工作中经常遇到的一些数据结构。不过小匹夫一直有种观点,就是光说的热闹实际啥也不做真的没啥意思。光说不练假把式,那么这篇文章不如记录一下小匹夫自己动手实现一个有类似功能的数据结构的过程吧。模仿List寻思半天,写代码是为了啥...阅读全文
    posted @ 2014-12-21 15:47 慕容小匹夫 阅读(974) | 评论 (8) 编辑
     
    摘要: 前言:最近小匹夫参与的游戏项目到了需要读取数据的阶段了,那么觉得自己业余时间也该实践下数据相关的内容。那么从哪入手呢?因为用的是Unity3d的游戏引擎,思来想去就选择了C#读取XML文件这个小功能。网上的例子倒也不少,但总是觉得缺点什么。比如读取xml文件之后该如何处理?看到的文章基本上都是手动创...阅读全文
    posted @ 2014-12-20 16:20 慕容小匹夫 阅读(1226) | 评论 (6) 编辑

    转载于:https://www.cnblogs.com/lihonglin2016/p/4614596.html

    展开全文
  • https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/ecs_entities.html System Update Order(系统更新顺序) 要使用“组件系统组”来指定系统的更新顺序,您可以在你的system类的声明中加入...

    以下文档均来源于ECS官网:

    https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/ecs_entities.html

    System Update Order(系统更新顺序)

    要使用“组件系统组”来指定系统的更新顺序,您可以在你的system类的声明中加入[UpdateInGroup]属性,这会将你的system放入到一个系统组中。 然后,您可以使用[UpdateBefore]和[UpdateAfter]属性来指定此system在系统组内的更新顺序。

    ECS框架创建一系列的默认系统组,您可以使用这些系统组在每帧对应的阶段来更新系统。 您可以将一个组嵌套在另一个组中,以便你的整个系统组在正确的阶段更新的同时,组内各系统之间据各自在组内的顺序进行更新。

    Component System Groups(组件系统组)

    ComponentSystemGroup类存储了一组相关联的组件系统,它们将按照指定的顺序一起更新。 ComponentSystemGroup派生自ComponentSystemBase,因此在所有重要方面它都与组件系统一样 -- 它可以相对于其他系统进行排序、具有一个OnUpdate()方法等。最重要的是,这就意味着组件系统组可以嵌套在其他的组件系统组内部,形成层次结构。

    默认情况下,当调用ComponentSystemGroup的Update()方法时,它会在其子系统列表中的每个子系统上调用Update()。 如果任何子系统本身就是系统组,那么它们将递归更新自己的子系统。 最终会产生一棵系统树,遍历顺序遵循深度优先。

    System Ordering Attributes(系统排序属性)

    以下现有的系统排序属性是保留的,它们具有略微不同的语义和限制。

    • [UpdateInGroup]  - 指定此System应该归属的ComponentSystemGroup。如果省略此属性,此System将被自动添加到默认World的SimulationSystemGroup(见下文)。
    • [UpdateBefore]和[UpdateAfter]  - 指定此System相对于其System的顺序。两个属性的参数所指定的系统类型必须是归属于同一个系统组。当排序指定已经跨组时,顺序将由在包含两个系统各自的组来处理:
      示例:如果SystemA在GroupA中,且SystemB在GroupB中,而GroupA和GroupB都是GroupC的成员,则GroupA和GroupB的顺序隐式确定SystemA和SystemB的相对顺序;没有必要明确排序系统。
    • [DisableAutoCreation]  - 避免在默认世界的初始化期间来创建本系统。您必须显式创建和更新系统,当然,您也可以将具有此标记的系统添加到ComponentSystemGroup的更新列表中,然后它将像该列表中的其他系统一样自动更新。

    默认系统组

    默认World包含了一个分层的ComponentSystemGroup实例,它向Unity播放循环中添加了三个根级的系统组(以下列表还显示了每个组中预定义的子System):

    • InitializationSystemGroup (在播放循环的Initialization 阶段结束时更新)
      • BeginInitializationEntityCommandBufferSystem
      • CopyInitialTransformFromGameObjectSystem
      • SubSceneLiveLinkSystem
      • SubSceneStreamingSystem
      • EndInitializationEntityCommandBufferSystem
    • SimulationSystemGroup (在播放循环的Update阶段结束时更新)
      • BeginSimulationEntityCommandBufferSystem
      • TransformSystemGroup
        • EndFrameParentSystem
        • CopyTransformFromGameObjectSystem
        • EndFrameTRSToLocalToWorldSystem
        • EndFrameTRSToLocalToParentSystem
        • EndFrameLocalToParentSystem
        • CopyTransformToGameObjectSystem
      • LateSimulationSystemGroup
      • EndSimulationEntityCommandBufferSystem
    • PresentationSystemGroup (在播放循环的PreLateUpdate 阶段结束时更新)
      • BeginPresentationEntityCommandBufferSystem
      • CreateMissingRenderBoundsFromMeshRenderer
      • RenderingSystemBootstrap
      • RenderBoundsUpdateSystem
      • RenderMeshSystem
      • LODGroupSystemV1
      • LodRequirementsUpdateSystem
      • EndPresentationEntityCommandBufferSystem

    请注意,此列表的具体内容可能会{译者注:在后续的版本中}发生变化。

    多个世界

    除了上述的默认世界之外,您还可以创建多个世界(也可以替换默认世界)。 同一个组件系统类,可以在多个World中实例化,并且可以从所在Wolrd更新顺序中的不同位置,以不同的速率更新每个实例。

    目前,还没有办法手动指定世界中的每一个系统的更新顺序。然而,你可以控制系统在哪个World中创建,以及应该将它们添加到哪个现有的系统组中。也就是说,自定义的WorldB,可以实例化SystemX和SystemY,将SystemX添加到该World默认的SimulationSystemGroup,并将SystemY添加到该World默认的PresentationSystemGroup。 这些系统可以相对组内的兄弟系统进行排序,并将跟随所在的组一起更新。

    为了支持这种用法,新的接口ICustomBootstrap应运而生:

    public interface ICustomBootstrap
    {
        // 返回那些需要被默认的引导过程{译者注:创建默认的World和入口点,并排序系统}处理的系统
        // 如果返回null,那么默认的World都不会被创建.
        // 如果返回空的列表,将会创建默认的World和入口点
        List<Type> Initialize(List<Type> systems);
    }
    

    当你实现该接口后,在默认世界的初始化之前,包含所有组件系统类型的列表将被传递给该实现类的Initialize()方法。定制此引导程序时,你需要遍历整个系统类型列表,并在将系统创建在所需要World中。 您也可以从Initialize()方法返回系统类型列表,它们将被创建在常规的、默认初始化的世界中。

    例如,以下是自定义 MyCustomBootstrap.Initialize()实现的典型过程:

    1. 创建任何其他世界(World),及其顶级的系统组(ComponentSystemGroup)。
    2. 遍历系统类型列表中的每个单元:
      1. 向上遍历ComponentSystemGroup的层次结构,以查找此系统类型所在的顶级系统组
      2. 如果该系统组是在步骤1中创建的,那么就在该系统组相应的World中创建该系统,并使用group.AddSystemToUpdateList()方法来将系统增加到更新列表。
      3. 如果不是,则增加这类系统类型到返回列表,留给后续的默认世界初始化。
    3. 在新建的顶层系统组上调用 group.SortSystemUpdateList().
      1. 可选地,将这些新建的系统组增加到默认的世界系统组内部。
    4. 那些未处理过的系统将在默认世界初始化时,得到相应处理。

    注意: ECS框架是通过反射来找到你的ICustomBootstrap实现的.

    技巧和提示

    • 使用 [UpdateInGroup] 来指定你所写的每个系统所在的系统组。 
      如果没有指定的话, 默认所在的系统组是SimulationSystemGroup.
    • 在Unity播放循环之外,你需要手动Tick每个系统组来更新子系统.
      增加 [DisableAutoCreation]属性到一个系统(或系统组) ,可以避免它被创建或者增加到默认的系统组。 你仍然可以通过World.GetOrCreateSystem()来手动创建系统,再通过调用MySystem.Update()方法在主线程更新它。这是在Unity播放循环之外插入系统的简易方法(例如,你有一个系统,它会在每帧的后面或者前面执行).
    • 尽可能使用现有的EntityCommandBufferSystem,而不是增加新的实体命令缓存系统。
      一个EntityCommandBufferSystem 代表了一个同步点,此时,主线程需要等待作业线程完毕,才能执行该缓存内部那些未处理的实体命令。相对于创建新的实体命令缓存系统来说,直接使用那些预定义在根级系统组中的,成“Begin / End”对的实体命令缓存系统,不太可能在帧流水线中引入新的“气泡”{译者注:新的同步点}。
    • 避免在ComponentSystemGroup.OnUpdate()方法中增加业务逻辑.
      由于ComponentSystemGroup从功能上来说它自己就是一个组件系统, 在其OnUpdate()方法内部增加逻辑貌似是美好的, 执行一些业务逻辑,生成一些Job, 等等。通常来说,我们建议不要这样做,因为,这些定制逻辑相对于该系统子成员的更新顺序,是相对提前的还是相对落后的,对于该系统外部来说并不是十分清晰。系统组的功能最好还是只用作处理分组,而在单独的组件系统中实现你所想要实现的逻辑,使得该逻辑的执行相对系统组有明确的先后顺序。
    展开全文
  • 流水线 1.应用阶段:(CPU)输出渲染图元,粗粒度剔除等 比如完全不在相机范围内的需要剔除,文件系统的粒子系统实现就用到粗粒度剔除。 2.几何阶段:(GPU)把顶点坐标转换到屏幕空间,包含了模型空间 到世界空间 到...

    流水线
    1.应用阶段:(CPU)输出渲染图元,粗粒度剔除等 比如完全不在相机范围内的需要剔除,文件系统的粒子系统实现就用到粗粒度剔除。
    2.几何阶段:(GPU)把顶点坐标转换到屏幕空间,包含了模型空间 到世界空间 到观察空间(相机视角view) 到齐次裁剪空间(投影project空间,四维矩阵,通过-w<x<w判断是否在裁剪空间,xyz值都在-w,w之间)
    到归一化设备坐标NDC(四维矩阵通过齐次除法,齐次坐标的xyz除以w实现归一化(x/w,y/w,z/w,w/w),xy坐标到在-1,1上,z坐标一般是0,1) 到屏幕空间(通过屏幕宽高和归一化坐标计算)。
    a.顶点着色器:坐标变换和逐顶点光照,将顶点空间转换到齐次裁剪空间。
    b.曲面细分着色器:可选
    c.几何着色器:可选
    d.裁剪:通过齐次裁剪坐标的-w<x<w判断不在视野范围内的部分或者全部裁剪,归一化。
    e.屏幕映射:把NDC坐标转换为屏幕坐标
    3.光栅化阶段:(GPU)把几何阶段传来的数据来产生屏幕上的像素,计算每个图元覆盖了哪些像素,计算他们的颜色、
    a.三角形设置:计算网格的三角形表达式
    b.三角形遍历:检查每个像素是否被网格覆盖,被覆盖就生成一个片元。
    c.片元着色器:对片元进行渲染操作
    d.逐片元操作:模板测试,深度测试 混合等
    e.屏幕图像


    矩阵:
    MA=AM的转置(M是矩阵,A是向量,该公式不适合矩阵与矩阵)

    坐标转换:
    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);顶点位置模型空间到齐次空间
    o.worldNormal = mul((float3x3)_Object2World,v.normal);//在正交变化下可以得到近似模型空间转换到世界空间的法向量,但是非正交情况会得到错误的情况。也不能只是逆矩阵(如果用_World2Object 会出错)
    o.ViewNormal = mul(v.normal, (float3x3)UNITY_MATRIX_IT_MV);//公式上的正确转换方式,法线模型空间到view空间,用该矩阵的逆转置矩阵去转换。但是Unity里边也许考虑了这个 可能不需要再做一遍。
    //但是这里要注意下在Unity里边也许 o.view_pos_nor.xyz = mul(UNITY_MATRIX_MV, float4(v.normal,0)).xyz;是正确的,有可能Unity的这个矩阵帮我们考虑法线问题。‘

    normal或者顶点转换到View空间参与计算,记得考虑坐标系变化。可以i.view_pos_nor.z=-i.view_pos_nor.z;把z反向,因为Unity物体是左手坐标系,camera space是右手坐标系。
    当然也可以直接在变换矩阵里边考虑好 坐标系的变化。Camera.worldToCameraMatrix 是右手坐标系计算,需要反转z,transform.worldToLocalMatrix和unity一样的左手坐标系计算。
    在Opengl中,本地坐标系和世界坐标系都是右手坐标系,直到投影空间中才变换为左手。而unity中的本地坐标系和世界坐标系是左手系统,但是View空间是右手坐标。


    API:
    UNITY_MATRIX_MVP 将顶点方向矢量从模型空间变换到裁剪空间
    UNITY_MATRIX_MV 将顶点方向矢量从模型空间变换到观察空间
    UNITY_MATRIX_V 将顶点方向矢量从世界空间变换到观察空间
    UNITY_MATRIX_P 将顶点方向矢量从观察空间变换到裁剪空间
    UNITY_MATRIX_VP 将顶点方向矢量从世界空间变换到裁剪空间
    UNITY_MATRIX_T_MV UNITY_MATRIX_MV的转置矩阵
    UNITY_MATRIX_IT_MV UNITY_MATRIX_MV的逆转置矩阵,用于将法线从模型空间转换到观察空间
    _Object2World 将顶点方向矢量从模型空间变换到世界空间,矩阵。
    _World2Object 将顶点方向矢量从世界空间变换到模型空间,矩阵。
    模型空间到世界空间的矩阵简称M矩阵,世界空间到View空间的矩阵简称V矩阵,View到Project空间的矩阵简称P矩阵。

    这些矩阵需要先声明了才能用


    _WorldSpaceCameraPos 该摄像机在世界空间中的坐标
    _ProjectionParams x = 1,如果投影翻转则x = -1 y是camera近裁剪平面 z是camera远裁剪平面 w是1/远裁剪平面
    _ScreenParams x = 屏幕宽度,y = 屏幕高度,z = 1 + 1.0/屏幕宽度, w = 1 + 1.0/height屏幕高度(指像素数)
    _ZBufferParams x = 1-far/near y = far/near z = x/far w = y/far
    unity_OrthoParams
    unity_Cameraprojection
    unity_CameraInvProjection
    unity_CameraWorldClipPlanes[6] 摄像机在世界坐标下的6个裁剪面,分别是左右上下近远、


    1.表面着色器
    void surf (Input IN, inout SurfaceOutput o) {}表面着色器,unity特殊封装的着色器
    Input IN:可以引用外部定义输入参数
    inout SurfaceOutput o:输出参数
    struct SurfaceOutput//普通光照
    {
    half3 Albedo;//纹理,反射率,是漫反射的颜色值
    half3 Normal;//法线坐标
    half3 Emission;//自发光颜色
    half Specular;//高光,镜面反射系数
    half Gloss;//光泽度
    half Alpha;//alpha通道
    }
    基于物理的光照模型:金属工作流SurfaceOutputStandard 高光工作流SurfaceOutputStandardSpecular
    half3,half4代表rgba或者xyz,可以分开用 Albedo.xy=1.或Albedo.ga=1


    #pragma surface surfname lightModel op - 指出函数surfname 表面着色器。lightModel的光照模型和可选op操作,还可以添加顶点修改函数vertex和颜色修改函数finalcolor。lightModel的输入结构有定义的固定几种
    #pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
    #pragma vertex name - 指出函数name 是顶点着色器。
    #pragma fragment name - 指出函数name 是片段着色器。
    #pragma fragmentoption option - 添加option 到编辑的OpenGL片段程序。参看ARB fragment program说明书了解被允许的选项列表。这个指示在顶点程序或者编辑到非OpenGL targets的程序没有影响。
    #pragma multi_compile_builtin - 为了pixel-lit shaders;;这个将告知Unity去编辑大量的这个着色器程序数列以支持所有的照明种类,和所有的阴影选项。
    #pragma multi_compile_builtin_noshadows - 对于pixel-lit 着色器,不接受阴影。这将告知Unity去编辑几个该着色器程序的数列来支持所有的照明种类。这比multi_compile_builtin pragma可以更快的编辑,而且结果着色器也更小。
    #pragma target name - 那个着色器target 去编辑。细节参看shader targets。
    #pragma only_renderers space separated names - 只为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。
    #pragma exclude_renderers space separated names - 不为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。

    #pragma multi_compile TEST1 TEST2 告诉Unity编译两个不同的
    shader版本 分别定义了宏定义TEST1和TEST2.在C#中用
    material.shaderKeywords = [“TEST1”]或者Shader.EnableKeyword
    (“TEST1”)去区分调用不同宏的内容;;


    2.顶点着色器
    struct appdata_full {//vertex输入
    float4 vertex : POSITION;//must
    float4 tangent : TANGENT;
    float3 normal : NORMAL;
    float4 texcoord : TEXCOORD0;//该顶点的纹理坐标,第一组纹理坐标uv 也就是第一张贴图的坐标、为了实现多重纹理贴图,比如子弹打在墙上的弹痕等
    float4 texcoord1 : TEXCOORD1;//n的数量和shadermodel版本有关, Mesh.uv2
    float4 texcoord2 : TEXCOORD2;//Mesh.uv3 可以通过外部设置该uv坐标
    float4 texcoord3 : TEXCOORD3;
    fixed4 color : COLOR;//顶点颜色
    };@上次买的

    3.片段着色器
    struct v2f{//vertec的输出和frag的输入
    float4 vertex :SV_POSITION;//must
    float3 color0:COLOR0;
    float3 color1:COLOR1;
    float4:texcoord:TEXCOORD0;//TEXCOORD0-TEXCOORD7自定义纹理坐标
    }
    SV_Tatget //frag的输出,输出float4的颜色 也可以是COLOR0各个平台会自动转换标志


    光照:
    1.逐顶点光照:在顶点着色器阶段计算光照,效率高但是效果不好,在边缘像素映射的时候插值可能会产生锯齿。
    2.逐像素光照:在片元着色器阶段计算光照,计算量大,但是边缘表现效果好。
    3.半兰伯特模型:处理无光照的地方,也让其有光,不然可能是全黑。经验模型。
    4.Blinn-Phong模型:高光反射模型的经验模型,高光部分看起来会更大更亮写。更符合实际些。

    #include “Lighting.cginc”
    Tags { “LightMode”=“ForwardBase” }

    WorldSpaceViewDir(float4 v) 输入模型空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
    UnityWorldSpaceViewDir(float4 v) 输入世界空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
    ObjSpaceViewDir(float4 v) 输入模型空间中的顶点坐标,返回模型空间中从该点到摄像机的观察方向
    WorldSpaceLightDir() 仅用于前向渲染,输入模型空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
    UnityWorldSpaceLightDir() 仅用于前向渲染,输入世界空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
    ObjSpaceLightDir() 仅用于前向渲染,输入模型空间中的顶点坐标,返回模型空间中从该点到光源光照方向,没有归一化。
    UnityObjectToWorldNormal(float3 v)把法线从模型空间转换到世界空间
    UnityObjectToWorldDir(float3 v)把方向矢量从模型空间转换到世界空间
    UnityWorldToObjectDir(float3 v)把方向矢量从世界空间转换到模型空间
    _WorldSpaceLightPos0.xyz 获取平行光光源方向,或者点光源的光源位置
    _LightColor0.rgb 获取当前pass的光源颜色和强度
    UNITY_LIGHTMODEL_AMBIENT.xyz; 环境光
    normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); 世界空间下的view视觉方向 UnityWorldSpaceViewDir

    a.漫反射公式:diff=Cmax(0,dot<L,N>);//C是颜色和强度_LightColor0.rgb
    代码: diff=max(0,dot(i.normal,i.lightDir))//i的单位向量and单位法向量
    c=tex2D(tex,i.uv)
    _LightColor0*diff//_LightColor0表示的是场景中平行光的颜色和强度

    b.高光反射公式:Spec=pow(max(0,dot(R,V),gloss))//R 单位反射向量reflect(ray,normal)函数获取,V视线单位方向向量 ,gloss光色度
    代码: Spec=pow(max(0,dot(reflect(-i.lightDir,i.normal),viewdir),32))
    c=c**_LightColor0*(diff+Spec)

    颜色相加和相乘的物理意义,一般的各种光源都是颜色叠加,相乘会变淡。

    在PS中 tex2D 自动计算应该使用的纹理层。
    tex2Dbias需要在t.w中指定一个偏移量来把自动计算出的纹理层全部偏移指定的值。
    tex2Dgrad需要提供屏幕坐标x和y方向上的梯度来确定应该使用的纹理层。
    tex2Dlod需要在t.w中明确指定要使用的纹理层。
    texCUBElod(_Cube,float4(dir.xyz,lod))方向和Lod系数


    纹理 uv坐标是顶点存储了图片上的坐标
    _MainTex (“Main Tex”, 2D) = “white” {}
    sampler2D _MainTex;
    float4 _MainTex_ST;//Unity中 纹理_ST来默认声明该纹理的属性_MainTex_ST.xy表示Scale, Till缩放,_MainTex_ST.zw表示Transform 偏移
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//vs输入纹理坐标和纹理值输出UV,ps对uv进行纹理采样和计算。UV通常在0-1范围,等于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;//反射率

    法线贴图:xyz映射存成rgb值。一般存在切线空间,z轴法线方向,x轴切线方向,y轴副(法)切线方向,我们ds的法线贴图xy存法线z存粗糙度。
    TANGENT_SPACE_ROTATION;//Unity来获取rotation矩阵,从模型空间到切线空间变换的矩阵。仅存在旋转和平移时,一个矩阵的转置矩阵等于他的逆矩阵。
    自己实现:
    float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; //切线空间的w分量用来存储负法线向内还是向外
    float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);//float3x3是按行存储

    float3 tangentNormal = UnpackNormal(packedNormal);Unity将法线贴图纹理坐标0,1映射到正常法线坐标-1,1,返回切线空间下的法线方向。法线贴图要设置成Normal格式。该设置unity有优化 rgb值不再是法线xyz的映射了,如果不设置的话要自己算 该公式不能用。
    自己实现:
    tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;//坐标反映射,自己计算的方法 因为packedNormal.z分量一般被压缩去掉,比如我们ds的z分量存储的是粗糙度,xy存法线。
    tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));//通过xy计算z 法线贴图一般会采用压缩的方式存储 那么只有xy分量是正确的,z分量需要经过xy计算出来。
    如果法线贴图是蓝色:代表存在切线空间,而且xyz可能没有被压缩。如果显示了其他颜色比如ds的绿色,就代表z通道被用做其他用途了。
    虽然在切线空间下计算光照更省,但为同一建议一般在世界空间下计算。

    遮罩纹理


    1.透明度测试AlphaTest:只要有一个片元的透明度不、、满足条件就被裁剪,用来优化显示。
    AlphaTest Greater AlphaValue//仅渲染 alpha 值大于 AlphaValue 的像素。AlphaValue :0-1
    AlphaTest GEqual AlphaValue //仅渲染 alpha 值大于或等于 AlphaValue 的像素。
    AlphaTest Less AlphaValue//仅渲染 alpha 值小于 AlphaValue 的像素。
    AlphaTest LEqual AlphaValue //仅渲染 alpha 值小于或等于 AlphaValue 的像素。
    AlphaTest Equal AlphaValue //仅渲染 alpha 值等于 AlphaValue 的像素。
    AlphaTest NotEqual AlphaValue //仅渲染 alpha 值不等于 AlphaValue 的像素。
    AlphaTest Always //渲染所有像素。这在功能上相当于 Alpha 测试关 (AlphaTest Off)。
    AlphaTest Never //不渲染任何像素。
    第一步透明度测试不通过的放弃该片元
    2.模板测试:Stencil如果开启了模板测试,GPU会首先会读取模板缓冲区的值,然后把读取的参考值ref和该缓存值进行比较,比较方式由Comp指定,比如大于Greater就表示通过模板测试,
    然后由Pass Fail ZFail去指定通过和不通过模板和深度测试后对缓冲区的值进行的Operation处理。
    Stencil
    { Ref 2 //设置模板参考值为2
    Comp equal //比较方式,有8种比较方式。 Greater指ref值大于缓存值。
    Pass Operation //这个是当stencil测试和深度测试都通过的时候,进行的stencilOperation操作方法
    Fail Operation //这个是在模版测试和深度测试都失败时执行的stencilOperation方法
    ZFail Operation //这个是在stencil测试通过,但是深度测试没有通过的时候执行的stencilOperation方法。
    ReadMask readMask //readMask默认是255,ds按位进行对比的时候常用,设置隐码后 读取ref和buff值都需要与该码进行与操作后再比较Comp。(0-255)
    WriteMask writeMask//写操作进行与操作 ,设置该掩码后先进行按位与操作后再写入
    }
    Comp 的参数包括Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never
    Operation的参数包括:
    Keep 保持
    Zero 归零
    Replace 拿比较的参考值替代原来buffer的值
    IncrSat 值增加1,但是不溢出,如果是255,就不再加
    DecrSat 值减少1,不溢出,到0就不再减
    Invert 翻转所有的位,所以1会变成254
    IncrWrap 值增加1,会溢出,所以255会变成0
    DecrWrap 值减少1,会溢出,所以0会变成255

    第二步模板测试不通过的放弃该片元

    clip(x) //x的任何分量小于0 被裁剪掉
    discard//舍弃当前片元
    ZWrite Off//关闭深入写入 默认开启
    ColorMask RGB|A|0 //设置颜色通道的写掩码,为0表示该pass不进行颜色输出。
    3.深度测试ZTEST:一个片元离摄像机的远近,渲染后会进行深度写入,通常会判断缓存深度和当前片元深度 可知前后关系。
    ZTest Always //指的是直接将当前像素颜色(不是深度)写进颜色缓冲区中 相当于ZTest Off
    ZTest Never //而Never指的是不要将当前像素颜色写进颜色缓冲区中,相当于消失。
    ZTest Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never/Off,默认值为LEqual 即当物体深度小于或等于缓存深度值时(越远深度越大),该物体渲染,就是默认的先后顺序渲染。
    第三步深度测试不通过的放弃该片元

    透明度混合AlphaBlending:该片元需要关闭深度写入,不关闭深度测试。会导致片元之间深度穿插。可以采用2个pass,第一个pass只用来做深度写入ZWrite On,第二个pass只用来输出颜色ZWrite Off,这样深度和颜色效果才会正确
    Blend Off//关闭混合,只有blend打开后ps输出a通道才有意义
    Blend SrcFactor DstFactor//用同样的因子对rgba进行混合(默认都开启混合)第一个参数对源颜色(当前片元颜色,ps输出的颜色)*SrcFactor混合,
    第二个参数对目标颜色(当前读到的缓冲区颜色)*DstFactor混合,混合后默认相加后会重新写入缓冲区(相加后超过1的自动截断到1)。混合包括RABG值。结果都是源颜色和目标颜色与各自因子相乘后再加起来作为输出颜色。
    shader里边的向量相乘不同于点乘叉乘,相当于各项分别相乘。

    Blend SrcFactor DstFactor,SrcFactorA DstFactorA//把rgb和a的混合因子分开。
    混合因子
    One //因子是1
    Zero //因子是0
    SrcColor //因子为源颜色值,当前片元颜色,对应rgba分量分别与SrcColor分量相乘
    SrcCAlpha //因子为源颜色透明值值,对应rgba分别与SrcCAlpha相乘。
    DstColor //因子为目标颜色值,当前读到的缓冲区颜色
    DstAlpha //因子为目标颜色透明值值
    OneMinusSrcColor//因子为1-源颜色
    OneMinusSrcAlpha//因子为1-源alpha
    OneMinusDstColor//因子为1-目标颜色
    OneMinusDstAlpha//因子为1-目标alpha
    例子:
    Blend SrcAlpha OneMinusSrcAlpha// Alpha混合,正常的透明度混合
    Blend OneMinusDstColor One //柔和相加Soft Additive
    Blend One One // Additive相加 线性减淡
    Blend One OneMinusDstColor // Soft Additive比较柔和的相加
    Blend DstColor Zero // Multiplicative乘法
    Blend DstColor SrcColor // 2x Multiplicative2倍乘法

    BlendOp OP//对源和目标颜色进行其他操作,而不是默认的相加,op操作包括:
    Add //相加
    Sub //源颜色减目标颜色
    RevSub//目标颜色减源颜色
    Min //使用2者较小的值
    Min //使用2者较大的值
    chen
    BlendOp Min
    Blend One One //组合变暗

    双面渲染:一般采用多个pass分别渲染正面和背面
    Cull Back|Front|Off
    Cull Back默认的背对相机图元不渲染
    Cull Front朝向相机图元不渲染,只显示背面
    Cull Off关闭剔除功能 全部渲染 性能低,但是可以实现比如看见物体内部结构。
    不透明物体有深度测试,先渲前后没有关系,但是先渲染近的效率会更高,因为远的会被深度测试自动剔除不用渲染。没办法保证远近?
    透明物体一般要先渲远的,再渲近的才能保证视觉顺序正确。


    SubShader的Tag{}标签类型:
    Queue:渲染顺序,保证渲染顺序小的先渲 大的后渲,在quene一样大的时候无法保证渲染顺序。同一批renderQueue值的材质一起渲,渲完后统一进行深度填入,第二批再取新值进行渲染。
    RenderType:Unity可以运行时替换符合特定RenderType的所有Shader,着色器分类 也可以自定义。
    ForceNoShadowCasting:值为”true”时,表示不接受阴影。
    IgnoreProjector:值为”true”时,表示不接受Projector组件的投影。常用语半透明物体
    DisableBatching:是否对subshader进行批处理,当shader中需要对顶点进行偏移的时候,该项设置为true
    CanUseSpriteAtlas:当该subshader用于sprite时 该标签设为false
    PreviewType:指明材质面包怎么预览材质 比如 “PreviewType”=“Plane”
    LightMode : 渲染路径 ,pass的标签类型


    渲染队列:“Queue”=“Transparent”
    Background:1000 //该声明的物体最先被渲染
    Geometry:2000 //默认的不透明物体使用的渲染队列
    AlphaTest:2450 //透明度测试,默认不透明物体渲染完后就渲染该物体
    Transparent:3000//透明物体,在Geometry和AlphaTest后渲染,保证不透明物体渲染完了再渲染透明的。
    Overlay:4000 //该队列用来实现叠加效果,该物体会在最后被渲染。


    RenderType:
    Opaque:绝大部分不透明的物体都使用这个;
    Transparent:绝大部分透明的物体、包括粒子特效都使用这个;
    Background:天空盒都使用这个;
    Overlay:GUI、镜头光晕都使用这个


    渲染路径
    Tag{ “LightMode” = “ForwardBase”}//为每个pass指定渲染路径
    LightMode包括:
    Always:所有渲染路径该pass都会渲染,但不计算光照
    ForwardBase:前向渲染,该pass会计算环境光,最重要的平行光,逐顶点光和 Lightmaps
    ForwardAdd:前向渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源。光源多该pass会被多次调用 效率变低。
    Deferred:延时渲染,该Pass会渲染G-buffer
    ShadowCaster:把物体的深度信息渲染到阴影映射纹理或深度纹理中
    PrepassBase:遗留的延迟渲染,该pass会渲染法线和高光反射的指数部分、
    PrepassFinal:遗留的延迟渲染,该pass通过合并纹理 光照 自发光来渲染得到最后的颜色
    Vertex:遗留的顶点照明渲染

    1.前向渲染:包括ForwardBase类型渲染常用光照和ForwardAdd额外光照
    #pragma multicompile_fwdbase //ForwardBase中用来保证光照衰减等参数正确赋值。
    #pragma multicompile_fwdadd //ForwardAdd中用来保证可以访问到正确的光照变量.
    #pragma multicompile_fwdadd_fullshadows //ForwardAdd中用来计算阴影效果
    USING_DIRECTIONAL_LIGHT//平行光的宏定义
    _LightColor0 //该pass的桌像素光照颜色
    _WorldSpaceLightPos0 //获取平行光光源方向,或者点光源的光源位置
    _LightMatrix0 //世界空间到光源空间(光源位置为坐标原点的坐标系)的变换矩阵
    _LightTexture0 //光照衰减纹理

    tips:光源的RendeMode参数设置为Important unity会自动采用像素光源,如果不重要就是顶点光源。还有qualitysetting里边的PixelLIghtCount,超过这个数也会用顶点光照
    光照衰减
    float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;//UNITY_ATTEN_CHANNEL获得衰减值所在的分量
    float shadow=SHADOW_ATTENUATION(i);//负值使用SHADOW_COORDS对相关纹理进行采样,返回值为阴影。关闭阴影的状态是等于1
    return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);//atten为衰减,shadow为阴影
    ------计算别人投到自己身上的阴影和衰减
    SHADOW_COORDS(n)//声明一个_ShadowCoord的阴影纹理坐标 ps输入坐标,n的值是声明TEXCOORD0-7坐标使用的个数
    TRANSFER_SHADOW(o);//用于在顶点着色器中计算上一步声明中的阴影纹理坐标 并传向ps阶段。
    float shadow=SHADOW_ATTENUATION(i);//负值使用SHADOW_COORDS对相关纹理进行采样,返回值为阴影。关闭阴影的状态是等于1
    UNITY_LIGHT_ATTENUATION(atten,v2f i, i.worldPos);//计算别人投影到身上的阴影#include “AutoLight.cginc” Unity会将光照衰减和阴影相乘后存在第一个参数中,并自动声明atten变量。第二个参数结构体包含SHADOW_COORDS,第三个参数世界空间坐标
    return fixed4((diffuse + specular) * atten, 1.0);//UNITY_LIGHT_ATTENUATION出的atten为衰减和阴影
    -----计算阴影投影到别人身上,自己的阴影
    V2F_SHADOW_CASTER//unity里边定义阴影投射需要定义的变量
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(0)//unity对顶点进行自动处理
    SHADOW_CASTER_FRAGMENT(i)//unity自动完成阴影投射部分,把结果输出到深度图和阴影映射纹理中
    –ds2的阴影采用的是屏幕后处理的方式去计算阴影,延迟渲染
    2.顶点照明渲染:过时的渲染方式。效果差。

    3.延迟渲染:通常2个pass,第一个pass计算哪些片元可见,第二个pass计算真实光照。


    1.反射方向:float3 reflect = reflect(o.lightDir, o.worldNormal);//入射光线,表面法线
    2.折射: float3 refract = refract(normalize(o.lightDir), normalize(o.worldNormal), _RefractRatio);//入射光线单位向量,表面法线单位向量,介质比

    3.镜子效果:使用相机的RenderTexture来设置渲染纹理。o.uv.x = 1 - o.uv.x;坐标需要翻转一下。
    4.玻璃效果:反射和折射使用cubemap进行采样 是天空盒的cubemap,然后反射需要采样的是周围环境的光照和纹理。
    GrabPass { “_RefractionTex” }//会把屏幕输出到_RefractionTex的texture中, _RefractionTex_TexelSize ,要先声明该变量 就可以可以得到该纹理纹素大小,例如255*255的纹素是(1/255,1/255) 值是: Vector4(1 / width, 1 / height, width, height);
    GrabPass{} //然后用_GrabTexture直接访问屏幕图像,但是这样效率比较低,推荐要上面需要声明的方法。
    o.scrPos = ComputeGrabScreenPos(o.pos);//得到对应被抓取的屏幕图像的采样坐标
    反射和折射需要显示环境的效果,所以需要对环境的cubemap进行采样。先用反射和折射的公式计算出光线,然后对环境贴图进行采样texCUBE(_Cubemap, i.worldRefl).rgb就可以得到具体效果了。
    反射skybox 3d采样,折射屏幕抓取图像2d采样。


    时间变量
    _Time:float4 //t是自该场景加载开始所经过的时间,4个分量是(t/20,t,2t,3t)
    _SinTime:float4 //t是时间的正玄弦值,四个分量的值分别是(t/8,t/4,t/2,t)
    _CosTime:float4 //t是时间的余玄弦值,四个分量的值分别是(t/8,t/4,t/2,t)
    unity_DeltaTime:float4// dt是时间增量,4个分量分别是(dt,1/dt,smoothDt,1/smoothDt)
    序列帧动画:时间去控制uv坐标映射转换。uv坐标的xy是顶点坐标,映射到小格子里边,和UItexture的xy和宽高不一样。
    背景偏移动画:时间控制uv坐标偏移。
    水流动画:通过时间和正弦函数去控制顶点偏移,通过时间控制uv移动。设置DisableBatching=true
    广告牌BillBoarding:根据视觉方向来旋转被纹理着色的多边形。顶点动画


    屏幕后处理
    void OnRenderImage(RenderTexture src, RenderTexture dest){}//全部渲染完后将调用,屏幕纹理存在src上,用来计算后通过dest返回,添加[ImageEffectOpaque]属性,可使不透明物体(AlphaTest)被渲染完后立即调用.
    Graphics.Blit(src, dest);//直接copy纹理。src是屏幕当前或上一步渲染的纹理,dest是目标纹理
    Graphics.Blit(src, dest, material,pass=-1);//将把src传到shader的material的_MainTex纹理。经过material(shader)的处理后输出到dest渲染到屏幕.pass默认是-1会调用所有pass,否则只调用给定顺序的pass。指定pass渲染很重要。

    基于颜色变化的边缘检测:Sobel卷积算法,对边缘点进行采样计算 和特定矩阵卷积相乘。
    高斯模糊:多次采样纹理混合 消耗较大
    Bloom效果:把较亮区域提取出来进行高斯模糊 模拟扩散效果,然后再与原纹理混合。
    运动模糊:将上一帧的屏幕图像存到renderTexture中,然后执行Graphics.Blit(src, renderTexture, material),shader将开启混合Blend SrcAlpha OneMinusSrcAlpha把src纹理和目标缓冲纹理renderTexture进行混合,然后再Blit输出到dst进行渲染。就得到了运动模糊效果。


    深度和法线纹理
    float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
    _CameraDepthNormalsTexture //unity中调用camera.depthTextureMode=DepthTextureMode.Depth/DepthNormal;这句话后可以通过该变量访问深度纹理或者深度和法线纹理,project空间
    float _depth=SAMPLE_DEPTH_TEXTURE(tex,uv)//对深度纹理进行采样,返回project空间下非线性深度值。和tex2D类似 只是封装了平台。自动将NDC坐标下的深度映射(0,1)需要转换到(-1,1)veiw空间去计算
    _depth 是非线性深度值
    LinearEyeDepth(_depth)负责把深度纹理的采样结果转换到视角view空间下的线性深度值 转换到nearclip到farclip范围
    Linear01Depth(_depth)则会返回一个范围在0,1的线性深度值,转换到0-1的范围。

    half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv);
    half2 centerNormal = DecodeViewNormalStereo(sample1);//center.xy存的法线的映射值,EncodeViewNormalStereo
    float centerDepth = DecodeFloatRG(center.zw);//zw深度,EncodeFloatRG
    DecodeDepthNormal(sample1,out centerNormal,out centerDepth)//获取采样的法线和深度

    Camera.worldToCameraMatrix //世界转相机矩阵 world2view
    Camera.cameraToWorldMatrix //相机转世界矩阵
    Camera.projectionMatrix //get投影矩阵viewToproject 视角空间到2维投影空间矩阵,set自定义的投影矩阵。如果你改变这个矩阵,相机的渲染不再基于它的fieldOfView更新,直到调用ResetProjectionMatrix
    默认把view2project矩阵叫成project矩阵,默认把World2view矩阵叫做view矩阵。比如ViewProject就是world 2 project矩阵
    全局雾效
    深度雾效:通过每个顶点的深度值计算出该点到摄像机的距离d,然后把距离d进行参与公式计算得到雾效图(远的雾浓 rgb值大,近的雾淡 rgb值小),再把原图和雾效图进行混合。一般用线性,指数,指数平方公式,ds采用指数平方。
    地面雾效:通过深度值和摄像机的方向向量计算该点到摄像机的偏移量,再加上摄像机的位置得到该顶点在世界空间中的坐标,然后把该坐标的y值参与雾效计算。如果用坐标z参与计算和深度雾类似。
    #pragma multi_compile_fog

    基于法线的边缘检测:防止阴影等信息干扰检测,判断临近的4个点的法线和深度值是否是近似,如果差距过大则是边缘roberts算法。(屏幕后处理)


    渲染轮廓线:基于法线方向扩散的边缘检测,第一个pass对顶点进行法线方向扩散渲染,第二个pass用真实渲染实际光照,覆盖第一次,对扩散的顶点未被覆盖的像素就产生了轮廓效果。(模型轮廓)
    主角遮挡轮廓线渲染:第一个pass 渲染Ztest greater不写深度,第二个pass ztest less正常渲染颜色。这里要注意:要想主角材质之间不渲染相互遮挡,所有RenderQueue必须设成相同,才能保证在同一批里边渲
    染 深度值是一样的,如果先后渲染顺序不同,深度值已经被写入过,就会出现内部互相遮挡渲染。


    噪声
    消融效果:怪物消失渐散的效果,把某个像素值小于阈值的裁剪掉,阈值附近的值用burncolor进行混合。阴影的pass里边算阴影时也把该项给clip掉,这样阴影就动态变化了//clip(burn.r - _BurnAmount);
    水面扰动效果:用时间去控制偏移距离,然后对该顶点的uv偏移两点的法线平均值来代替该点的法线值。水面=反射+折射+绕动,波浪效果要用正弦函数偏移顶点、
    ds水内的扰动UV=uv+0.03*offset, offset通过时间和噪声图计算,这样会产生顶点和旁边点的扰动0.03倍距离左右。
    float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
    / Get the normal in tangent space
    fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
    fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
    fixed3 bump = normalize(bump1 + bump2);
    全局(动态)雾效:通过时间控制噪声纹理的偏移距离,然后根据噪声颜色值来参与计算雾效浓度,然后计算雾效,就有了流动和淡浓的效果。

    线性空间:颜色的正常编码比如0,0.5,1编码到0,0.5,1.
    伽马空间:颜色编码存储在伽马空间,对颜色值一般进行Power(col,0.45)计算,比如0,0.22,1编码到0,0.5,1.把更多的颜色值用来存储眼睛容易辨别的较暗局域。该转换会使颜色看起来变亮了、我们的大多数图片颜色值存储在该空间。
    伽马矫正:指将线性空间衍射到伽马空间。2.2*0.45=1
    CRT显示伽马:由于历史和巧合,屏幕对颜色进行输出的时候会进行显示伽马操作,计算Power(col,2.2),所以输出会比原图变暗。power有很多好处,比如可以使线性硬边缘软化,看起来更柔和。
    Unity ColorSpace Setting:不支持移动平台,所以我们一般都选择Gamma Space
    Gamma Space:实际上就是“放任模式”,不会对shader的输入进行任何处理,即使输入可能是非线性的;也不会对输出像素进行任何处理,这意味着输出的像素会经过显示器的display gamma转换后得到非预期的亮度,通常表现为整个场景会比较昏暗。
    Linear Space:Unity会背地里把输入纹理设置为sRGB模式,这种模式下硬件在对纹理进行采样时会自动将其转换到线性空间中.也会设置一个sRGB格式的buffer,此时GPU会在shader写入color buffer前自动进行伽马校正再次转换到伽马空间。
    流程Gamma Space:原光照-伽马编码(图片存储的格式编码,变亮)-Unity不处理-CRT显示伽马(变暗)-输出原光照颜色(相比编码后的原图会变暗)
    流程Linear Space:Unity光照-Unity自动转换线性空间计算 然后再伽马矫正转换到伽马空间存储在缓冲中-CRT显示伽马-输出颜色正确,如果用gamma的话 计算完光照不会进行伽马矫正 所以经过CRT会变暗。linear的线性转换指图片采样 ,光照计算两者一样。
    ds2:图片格式在伽马空间,选择ColorSpace为Gamma,进行颜色采样时先进行float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 ); 伽马矫正,在线性空间计算光照屏幕后处理,最后在matcopy输出的时候再return fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);还原回去。最后经过CRT的伽马矫正输出。

    HDR高动态范围渲染:正常光照图像范围是0-1,但是实际光照可能超过1 ,HDR使用16位图进行渲染 使颜色值包括在更大的范围内计算 最后再通过Tonemapping衍射到0-1范围 ,使颜色更加真实。
    当camera上的hdr被激活时,OnRenderImage的source图会存储成16位的ARGBHalf格式,然后计算Bloom和Tonemapping生成的采样图也可以定义成16位格式,那么效果会更好。HDR对美术和硬件性能要求高,所以手游一般很少用,难达到那么好的效果。
    Tonemapping 色调映射:HDR使效果看起来更柔和,防止过亮和过暗, tonemapping根据当前的场景推算出场景的平均亮度,再根据这个平均亮度选取一个合适的亮度域,再将整个场景映射到这个亮度域得到正确的结果。用该公式把采用的hdr颜色转换到ldr范围值内。
    如果没有HDR也能简单的柔和。只有在相机启用HDR时才更有意义。这个特效需要显卡拥有像素着色器(2.0)或者OpenGL ES 2.0,但是我们采用的是简单的伽马参数控制的色调映射。同时在该算法里边计算亮度,饱和度和锐化修改。简单的tonemapping算法有很多种实现方式。
    算法千差万别,但是目的是一样的,就是通过公式,把输入值HDR颜色或正常颜色映射到0-1范围内去显示,可以画出公式曲线理解都在0-1范围内,并且根据曝光系数调整曲线 使大部分颜色输出到看起来更加接近该亮度的区域。
    Bloom :可通过HDR实现效果更佳,bloom本身可通过模糊实现,开启HDR后高斯采样图我们可以使用RenderTextureFormat.ARGBHalf格式的纹理 采用了更高精度的纹理后,再通过算法效果会更好。
    数据点云模型的作用:有时候求比如高斯模糊的时候,可以用工具按规则生成周围随机的一些偏移点,数据点云的模型效果有时候比直接计算效果好,如果提前知道模型和计算方式的话。

    延迟渲染:所谓延迟渲染只是预先把所有灯光的颜色算好后,叠加到主光上参与光照计算,比如ds会把预先把点光源光照计算出来,通过在光源处Camera.RenderToCubemap(rt)渲染周围全景图,用该全景图去计算光照,全部点光源计算完了 再去叠加计算主光的光照。而不用像延迟渲染那样每个灯光要渲染计算所有场景光照。

    全局光照GI:计算的是光的二次反射,有两种算法,漫反射为主:一般从该点法线方向按一定规则随机散射几条射线出去一定距离,只要计算到该射线点和该采样点相交的(该种射线方向可只采样一次对比),就把交点算入该点的颜色影响范围内,把几条射线的交点颜色按照距离衰减叠加到源颜色 就可以了。
    另一种是高光为主(ds)方法,从视线的反射方向方向射出几条射线,射出去计算交点和SSR一样,分8段计算深度对比,每段都有一定随机偏移,计算交点后出uv采样叠加到源颜色上。如果所有射线采样点深度都比实际采样点深度大,说明该方向没有对该点颜色有贡献。

    环境遮蔽AO:传统的AO实现方法通常是使用光线跟踪的来完成。通过从每个可视点发出若干条光线 ,并与全局的场景进行相交测试来得到相应点的AO值。用于计算某点除了光源以为被其他的物体遮挡导致的软阴影。所以开启会变暗。
    屏幕空间环境光遮蔽SSAO:屏幕后处理实现,根据点云模型(或者random采样),取周围规则随机点进行深度采样,然后将该点深度和周围采样点的深度进行对比,正常模型是对周围法线求半球积分 半球积分需要更多的采样次数效果才会比球积分好,为效率我们求得是对视角方向整球积分,半球积分需要对cos积分后进行二次积分,不然会导致半球面的同一个角度的圆圈上的点算出来的值是一样的,结果不正确。
    我们简化为向量P(当前点到周围采样点uv偏移对应的viewpos的方向向量)与viewdir的点乘(cos是sin的积分,全球积分),该SSAO各个角度看到的效果强弱不一样,当该点为凸点 周围的采样深度大于该点,当该点为凹点时周围深度小于该点,点乘后大于0为凹,小于0为凸,根据深度差与ViewDir的点乘就知道周围点对该点的AO贡献值。由于我们blend one one,而且是整球积分点乘后的AO值-1,1,大于0表示凹处被遮挡,小于0表示凸出不遮挡,转换到0,1后,减去0.5可只显示遮挡处,但是会导致夹角处半圆覆盖面积不够采样失真,所以我们用原值,整体场景会变亮很多,再通过其他方式把暗度调下
    SSAOPro采用的是向量P(当前点到周围采样点uv偏移对应的wordpos的方向向量)与当前点normal的点乘的半球积分max(0.0, dot(norm, v))(1/(1+d)),d是该点到采样点的距离,该SSAO强弱与视角无关,采样次数少的时候全球积分效果好,采样很多或者模型精度高的话半球积分效果好,目前手机上只能用全球积分可以达到效果。SSAO优化一个采用UIMASK裁剪被UI遮挡的地方,另外一个用深度裁剪 远处的地方不进行SSAO可以提高效率。另外对SSAO计算后的图进行Blur后再叠加,效果会更好但是指令数会增加。
    SSAO消耗=blur指令数
    blur像素+SSAO指令数*SSAO像素数量,把shader编译到汇编指令,数一下有多少条指令 可做参考,只选单平台可方便看。SSAOPro140个指令非常高了,像我们SSAO才50个。
    常用优化方法:降低分辨率,减少采样数,UIMask,隔像素计算。
    高斯模糊Blur:高斯公式计算了周围所有点对该点的叠加的影响值,越远影响越小,采样范围越大效果越正确。我们一般取周围2圈的附近代表点进行模拟,因为实际方式去采样一圈8个点效率太低、
    取该点周围1个像素和2个像素距离的四个点,再乘以一个倍数,然后再将多张图按高斯规律叠加,也就是根据高斯算法离中心点越近的所占该点像素值比率越大,因为高斯公式是2维的,所以正常采用2个pass分别对水平竖直方向一维采样模拟降维,但是效果要求不高的时候我们也可以用一个pass取模拟近似效果,9个点:float weight[3] = {0.4026, 0.2442/2, 0.0545/2}保证所有采样点叠加在一起的权重和为1。如果不按高斯权重进行叠加颜色会得到其他类似模糊效果
    另外cpu阶段传过来采样的图最好先进行降分辨率(参数控制分辨率降低倍数)处理再采样提高效率。然后CPU还要多次采样混合效果才好,但效率低。

    Bloom:把较亮区域提取出来进行高斯模糊 模拟扩散效果,然后再与原纹理混合。bloom的高斯模糊需要用精确的二维采样效果才能好。
    运动模糊Blur:指的的是物体运动的拖影模糊,但赛车拖影模糊一般用环境径向模糊,物体运动模糊可计算上一帧和当前帧进行混合采样 和 相机运动模糊可求取相机运动方向在uv里边进行偏移后混合采样。

    Dof景深DepthOfFeild:指目标渲染附近的一定深度范围内显示清晰图片,其他范围呈现模糊效果。游戏中一般用一个Dis,深度大于Dis的点经模糊处理 越远越模糊 形成景深效果。ds的方式是用DrawMesh 第一个pass把深度大于Dis的点模糊渲染到tex中,第二个pass去把上帧绘制的tex直接加到currentColor中。其实我们也可以采用一个pass 把深度大于DIs的直接模糊返回,小于的返回原值就好了。
    自发光:一般是用做边缘发光,正常就是在shader内部自定义光照参数,比如UI上显示的模型不经过光照可以自定义光照。
    抗锯齿AA:FXAA快速近似抗锯齿是屏幕后处理常用方法,取uv当前顶点和左上,左下,右上,右下的半个uv像素偏移点,求取5个点的亮度值(dot(col,float3(0.22,0.707,0.071))或其他公式Luminance()),然后分别求出5个点的最小和最大亮度值,如果最大亮度与最小亮度值的差小于某个阀值(比如0.5或者计算)判定为非边缘点直接返回当前颜色,
    然后对uv加减半个像素偏移(*Dir)采样出col0,col1,再对uv加减2个像素偏移(*Dir)采样出col2,col3(其中像素偏移值要和两对角线亮度之差的差and和组成的向量通过公式计算单位向量Dir后相乘取近似),然后把前2个颜色合并(col0/2+col1/2)得ColA如果该值亮度不在之前计算的最小值与最大值之间返回该值,否则把四个颜色进行合并来返回、
    求像素偏移倍数Dir是FXAA关键,该向量相乘后得到的uv点可以更加趋向边缘,比如四个采样点,左边和右边色差大,乘以Dir后取样的左上右下点会趋向上下方向,使得边缘更加真实。抗锯齿采样其实就是轻微的模糊处理,只是采样范围小点,亮度变化不大的不模糊就好了。
    多重采样抗锯齿MSAA:只对画面中物体的边缘进行放大、混合的抗锯操作,因为边缘是锯齿最明显的地方(注意不是所有的边缘)。提取边缘,主要是结合深度技术。MSAA是种硬件AA。我们一般说的4x、8x,就是放大倍数,放得越大,供混合的采样越充份,效果越好,但是处理速度也就越慢
    草随风动:
    水的泡沫:
    边缘光RimLight:有两种方法实现,传统的算法是求视角方向和顶点法向量,如果两者垂直说明该顶点边缘光,加上边缘光颜色就好,该种效果所有轮廓线边缘都会渲出。ds的办法是屏幕后处理用法线贴图的xy(节省效率可不用decodenormal)对一张masktexture进行采样,采样后的颜色乘以边缘光颜色返回即可。
    原理在于mask贴图左下角为原点,向上y变大,向右x变大(0-1),对于模型顶点的法向量也是向上y为正 向下y为负,向右x变大(-1,1),其未经decode的法线贴图xy值(0-1)方向刚好对应了mask采样方向,那么对应maxk的上半部分赋一个凹圆颜色值,其他地方为0值,那么采样的叠加的结果就是所有模型的上边缘会有边缘光因为normal.y>0采样、
    ds里边还采用了stencil开关开材质是否开启边缘光。然后相机离主角位置远近来动态设置边缘光颜色倍数,越远设置越大看起来边缘光更多。

    橡胶皮肤材质SSS次表面散射:光线进入材质后进入材质内部经反射折射影响周围点的亮度呈现橡胶透明效果。ds的ssss优化过用模拟点来找到周围的点的采样的亮度值再来计算这些点对该点的亮度影响值,然后叠加,影响值一般与距离平方成反比,越近影响越大。

    体积光:有三种实现方式,第一种是对顶点光源反方向进行拉丝偏移,第二种是对亮度较大的顶点uv进行光源反方向模糊偏移径向模糊处理。第三种是光线追踪、
    径向模糊:可实现赛车的运动周围环境模糊和太阳光光线射出的效果,太阳光将亮度高的图分离出来降分辨率进行模糊然后和原图叠加。从屏幕中心点或者太阳位置向四周各自方向进行uv偏移实现太阳光的体积光。先求中心点到当前uv点的方向dir(包含距离和方向信息),最后对该点进行多次偏移采样叠加,每次偏移uvoffset=diri_Dis就可以实现该效果。为了优化效率可以把采样模糊的图分辨率降低,模糊后和原图混合。

    水下:绘制Quad,通过blend 混合,判断世界空间该点的高度是否小于水面高度,小于水面高度部分进行扰动效果加雾效处理,当摄像机高于水面不渲染,低于渲染,处于水面之间的和水进行lerp操作。
    更加逼真的水下效果需要绘制水面的反射水滴和折射天空,水中更加体现距离差的雾效和水底的焦散效果。还有最基本的扰动。
    水下渲染最生硬的地方是当相机处于水之间的时候,那么水面上下可能都只在屏幕内,水下侧可以计算玩家位置上方水的屏幕y坐标向下插值blend减弱水效果,但是水上侧和场景融合就比较生硬 只能做相机碰撞解决 逻辑保证相机只能在水下或者水上。

    下雨:用数张雨滴的法线贴图,轮流采样作为纹理的法线方向就有了下雨效果,法线贴图内含有数个雨滴各种效果。通过drawmesh绘制quad。雨周围的反射可以用cubemap。一般小场景的反射用cubemap采样,大场景只能再计算一次反射效果了。
    float2 rainuv=frac(worldpos.xz*0.25);//用世界坐标的每隔四米采样一次rain图片。frac取小数部分。然后blend one one
    屏幕反射:绘制Quad,通过blend one one,从视角方向计算顶点的反射光线,首先对当前点顶点坐标通过噪声图随机偏移一点使之产生模糊效果(因为该算法得不到精确的镜面反射效果),然后从改点的反射光线方向射出一条射线比如for循环10次 每次在该反射方向加上一个偏移值进行对比计算,
    如果加偏移后的点的深度大于该点对应屏幕(实际背景)的深度值,说明射线已经射到模型内部了,那么取出该uv给原顶点用,同事对skytexture进行反射采样和射线反射进行叠加。其中射线的每次偏移量根据摄像机角度计算,远处偏移大,垂直角度偏移小。由于射线采样深度次数多,所以效率不高。
    垂直同步:显示器上的所有图像都是一线一线的扫描上去的,垂直同步就是等上一帧的线扫完了再画下一帧 ·防止画面撕裂,,高配置硬件开启垂直同步能使得游戏进程和显示器刷新率同步,使得画面平滑,使得画面稳定,低配置关闭垂直同步可以一定程度上减小显卡的负担,让画面更加流畅一些。

    噪音的作用:

    自阴影HorizonMapping:1.通过法线方向与光源方向是否一致可以判断该点是否是阴影点,2为解决自身遮挡的问题,计算每一个点的仰角(当前点向某一个方向射出的射线,使其恰好遮挡该点,没有遮挡则是0,用递归偏移去计算)然后把信息存在贴图里边,光照的时候就只计算该点的仰角和光源角度,光源仰角大于该点我们就照亮该点。3.该方法只适合光源在某一个平面运动,如果光源在360空间运动,需要更麻烦的插值计算或者实时计算。


    OnPreCull () // 把这个赋给相机.每帧在相机裁剪场景前调用,裁剪决定哪些物体可见 会被渲染。
    OnPreRender ()// 把这个赋给相机.每帧在相机渲染场景之前被调用
    OnPostRender () // 把这个赋给相机.每帧在相机完成场景渲染之后被调用。
    void OnRenderImage(RenderTexture src, RenderTexture dest){}//屏幕后处理函数
    Graphics.Blit (src, dest,mat,pass);//屏幕后处理 src当前屏幕纹理
    Graphics.DrawMesh(aMesh, Vector3.zero, Quaternion.identity, aMaterial, 0);//给定材质绘制网格模型 只能在Update里边调用生效。

    mat.SetPass(0);//给当前渲染帧设置shader渲染,为渲染激活给定的pass 设置后如果blit没有使用材质也会使用该pass,Blit and DrawMeshNow.
    Graphics.SetRenderTarget(rtex)//设置当前的渲染纹理,如果设置该项后,DrawMeshNow再绘制的图像将保存在该纹理中,然后blit到dst就可以单独渲染显示了。这是渲染总的图形目标不同于相机视角的渲染目标。blit完后也需要重新设置SetRenderTarget。
    Graphics.SetRenderTarget(colorbuff,depthbuff)//把渲染好的纹理和深度设置好 再绘制DrawMeshNow 才会有叠加效果。一般情况下DrawMeshNow的rendertarget设置成相同的一个。
    Graphics.DrawMeshNow(aMesh, Vector3.zero, Quaternion.identity);//只可渲染静态物体,在相机上,用上一次(最近一次)处理的材质进行绘制,一般前边加mat.SetPass(1);只用该pass进行渲染。matrix 可以对mesh进行矩阵变换。一定要SetRenderTarget才会绘制在对应的buffer上
    Graphics 是unity的底层API,Camera是Unity的上层API
    手动绘制动态物体可用:CommandBuffer渲染,buff.DrawRender(render,mat);Graphics.ExcuteCommandBuffer(buff);该API会消耗2个drawcall,所以静态物体尽量用DrawMeshNow.

    Rendertexture rt=new Rendertexture(…)//w.h.depthbuffer,对于要渲染深度的rt,depthbuffer要设置至少16位。
    camera.targetTexture=rt //设置相机的rt 相机会把渲染内容设置到rt上,然后该相机game窗口不可见
    camera.clearFlags=CameraFlags.SolidColor;//记得设置的默认颜色
    camera.SetTargetBuffers(rt.colorBuffer,rt.depthBuffer)//设置当前相机的渲染目标RenderTarget设置后要么EncodeDepth
    camera.RenderWithShader(shader,“RenderType”)//找物体shader有"RenderType"字段 和shader有"RenderType"字段相等的shader的subshader进行替换渲染。每帧调用才会生效。也可以自定义如"Tag"=“MainChar”
    绘制到rt后再和其他相机rt混合然后blit输出,在该功能下的相机深度和颜色都是新的只满足条件的物体的。如果在场景中,多个camera需要把深度和颜色存起来,然后设进去。
    用camera渲染和其他camera混合渲染的时候camera.clearFlags=CameraFlags.Nothing不然如果设置SolidColor会把以前的color 给Clear掉

    ds渲染colortexture和depthtexture,colortexture的colorbuffer用来存颜色和粗糙度,depthbuffer用来存系统深度,depthtexture的colorbuffer用来存深度和法线。
    RenderTexture的colorbuffer和depthbuffer的理解:colorbuffer用来存camera.SetTargetBuffers后的颜色值,其中的值可以自己定义 可以是颜色也可以是深度和法向量 然后自己用来计算 对应RenderTexture的rgba值,depthbuffer是系统用来写depth用的,还包含了stencil等信息,只决定前后关系,不会给我们参与计算。
    所以在SetTargetBuffers为多个RT时,shader里边的frag输出也是输出多个color值。
    Camera.SetReplacementShader(shader,tag)//调用这个函数之后,相机将使用替换的shader来渲染它的视图,一直替换渲染。当replacementTag为空时会替换视口中所有物体的shader。
    Camera.ResetReplacementShader()//从相机上移除shader替换。

    要分清楚camera的SetTargetBuffers和Graphics的SetRenderTarget。如果是相机渲染到某个纹理用camera的,但是如果要设置图形输出的纹理设置Graphics,不要搞混。

    ds渲染流程:shader写好后在colorbuff.cg里边运算和输出,屏幕后处理会根据平台和设置使用替换的shader如MRT.shader去渲染,然后MRT.cg去输出颜色。
    模型材质上使用的shader并不是最终渲染使用的shader,根据设置在precull阶段 会使用替换shader去渲染,比如流程用前向渲染和支持MRT的Shader渲染和低版本的DiffuseRoughness渲染。然后屏幕后处理阶段用替换shader去实现一些特殊效果。
    同一个shader的pass越多那么提交GPU绘制的批次就越多 也就是drawcall越多。所以主角轮廓线渲染怪物深度时要当心。
    屏幕水波纹扰动效果:1.把上一帧的屏幕图像传给水波纹和maintex参与计算扰动,然后缩放水波纹大小就形成了水波纹大小。技能特效的扰动也用该方法。


    阴影:1.先用阴影相机变换到光源的位置渲染深度,将深度存储在自定义深度纹理中。由于深度z范围从nearclip到farclip,将z除以远裁剪值,以把深度转换到0-1空间进行存储。
    2.用正常相机渲染顶点 将顶点转换到阴影相机空间 求得深度值z,再除以farclip转换到0-1范围内 再和上面采样的depth做比较 判断该点是否是阴影。阴影相机的targetTexture=rendertexture一定要设置,保证获取阴影相机proj矩阵是按照阴影相机宽高渲染,不然project矩阵宽高会不对。
    阴影锯齿解决:
    1.由于场景阴影分辨率不足,阴影计算锯齿会比较严重,为了使阴影效果更逼真 没有锯齿,可以采用周围四次采样深度值进行插值计算。普通场景可采用该方式提高效果。
    5.对于主角或一些较高要求的物件,可以采用另外的阴影相机去渲染一张高分辨率的深度图计算简单计算阴影,这样就不会有锯齿了,最多可能会显得比较自然硬。
    6.可以在normal方向或者光照方向偏移一点点的bias来减少锯齿,不能直接对z进行偏移。另外在画阴影的时候参与光照计算效果会更好,如果直接画阴影可能会
    4.渲染RT的new Rendertexture(…)的时候 一定要设置深度至少16位才有效。第三位参数,不然深度可能没写进去。
    5.一般采用计算光照渲染的时候乘以阴影系数的效果会比直接画阴影效果好
    6.在设置相机的buffers和RenderWithShader后必须调用Graphics.SetRenderTarget,否则GPU上后续pass会失去原rt的信息。因为camera.SetTargetBuffers会自动设置gpu的SetRenderTarget,但是调用完后会清除,所以必须重设。
    7.normal等信息最好来自depthTex采样,而不是vertex传过来,因为depthTex已经处理过了是否有bump等情况,会方便点。
    8.RenderWithShader计算的参数来自于原材质的shader,需要声明原变量就可以用,但是在计算主角的时候 我们直接采样RT 就是用pro_uv去采样参与计算、DiffuseRT经ColorBuffer.cg计算里边已经包含了粗糙度等基本信息 方便后续计算。
    9.关于阴影系数,正常就是0和1,对于多次采样有过度的可能有插值,对于单次采样的0-1表现可能会生硬,一般采用该系数乘以一个值转换到0-0.n或者0.n到1
    10.软阴影判断阴影处的强度,可以通过判定该点和周围采样的深度差值,如果完全处于阴影点,周围的深度和该点一样,如果处于阴影边缘点 周围点的深度和该点就会有较大差值。
    场景普通阴影(低分辨率)等级分级:
    0级:没有阴影处理
    1-级:采样1次深度进行简单的深度对比。(当相机分辨率足够的时候 用这个最精确,主角阴影绘制)
    1级:y方向采样2次深度进行对比插值,会有一点点边缘软阴影。
    2级:2个方向进行4次采样,再进行2次lerp,软阴影效果会更好一点。
    3级:周围9个点进行采样和多次插值,软阴影效果可以很棒了。
    4级:周围8或12个点按一定规则去采样,再和rot随机图进行矩阵乘法组合随机转向,保证相连点不采样到同一点,最后叠加的效果边缘模糊感更强。rot采样图很重要。ds暂时没用
    float depth = DecodeDepth(XY_Depth.xy)-newdepth>0;// depth=0 or 1


    常用矩阵变换
    Camera.worldToCameraMatrix //世界转相机矩阵 world2view 右手坐标系
    Camera.cameraToWorldMatrix //相机转世界矩阵
    Camera.projectionMatrix //get投影矩阵viewToproject 视角空间到2维投影空间矩阵
    transform.TransformDirection (Vector3.forward);//从自身坐标到世界坐标变换方向。返回的向量与direction有同样的长度。
    transform.InverseTransformDirection(0, 0, 1);//变换方向从世界坐标到自身坐标 important
    transform.TransformPoint(2, 0, 0);//顶点变换位置从自身坐标到世界坐标
    transform.InverseTransformPoint(0, 0, 0);//顶点变换位置从世界坐标到自身坐标,c#里相机的世界空间转view空间用这个,不需要用矩阵计算。
    transform.worldToLocalMatrix//Unity的左手坐标系,相机可用这个从world转换到view空间,这个与Camera.worldToCameraMatrix(右手坐标系)的区别在于z轴方向反向。Camera.worldToCameraMatrix与shader的UNITY_MATRIX_MV相同,用这个计算需要反转z值。transform是unity左手系,camera是右手系。
    GL.GetGPUProjectionMatrix(camera.projectionMatrix,true)//从Camera的投影矩阵得到GPU(图形处理器)的投影矩阵(该函数在跨平台是有用)。view2project
    vec3=Matrix4x4.MultiplyPoint(vec3) //通过该矩阵把vec3点变换位置,从某一空间投影到另一个空间。
    vec3=Matrix4x4.MultiplyVector(vec3) //通过该矩阵把vec3向量变换方向
    Camera.RenderToCubemap(rt)//用相机渲染360全景图,其中rt要设置isCubeMap=true,isPowerOfTwo=true; 这时候修改camera的旋转是没用的,一般用RenderTexture去渲染比CubeMap消耗少、
    Camera.WorldToScreenPoint§//wolrd to 屏幕空间 左下角0,0 右上角width,height。 z就是深度了
    Camera.WorldToViewportPoint§//world to view空间 左下角0,0 右上角1,1


    平台和宏定义
    Direct3D里面是左上角为原点;
    OpenGL和OpenGL ES是左下角为原点。
    三维坐标转换为齐次坐标 顶点把w分量设为1,向量把w分量设为0
    SHADER_API_OPENGL - desktop OpenGL
    SHADER_API_D3D9 - Direct3D 9
    SHADER_API_D3D11 - desktop Direct3D 11
    SHADER_API_GLES - OpenGL ES 2.0 (desktop or mobile), use presence of SHADER_API_MOBILE to determine.
    SHADER_API_GLES3

    顶点转换:
    #if UNITY_UV_STARTS_AT_TOP//dx 这个不管用了 无法识别Shadow_API_Metal 苹果的新设备支持的平台
    proj.y*=-1;
    #endif
    不完全等价:
    #if SHADER_API_GLES||SHADER_API_GLES3
    //大部分安卓手机和部分ios手机,一般用该宏定义
    #else
    proj.y*=-1; //一般用这个
    #endif
    uv转换:
    #if SHADER_API_MOBILE
    #if UNITY_UV_STARTS_AT_TOP
    #else
    uv.y = 1-uv.y;
    #endif
    #else
    #if UNITY_UV_STARTS_AT_TOP
    uv.y = 1-uv.y;
    #endif
    #endif
    -------Unity预编译宏--------
    1.在pass里边#define USE_Dynamic_Fog 1 然后写多个pass分级, 然后在cg里边 #if USE_Dynamic_Fog==1 … #endif,也可以只定义不取值。

    2.#pragma multi_compile LOW Mid High,编译了3个宏,该方式宏只能多选一,不能选多个,用 material.shaderKeywords = [“LOW”];或者material.EnableKeyword(“LOW”),那么Shader里边只能判断三个状态,三个中的其中一个生效,如果没有Enable一个宏或者Enable了多个宏,则默认第一个宏打开。
    注意的是如果编译单个宏就是打开和关闭状态则用:该方法就可以在代码里边通过打开关闭开关分级。
    #pragma multi_compile R_Off R_On 也需要分两个宏来区分,与#define情况不同。或者
    #pragma multi_compile __ R_Off 与上等效(注意空格)。 等同于#pragma shader_feature R_Off //#if R_Off … #endif 这个和我们自己定义#define AO_QUILITY 3效果差不多


    矩阵乘法和投影矩阵:
    MNA:代表A先经过N矩阵变换再经过M矩阵变换。从右往左看。
    矩阵变换的顺序一般是:先缩放,再旋转再平移,MtMrMsP
    缩放矩阵:左对角线值
    旋转矩阵:左3x3矩阵
    平移矩阵:第四列
    Matrix4x4 cameraviewToshadowview=shadowcamera.worldToCameraMatrix
    camera.cameraToWorldMatrix//从一个相机的view空间转换到另一个shadow相机的view空间
    一般情况下矩阵乘法结合在CPU里边做好,在GPU阶段一般不这么做,一是效率不好,另外一个是shader的矩阵直接相乘意思和上面的不一样。
    world和View之间的矩阵变换是线性的,但是一旦转到project空间就是非线性的,所以要除以w进行归一化。
    常用变量计算方法:
    float4 projectpos/=projectpos.w;//归一化 归一化后的NDC坐标范围在-1,1内,projectpos先用MVP矩阵把顶点转换到project空间。
    float2 uv=projectpos.xy*0.5f+0.5f;//project(需要归一化)坐标的xy就是uv坐标的衍射 屏幕图像的uv,uv坐标也可以在顶点阶段用unity的接口ComputeScreenPos()计算,然后在frag里边去归一化就可以用了。
    projectpos.z存的是深度缓存0-1 但是unity将其进行了矫正 是非线性的,会导致近处精度高 远处精度低,那么远处效果就不会好,我们ds的深度值用的是view空间的z值0-far。
    float depth=-viewpos.z/_ProjectionParams.w;//view坐标的z值存的就是深度值,除以farclip衍射到0-1范围存储,z(<0表示在前方)坐标需要乘以-1衍射深度。但是使用我们自定义的viewmatrix不需要,采用不同坐标系已经定好了方向。projet的z是深度缓存。
    渲染深度的时候要记得先把背景图设置成白色,或者把相机clearFlag设置SolidColor然后颜色设置成白色,保证没有被渲染的地方深度为1

    关于像素深度值还原坐标:(因为每个点都要矩阵转换,所以效率不高)
    float3 viewpos=mul(project_Inverse,float3(projectpos.x,projectpos.y,depth));//若depth=projectpos.z 表示把该模型的顶点还原view空间,取depth代表屏幕处于同一点显示的前后坐标点将project空间转换到view空间、一般用来背景图的深度值还原其具体坐标。
    我们的计算方式:(project空间还原知道深度的像素view 坐标点 )
    float3 farpos=float3(projectpos.xy,1);//该值代表project空间远裁决上和当前屏幕xy重合的最远坐标点。ps里边计算projectpos的时候如果用系统提供的矩阵一定要在dx下projectpos.y=1-projectpos.y我们自己的矩阵好像是有转换过的。
    Vector3 _FarCorner;//c#里边计算的相机的farclip右上方坐标点(x,y,z),其对应的左下方就是(-x,-y,z);这两个坐标的衍射刚好对应于project空间xy的(-1,1)之间。
    float3 viewfarpos=farpos*_FarCorner;//该式直接将farpos映射到view空间的远裁决上和当前屏幕xy重合的最远坐标点。
    float3 viewpos=viewfarpos*depth;//利用相似三角形原理,在屏幕上重合的两个点,在相机perspective视角下呈现相似三角形,远裁剪上的点(proj.z=1)和求得点(z=depth)是一条线,直接相乘可得坐标点。

    ViewPos的理解,以相机为坐标系原点的坐标,显示结果是屏幕坐标中间是(0,0),向上y变大 向下y变小,右边x变大,坐标x变小为负、不要直接与世界坐标空间的一些位置参与计算。normalize(ViewPos)为相机到点方向与点到相机视角方向反向相反。
    float4 wpos=mul(View2World,float4(vpos,1));//矩阵转换空间的时候一定要带上四维坐标,如果只是三维的mul(View2World,vpos)代表值进行了三维运算的旋转和缩放,没有计算平移。如果要用w参与转换一定要先保证矩阵转换信息里边和原始值的w正确,像我们的farcorner的w是没用的,所以转换后w是无效的。

    关于我们屏幕后处理的DrawMesh:
    Graphics.DrawMesh(Quad,Vector3.zero,Quaternion.identity,0)//默认情况如果MVP转换该mesh位置在相机原点。视锥范围外 被剔除。
    o.pos=float4(v.verter.x2,v.vertex.y2,0,1);//简化直接转换到project空间的近裁剪位置。ZTest Less
    o.pos=float4(v.verter.x2,v.vertex.y2,1,1);//简化直接转换到project空间的远裁剪位置。ZTest Greater
    所以根据以上两个pos转换的位置不同,ZTest 也要不同。Quad的坐标是-0.5,0.5 乘2直接到proj空间的-1,1.然后proj.z值0-1代表近裁剪到远裁剪,proj.w=1 代表顶点。

    float3 dir=mul((float3x3)Project,vdir);//vdir是float3的时候 矩阵必须转换成三维矩阵,否则可能有些平台会报错。或者把vdir转换为float4也行,点w=1,向量w=0、
    float4 dir=mul(Project,float4(vdir,0));//or

    在给shader设置参数的时候:
    mat.SetTexture("",_tex);//尽量用这个,只给该材质设置参数
    Shader.SetGlobalTexture("",_tex)//全局变量,在cpu阶段查找所有材质,有该变量就设置该参数。效率影响在cpu 应该不是gpu

    环境光贴图
    Diffuse Environment CubeMap :使用网格表面的法线去采样
    specular environment CubeMap :使用反射矢量去采样


    常用光照模型和向量计算方法:
    1.漫反射Lambert:float3 diff=max(0,dot(i.normal,i.lightDir));//lightDir是光源方向的单位向量,normal是法向量。C是光源颜色。反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比。取max防止颜色为负
    2.Half-Lambert:float3 diff=dot(i.normal,i.lightDir)0.5f+0.5f;//将颜色空间映射到0-1而不是采用lambert的截断,lamber模型会使没有光照的地方全黑,可使用环境光避免,HalfLambert会使没有光照的地方显示微光。大部分情况用该模型。
    2.高光反射Phong:float3 spec=pow(saturate(dot(reflectdir,viewdir)),_Gross);//视角方向与反射方向点乘截取后的gloss次方。gloss光泽度越大 亮点越小,求反射光用reflect注意光源方向和入射方向相反。
    4.Blinn-Phong:float3= pow(max(0,dot(worldNormal,halfDir)),_Gross); float3 halfDir=normalize(worldlightdir,ViewDir); //对视角方向和光照方向相加再归一化得到halfDir作为参数,再来和法向量点乘 再power。高光看起来更亮更大一些,大部分情况用这个模型
    3.输出颜色:float4 col=(diff+spec+ambient)C
    3.光源方向:常说的光源(光照)方向一般指点到光源的方向,而不是入射光线的方向。_WorldSpaceLightPos0.xyz,.w=0表示平行光,.w=1表示点光源或聚光灯,当然不同引擎可能存的方向也不一样,但是unity是这样定义的。
    4.视角方向:float worldViewDir=normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); //unity API: UnityWorldSpaceViewDir(worldPos.xyz)一般指点到相机的视角方向,该公式在世界空间下。
    5.View空间视角放向:float3 viewdir=viewpos;//view空间的坐标值等于相机到该点的视角方向。
    5.反射方向:float3 reflect = reflect(o.lightDir, o.worldNormal);//入射光线(不是光源方向),表面法线,一般用来求采样cubemap或者天空盒采样的反射信息。场景中的实时反射一般采用单独渲染反射纹理, 把坐标进行变换后合并。
    6.视角光线的反射方向:float3 o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);//用视角方向的反向量 反求入射光线(反向),视角方向求视角光线的反射方向,用该反射方向去采样纹理。
    7.折射方向: float3 refract = refract(normalize(o.lightDir), normalize(o.worldNormal), _RefractRatio);//入射光线单位向量,表面法线单位向量,介质比
    8.视角光线的折射方向:float3 worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);//用视角方向的反向量 反求折射光线(反向)
    9.菲涅尔反射:float fresnel=pow(1-saturate(dot(worldViewDir,normal)),4)//视角方向和法线方向,他们之间的夹角越小,fresnel越弱 反射越越小 折射越强。另外也有反射和漫反射之间fresnel插值公式。为了效率可不执行power操作。
    float3 finalcol=fresnel
    reflect+(1-fresnel)refract;//fresnel插值反射和折射,用来计算反射和折射之间根据视角方向各种占的比重
    10:光照衰减公式:float atten= 1/(_WorldSpaceLightPos0.xyz,wpos.xyz);//衰减物理公式与距离的平方成反比,有时候也用距离反比函数做衰减。
    10.高度雾:float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);然后把该值和颜色进行插值。有线性雾,指数雾,指数平方雾。雾对噪声图进行动态采样,可以实现动态雾效的效果。
    11.PBR基于物理的着色:输入参数 粗糙度和金属性,ds的完美模式下采用PBR渲染,PBR()接口输入各种方向,返回diff和spec,内部实现了金属材质曲线的方式。但是快速的前向模式下,只是计算了简单的漫反射。
    金属度大,漫反射变小,高光反射变大,金属材质高光反射率RGB分量可能不同
    金属度小,漫反射变大,高光反射变小,非金属材质高光反射率RGB分量是一样的(大多数很接近<0.05, 0.05, 0.05>)
    粗糙度大:光滑变暗,漫反射增多,镜面反射变小
    粗糙度小:越光滑,镜面反射效果增多 漫反射变小
    复杂的PBR效果是叠加了环境高光贴图形成的,并不是单一的PBR公式。叠加的贴图可以是金属也可以是其他材质。
    传统光照模型:I=普通漫反射+高光
    Cook-Torrance光照模型:I=普通漫反射L+镜面反射光强R,光滑度控制鏡面反射,金屬度控制高光。
    L=Lambert
    saturate(1-f0
    f0);//float f0 = (1-RR)/(1+RR);
    镜面反射光强R=FDG/(VN)或R=FDG/((VN)(NL)),也可以省略成R=FDG,其中F是Frenel系数,D是微平面法线分布函数,G是几何遮挡系数
    F=f0+(1-f0)(1-vh)`5//f0代表入射角接近0 靠近法向量时的fresnel反射系数。该公式精确度1%。
    D复杂公式
    G几何遮挡公式,也可以简略为固定值比如0.25
    ds公式:float R=saturate(1-_SmoothBase)
    float3 diff=1/PI max(0,dot(lightdir,normal))saturate(1-power((1-RR)/(1+RR),2));
    float h=normalize(worldlightdir,ViewDir);
    float nh=saturate(dot(normal,h));
    float3 spec=R
    RRR/power((nhnh(RRRR-1)+1),2)(1+meatal*3)/4PI
    Ps.normal转换到View空间参与计算,记得考虑坐标系变化,可以i.view_pos_nor.z=-i.view_pos_nor.z把z反向,因为Unity物体是左手坐标系,view space是右手坐标系。ds的EncodeNormal和DecodeNormal会自动转换坐标系,但是Unity的EncodeViewNormalStereo是不会转换坐标系的。顶点转换也一样要考虑坐标系。

    12.次表面散射其中一种实现方式环绕光照:diff = ( dot(normal, lightDir) + wrap )/( 1+wrap ) //wrap为环绕参数,最后再计算该点模型的厚度作为参数相乘、输出颜色=coldiffdepthMinus;//diff乘以颜色和厚度(前后深度差)

    展开全文
  • shader

    2018-10-24 16:15:38
    流水线 1.应用阶段:(CPU)输出渲染图元,粗粒度剔除等 比如完全不在相机范围内的需要剔除,文件系统的粒子系统实现就用到粗粒度剔除。 2.几何阶段:(GPU)把顶点坐标转换到屏幕空间,包含了模型空间 到世界空间 到...

    流水线
    1.应用阶段:(CPU)输出渲染图元,粗粒度剔除等 比如完全不在相机范围内的需要剔除,文件系统的粒子系统实现就用到粗粒度剔除。
    2.几何阶段:(GPU)把顶点坐标转换到屏幕空间,包含了模型空间 到世界空间 到观察空间(相机视角view) 到齐次裁剪空间(投影project空间,四维矩阵,通过-w<x<w判断是否在裁剪空间,xyz值都在-w,w之间) 
                到归一化设备坐标NDC(四维矩阵通过齐次除法,齐次坐标的xyz除以w实现归一化(x/w,y/w,z/w,w/w),xy坐标到在-1,1上,z坐标一般是0,1)  到屏幕空间(通过屏幕宽高和归一化坐标计算)。
                a.顶点着色器:坐标变换和逐顶点光照,将顶点空间转换到齐次裁剪空间。
                b.曲面细分着色器:可选
                c.几何着色器:可选
                d.裁剪:通过齐次裁剪坐标的-w<x<w判断不在视野范围内的部分或者全部裁剪,归一化。
                e.屏幕映射:把NDC坐标转换为屏幕坐标
    3.光栅化阶段:(GPU)把几何阶段传来的数据来产生屏幕上的像素,计算每个图元覆盖了哪些像素,计算他们的颜色、
                a.三角形设置:计算网格的三角形表达式
                b.三角形遍历:检查每个像素是否被网格覆盖,被覆盖就生成一个片元。
                c.片元着色器:对片元进行渲染操作
                d.逐片元操作:模板测试,深度测试 混合等
                e.屏幕图像

    矩阵:
    MA=AM的转置(M是矩阵,A是向量,该公式不适合矩阵与矩阵)

    坐标转换:
    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);顶点位置模型空间到齐次空间
    o.worldNormal = mul((float3x3)_Object2World,v.normal);//在正交变化下可以得到近似模型空间转换到世界空间的法向量,但是非正交情况会得到错误的情况。也不能只是逆矩阵(如果用_World2Object 会出错)
    o.ViewNormal = mul(v.normal, (float3x3)UNITY_MATRIX_IT_MV);//公式上的正确转换方式,法线模型空间到view空间,用该矩阵的逆转置矩阵去转换。但是Unity里边也许考虑了这个 可能不需要再做一遍。
    //但是这里要注意下在Unity里边也许 o.view_pos_nor.xyz = mul(UNITY_MATRIX_MV, float4(v.normal,0)).xyz;是正确的,有可能Unity的这个矩阵帮我们考虑法线问题。‘

    normal或者顶点转换到View空间参与计算,记得考虑坐标系变化。可以i.view_pos_nor.z=-i.view_pos_nor.z;把z反向,因为Unity物体是左手坐标系,camera space是右手坐标系。
    当然也可以直接在变换矩阵里边考虑好 坐标系的变化。Camera.worldToCameraMatrix 是右手坐标系计算,需要反转z,transform.worldToLocalMatrix和unity一样的左手坐标系计算。
    在Opengl中,本地坐标系和世界坐标系都是右手坐标系,直到投影空间中才变换为左手。而unity中的本地坐标系和世界坐标系是左手系统,但是View空间是右手坐标。


    API:
    UNITY_MATRIX_MVP        将顶点方向矢量从模型空间变换到裁剪空间
    UNITY_MATRIX_MV         将顶点方向矢量从模型空间变换到观察空间
    UNITY_MATRIX_V          将顶点方向矢量从世界空间变换到观察空间
    UNITY_MATRIX_P          将顶点方向矢量从观察空间变换到裁剪空间
    UNITY_MATRIX_VP         将顶点方向矢量从世界空间变换到裁剪空间
    UNITY_MATRIX_T_MV       UNITY_MATRIX_MV的转置矩阵
    UNITY_MATRIX_IT_MV      UNITY_MATRIX_MV的逆转置矩阵,用于将法线从模型空间转换到观察空间
    _Object2World             将顶点方向矢量从模型空间变换到世界空间,矩阵。
    _World2Object            将顶点方向矢量从世界空间变换到模型空间,矩阵。
    模型空间到世界空间的矩阵简称M矩阵,世界空间到View空间的矩阵简称V矩阵,View到Project空间的矩阵简称P矩阵。
    这些矩阵需要先声明了才能用

    _WorldSpaceCameraPos    该摄像机在世界空间中的坐标
    _ProjectionParams   x = 1,如果投影翻转则x = -1   y是camera近裁剪平面   z是camera远裁剪平面    w是1/远裁剪平面
    _ScreenParams   x = 屏幕宽度,y = 屏幕高度,z =  1 + 1.0/屏幕宽度, w = 1 + 1.0/height屏幕高度(指像素数)
    _ZBufferParams   x = 1-far/near    y = far/near     z = x/far     w = y/far
    unity_OrthoParams
    unity_Cameraprojection
    unity_CameraInvProjection
    unity_CameraWorldClipPlanes[6]    摄像机在世界坐标下的6个裁剪面,分别是左右上下近远、


    1.表面着色器
    void surf (Input IN, inout SurfaceOutput o) {}表面着色器,unity特殊封装的着色器
    Input IN:可以引用外部定义输入参数
    inout SurfaceOutput o:输出参数
    struct SurfaceOutput//普通光照
    {
     half3 Albedo;//纹理,反射率,是漫反射的颜色值
     half3 Normal;//法线坐标
     half3 Emission;//自发光颜色
     half  Specular;//高光,镜面反射系数
     half  Gloss;//光泽度
     half  Alpha;//alpha通道
    }
    基于物理的光照模型:金属工作流SurfaceOutputStandard 高光工作流SurfaceOutputStandardSpecular 
    half3,half4代表rgba或者xyz,可以分开用 Albedo.xy=1.或Albedo.ga=1


    #pragma surface surfname lightModel op - 指出函数surfname 表面着色器。lightModel的光照模型和可选op操作,还可以添加顶点修改函数vertex和颜色修改函数finalcolor。lightModel的输入结构有定义的固定几种
    #pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
    #pragma vertex name - 指出函数name 是顶点着色器。
    #pragma fragment name - 指出函数name 是片段着色器。
    #pragma fragmentoption option - 添加option 到编辑的OpenGL片段程序。参看ARB fragment program说明书了解被允许的选项列表。这个指示在顶点程序或者编辑到非OpenGL targets的程序没有影响。 
    #pragma multi_compile_builtin - 为了pixel-lit shaders;;这个将告知Unity去编辑大量的这个着色器程序数列以支持所有的照明种类,和所有的阴影选项。 
    #pragma multi_compile_builtin_noshadows - 对于pixel-lit 着色器,不接受阴影。这将告知Unity去编辑几个该着色器程序的数列来支持所有的照明种类。这比multi_compile_builtin pragma可以更快的编辑,而且结果着色器也更小。
    #pragma target name - 那个着色器target 去编辑。细节参看shader targets。 
    #pragma only_renderers space separated names - 只为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。
    #pragma exclude_renderers space separated names - 不为给定的渲染器编辑着色器。默认情况下,着色器为所有的渲染器被编辑。细节参看 renderers。

    #pragma multi_compile TEST1 TEST2 告诉Unity编译两个不同的shader版本 分别定义了宏定义TEST1和TEST2.在C#中用material.shaderKeywords = [“TEST1”]或者Shader.EnableKeyword (“TEST1”)去区分调用不同宏的内容;;

    2.顶点着色器
    struct appdata_full {//vertex输入
        float4 vertex : POSITION;//must
        float4 tangent : TANGENT;
        float3 normal : NORMAL;
        float4 texcoord : TEXCOORD0;//该顶点的纹理坐标,第一组纹理坐标uv 也就是第一张贴图的坐标、为了实现多重纹理贴图,比如子弹打在墙上的弹痕等
        float4 texcoord1 : TEXCOORD1;//n的数量和shadermodel版本有关, Mesh.uv2 
        float4 texcoord2 : TEXCOORD2;//Mesh.uv3  可以通过外部设置该uv坐标
        float4 texcoord3 : TEXCOORD3;
        fixed4 color : COLOR;//顶点颜色
    };@上次买的

    3.片段着色器
    struct v2f{//vertec的输出和frag的输入
        float4 vertex :SV_POSITION;//must
        float3 color0:COLOR0;
        float3 color1:COLOR1;
        float4:texcoord:TEXCOORD0;//TEXCOORD0-TEXCOORD7自定义纹理坐标
    }
    SV_Tatget //frag的输出,输出float4的颜色 也可以是COLOR0各个平台会自动转换标志


    光照:
    1.逐顶点光照:在顶点着色器阶段计算光照,效率高但是效果不好,在边缘像素映射的时候插值可能会产生锯齿。
    2.逐像素光照:在片元着色器阶段计算光照,计算量大,但是边缘表现效果好。
    3.半兰伯特模型:处理无光照的地方,也让其有光,不然可能是全黑。经验模型。
    4.Blinn-Phong模型:高光反射模型的经验模型,高光部分看起来会更大更亮写。更符合实际些。

    #include “Lighting.cginc”
    Tags { “LightMode”=“ForwardBase” }

    WorldSpaceViewDir(float4 v)  输入模型空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
    UnityWorldSpaceViewDir(float4 v)  输入世界空间中的顶点坐标,返回世界空间中从该点到摄像机的观察方向
    ObjSpaceViewDir(float4 v)     输入模型空间中的顶点坐标,返回模型空间中从该点到摄像机的观察方向
    WorldSpaceLightDir()        仅用于前向渲染,输入模型空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
    UnityWorldSpaceLightDir()    仅用于前向渲染,输入世界空间中的顶点坐标,返回世界空间中从该点到光源光照方向,没有归一化。
    ObjSpaceLightDir()            仅用于前向渲染,输入模型空间中的顶点坐标,返回模型空间中从该点到光源光照方向,没有归一化。
    UnityObjectToWorldNormal(float3 v)把法线从模型空间转换到世界空间
    UnityObjectToWorldDir(float3 v)把方向矢量从模型空间转换到世界空间
    UnityWorldToObjectDir(float3 v)把方向矢量从世界空间转换到模型空间
    _WorldSpaceLightPos0.xyz    获取平行光光源方向,或者点光源的光源位置
    _LightColor0.rgb            获取当前pass的光源颜色和强度
    UNITY_LIGHTMODEL_AMBIENT.xyz; 环境光
    normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); 世界空间下的view视觉方向 UnityWorldSpaceViewDir

    a.漫反射公式:diff=Cmax(0,dot<L,N>);//C是颜色和强度_LightColor0.rgb
    代码:  diff=max(0,dot(i.normal,i.lightDir))//i的单位向量and单位法向量
        c=tex2D(tex,i.uv)
    _LightColor0*diff//_LightColor0表示的是场景中平行光的颜色和强度

    b.高光反射公式:Spec=pow(max(0,dot(R,V),gloss))//R 单位反射向量reflect(ray,normal)函数获取,V视线单位方向向量 ,gloss光色度
    代码: Spec=pow(max(0,dot(reflect(-i.lightDir,i.normal),viewdir),32))
        c=c**_LightColor0*(diff+Spec)
        
    颜色相加和相乘的物理意义,一般的各种光源都是颜色叠加,相乘会变淡。

    在PS中 tex2D 自动计算应该使用的纹理层。
    tex2Dbias需要在t.w中指定一个偏移量来把自动计算出的纹理层全部偏移指定的值。
    tex2Dgrad需要提供屏幕坐标x和y方向上的梯度来确定应该使用的纹理层。
    tex2Dlod需要在t.w中明确指定要使用的纹理层。
    texCUBElod(_Cube,float4(dir.xyz,lod))方向和Lod系数

    纹理 uv坐标是顶点存储了图片上的坐标
    _MainTex (“Main Tex”, 2D) = “white” {}
    sampler2D _MainTex;
    float4 _MainTex_ST;//Unity中 纹理_ST来默认声明该纹理的属性_MainTex_ST.xy表示Scale, Till缩放,_MainTex_ST.zw表示Transform 偏移
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//vs输入纹理坐标和纹理值输出UV,ps对uv进行纹理采样和计算。UV通常在0-1范围,等于o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;//反射率

    法线贴图:xyz映射存成rgb值。一般存在切线空间,z轴法线方向,x轴切线方向,y轴副(法)切线方向,我们ds的法线贴图xy存法线z存粗糙度。
    TANGENT_SPACE_ROTATION;//Unity来获取rotation矩阵,从模型空间到切线空间变换的矩阵。仅存在旋转和平移时,一个矩阵的转置矩阵等于他的逆矩阵。
    自己实现:
    float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; //切线空间的w分量用来存储负法线向内还是向外
    float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);//float3x3是按行存储

    float3 tangentNormal = UnpackNormal(packedNormal);Unity将法线贴图纹理坐标0,1映射到正常法线坐标-1,1,返回切线空间下的法线方向。法线贴图要设置成Normal格式。该设置unity有优化 rgb值不再是法线xyz的映射了,如果不设置的话要自己算 该公式不能用。
    自己实现:
    tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;//坐标反映射,自己计算的方法 因为packedNormal.z分量一般被压缩去掉,比如我们ds的z分量存储的是粗糙度,xy存法线。
    tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));//通过xy计算z  法线贴图一般会采用压缩的方式存储 那么只有xy分量是正确的,z分量需要经过xy计算出来。
    如果法线贴图是蓝色:代表存在切线空间,而且xyz可能没有被压缩。如果显示了其他颜色比如ds的绿色,就代表z通道被用做其他用途了。
    虽然在切线空间下计算光照更省,但为同一建议一般在世界空间下计算。

    遮罩纹理


    1.透明度测试AlphaTest:只要有一个片元的透明度不、、满足条件就被裁剪,用来优化显示。
    AlphaTest Greater AlphaValue//仅渲染 alpha 值大于 AlphaValue 的像素。AlphaValue :0-1
    AlphaTest GEqual AlphaValue    //仅渲染 alpha 值大于或等于 AlphaValue 的像素。
    AlphaTest Less    AlphaValue//仅渲染 alpha 值小于 AlphaValue 的像素。
    AlphaTest LEqual AlphaValue    //仅渲染 alpha 值小于或等于 AlphaValue 的像素。
    AlphaTest Equal AlphaValue    //仅渲染 alpha 值等于 AlphaValue 的像素。
    AlphaTest NotEqual AlphaValue    //仅渲染 alpha 值不等于 AlphaValue 的像素。
    AlphaTest Always     //渲染所有像素。这在功能上相当于 Alpha 测试关 (AlphaTest Off)。
    AlphaTest Never    //不渲染任何像素。
    第一步透明度测试不通过的放弃该片元
    2.模板测试:Stencil如果开启了模板测试,GPU会首先会读取模板缓冲区的值,然后把读取的参考值ref和该缓存值进行比较,比较方式由Comp指定,比如大于Greater就表示通过模板测试,
    然后由Pass Fail ZFail去指定通过和不通过模板和深度测试后对缓冲区的值进行的Operation处理。
    Stencil 
    {    Ref 2   //设置模板参考值为2
        Comp equal    //比较方式,有8种比较方式。  Greater指ref值大于缓存值。
        Pass  Operation     //这个是当stencil测试和深度测试都通过的时候,进行的stencilOperation操作方法
        Fail Operation //这个是在模版测试和深度测试都失败时执行的stencilOperation方法
        ZFail Operation        //这个是在stencil测试通过,但是深度测试没有通过的时候执行的stencilOperation方法。
        ReadMask readMask    //readMask默认是255,ds按位进行对比的时候常用,设置隐码后 读取ref和buff值都需要与该码进行与操作后再比较Comp。(0-255)
        WriteMask writeMask//写操作进行与操作  ,设置该掩码后先进行按位与操作后再写入
    }
    Comp 的参数包括Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never
    Operation的参数包括:
        Keep    保持
        Zero    归零
        Replace    拿比较的参考值替代原来buffer的值
        IncrSat    值增加1,但是不溢出,如果是255,就不再加
        DecrSat    值减少1,不溢出,到0就不再减
        Invert    翻转所有的位,所以1会变成254
        IncrWrap    值增加1,会溢出,所以255会变成0
        DecrWrap    值减少1,会溢出,所以0会变成255

    第二步模板测试不通过的放弃该片元

    clip(x) //x的任何分量小于0 被裁剪掉
    discard//舍弃当前片元
    ZWrite Off//关闭深入写入  默认开启
    ColorMask RGB|A|0 //设置颜色通道的写掩码,为0表示该pass不进行颜色输出。
    3.深度测试ZTEST:一个片元离摄像机的远近,渲染后会进行深度写入,通常会判断缓存深度和当前片元深度 可知前后关系。
    ZTest Always //指的是直接将当前像素颜色(不是深度)写进颜色缓冲区中 相当于ZTest Off
    ZTest Never    //而Never指的是不要将当前像素颜色写进颜色缓冲区中,相当于消失。
    ZTest Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never/Off,默认值为LEqual 即当物体深度小于或等于缓存深度值时(越远深度越大),该物体渲染,就是默认的先后顺序渲染。
    第三步深度测试不通过的放弃该片元

    透明度混合AlphaBlending:该片元需要关闭深度写入,不关闭深度测试。会导致片元之间深度穿插。可以采用2个pass,第一个pass只用来做深度写入ZWrite On,第二个pass只用来输出颜色ZWrite Off,这样深度和颜色效果才会正确
    Blend Off//关闭混合,只有blend打开后ps输出a通道才有意义
    Blend SrcFactor DstFactor//用同样的因子对rgba进行混合(默认都开启混合)第一个参数对源颜色(当前片元颜色,ps输出的颜色)*SrcFactor混合,
    第二个参数对目标颜色(当前读到的缓冲区颜色)*DstFactor混合,混合后默认相加后会重新写入缓冲区(相加后超过1的自动截断到1)。混合包括RABG值。结果都是源颜色和目标颜色与各自因子相乘后再加起来作为输出颜色。
    shader里边的向量相乘不同于点乘叉乘,相当于各项分别相乘。

    Blend SrcFactor DstFactor,SrcFactorA DstFactorA//把rgb和a的混合因子分开。
    混合因子
    One     //因子是1
    Zero    //因子是0
    SrcColor    //因子为源颜色值,当前片元颜色,对应rgba分量分别与SrcColor分量相乘
    SrcCAlpha    //因子为源颜色透明值值,对应rgba分别与SrcCAlpha相乘。
    DstColor    //因子为目标颜色值,当前读到的缓冲区颜色
    DstAlpha    //因子为目标颜色透明值值
    OneMinusSrcColor//因子为1-源颜色
    OneMinusSrcAlpha//因子为1-源alpha
    OneMinusDstColor//因子为1-目标颜色
    OneMinusDstAlpha//因子为1-目标alpha
    例子:
    Blend SrcAlpha OneMinusSrcAlpha// Alpha混合,正常的透明度混合
    Blend OneMinusDstColor One         //柔和相加Soft Additive
    Blend One One                       // Additive相加 线性减淡
    Blend One OneMinusDstColor          // Soft Additive比较柔和的相加
    Blend DstColor Zero                 // Multiplicative乘法
    Blend DstColor SrcColor             // 2x Multiplicative2倍乘法

    BlendOp OP//对源和目标颜色进行其他操作,而不是默认的相加,op操作包括:
    Add //相加
    Sub    //源颜色减目标颜色
    RevSub//目标颜色减源颜色
    Min //使用2者较小的值
    Min //使用2者较大的值
    chen
    BlendOp Min
    Blend One One //组合变暗

    双面渲染:一般采用多个pass分别渲染正面和背面
    Cull Back|Front|Off   
    Cull Back默认的背对相机图元不渲染
    Cull Front朝向相机图元不渲染,只显示背面
    Cull Off关闭剔除功能 全部渲染 性能低,但是可以实现比如看见物体内部结构。
    不透明物体有深度测试,先渲前后没有关系,但是先渲染近的效率会更高,因为远的会被深度测试自动剔除不用渲染。没办法保证远近?
    透明物体一般要先渲远的,再渲近的才能保证视觉顺序正确。

    SubShader的Tag{}标签类型:
    Queue:渲染顺序,保证渲染顺序小的先渲  大的后渲,在quene一样大的时候无法保证渲染顺序。同一批renderQueue值的材质一起渲,渲完后统一进行深度填入,第二批再取新值进行渲染。
    RenderType:Unity可以运行时替换符合特定RenderType的所有Shader,着色器分类 也可以自定义。
    ForceNoShadowCasting:值为”true”时,表示不接受阴影。 
    IgnoreProjector:值为”true”时,表示不接受Projector组件的投影。常用语半透明物体
    DisableBatching:是否对subshader进行批处理,当shader中需要对顶点进行偏移的时候,该项设置为true
    CanUseSpriteAtlas:当该subshader用于sprite时 该标签设为false
    PreviewType:指明材质面包怎么预览材质 比如 “PreviewType”=“Plane”

    LightMode : 渲染路径 ,pass的标签类型

    渲染队列:“Queue”=“Transparent”
    Background:1000  //该声明的物体最先被渲染
    Geometry:2000    //默认的不透明物体使用的渲染队列
    AlphaTest:2450    //透明度测试,默认不透明物体渲染完后就渲染该物体
    Transparent:3000//透明物体,在Geometry和AlphaTest后渲染,保证不透明物体渲染完了再渲染透明的。
    Overlay:4000    //该队列用来实现叠加效果,该物体会在最后被渲染。

    RenderType:
    Opaque:绝大部分不透明的物体都使用这个; 
    Transparent:绝大部分透明的物体、包括粒子特效都使用这个; 
    Background:天空盒都使用这个; 
    Overlay:GUI、镜头光晕都使用这个

    渲染路径
    Tag{   “LightMode” = “ForwardBase”}//为每个pass指定渲染路径
    LightMode包括:
    Always:所有渲染路径该pass都会渲染,但不计算光照
    ForwardBase:前向渲染,该pass会计算环境光,最重要的平行光,逐顶点光和 Lightmaps
    ForwardAdd:前向渲染,该pass会计算额外的逐像素光源,每个pass对应一个光源。光源多该pass会被多次调用 效率变低。
    Deferred:延时渲染,该Pass会渲染G-buffer
    ShadowCaster:把物体的深度信息渲染到阴影映射纹理或深度纹理中
    PrepassBase:遗留的延迟渲染,该pass会渲染法线和高光反射的指数部分、
    PrepassFinal:遗留的延迟渲染,该pass通过合并纹理 光照 自发光来渲染得到最后的颜色
    Vertex:遗留的顶点照明渲染

    1.前向渲染:包括ForwardBase类型渲染常用光照和ForwardAdd额外光照
    #pragma multicompile_fwdbase //ForwardBase中用来保证光照衰减等参数正确赋值。
    #pragma multicompile_fwdadd //ForwardAdd中用来保证可以访问到正确的光照变量.
    #pragma multicompile_fwdadd_fullshadows //ForwardAdd中用来计算阴影效果
    USING_DIRECTIONAL_LIGHT//平行光的宏定义
    _LightColor0    //该pass的桌像素光照颜色
    _WorldSpaceLightPos0    //获取平行光光源方向,或者点光源的光源位置
    _LightMatrix0            //世界空间到光源空间(光源位置为坐标原点的坐标系)的变换矩阵
    _LightTexture0            //光照衰减纹理

    tips:光源的RendeMode参数设置为Important unity会自动采用像素光源,如果不重要就是顶点光源。还有qualitysetting里边的PixelLIghtCount,超过这个数也会用顶点光照
    光照衰减
    float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
    fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;//UNITY_ATTEN_CHANNEL获得衰减值所在的分量
    float shadow=SHADOW_ATTENUATION(i);//负值使用SHADOW_COORDS对相关纹理进行采样,返回值为阴影。关闭阴影的状态是等于1
    return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);//atten为衰减,shadow为阴影
    ------计算别人投到自己身上的阴影和衰减
    SHADOW_COORDS(n)//声明一个_ShadowCoord的阴影纹理坐标 ps输入坐标,n的值是声明TEXCOORD0-7坐标使用的个数
    TRANSFER_SHADOW(o);//用于在顶点着色器中计算上一步声明中的阴影纹理坐标 并传向ps阶段。
    float shadow=SHADOW_ATTENUATION(i);//负值使用SHADOW_COORDS对相关纹理进行采样,返回值为阴影。关闭阴影的状态是等于1
    UNITY_LIGHT_ATTENUATION(atten,v2f i, i.worldPos);//计算别人投影到身上的阴影#include “AutoLight.cginc” Unity会将光照衰减和阴影相乘后存在第一个参数中,并自动声明atten变量。第二个参数结构体包含SHADOW_COORDS,第三个参数世界空间坐标
    return fixed4((diffuse + specular) * atten, 1.0);//UNITY_LIGHT_ATTENUATION出的atten为衰减和阴影
    -----计算阴影投影到别人身上,自己的阴影
    V2F_SHADOW_CASTER//unity里边定义阴影投射需要定义的变量
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(0)//unity对顶点进行自动处理
    SHADOW_CASTER_FRAGMENT(i)//unity自动完成阴影投射部分,把结果输出到深度图和阴影映射纹理中
    –ds2的阴影采用的是屏幕后处理的方式去计算阴影,延迟渲染
    2.顶点照明渲染:过时的渲染方式。效果差。

    3.延迟渲染:通常2个pass,第一个pass计算哪些片元可见,第二个pass计算真实光照。


    1.反射方向:float3 reflect = reflect(o.lightDir, o.worldNormal);//入射光线,表面法线
    2.折射: float3 refract  = refract(normalize(o.lightDir), normalize(o.worldNormal), _RefractRatio);//入射光线单位向量,表面法线单位向量,介质比

    3.镜子效果:使用相机的RenderTexture来设置渲染纹理。o.uv.x = 1 - o.uv.x;坐标需要翻转一下。
    4.玻璃效果:反射和折射使用cubemap进行采样 是天空盒的cubemap,然后反射需要采样的是周围环境的光照和纹理。
    GrabPass { “_RefractionTex” }//会把屏幕输出到_RefractionTex的texture中, _RefractionTex_TexelSize ,要先声明该变量 就可以可以得到该纹理纹素大小,例如255*255的纹素是(1/255,1/255) 值是:  Vector4(1 / width, 1 / height, width, height);
    GrabPass{} //然后用_GrabTexture直接访问屏幕图像,但是这样效率比较低,推荐要上面需要声明的方法。
    o.scrPos = ComputeGrabScreenPos(o.pos);//得到对应被抓取的屏幕图像的采样坐标
    反射和折射需要显示环境的效果,所以需要对环境的cubemap进行采样。先用反射和折射的公式计算出光线,然后对环境贴图进行采样texCUBE(_Cubemap, i.worldRefl).rgb就可以得到具体效果了。
    反射skybox 3d采样,折射屏幕抓取图像2d采样。

    时间变量
    _Time:float4     //t是自该场景加载开始所经过的时间,4个分量是(t/20,t,2t,3t)
    _SinTime:float4    //t是时间的正玄弦值,四个分量的值分别是(t/8,t/4,t/2,t)
    _CosTime:float4    //t是时间的余玄弦值,四个分量的值分别是(t/8,t/4,t/2,t)
    unity_DeltaTime:float4// dt是时间增量,4个分量分别是(dt,1/dt,smoothDt,1/smoothDt)
    序列帧动画:时间去控制uv坐标映射转换。uv坐标的xy是顶点坐标,映射到小格子里边,和UItexture的xy和宽高不一样。
    背景偏移动画:时间控制uv坐标偏移。
    水流动画:通过时间和正弦函数去控制顶点偏移,通过时间控制uv移动。设置DisableBatching=true
    广告牌BillBoarding:根据视觉方向来旋转被纹理着色的多边形。顶点动画


    屏幕后处理
    void OnRenderImage(RenderTexture src, RenderTexture dest){}//全部渲染完后将调用,屏幕纹理存在src上,用来计算后通过dest返回,添加[ImageEffectOpaque]属性,可使不透明物体(AlphaTest)被渲染完后立即调用.
    Graphics.Blit(src, dest);//直接copy纹理。src是屏幕当前或上一步渲染的纹理,dest是目标纹理
    Graphics.Blit(src, dest, material,pass=-1);//将把src传到shader的material的_MainTex纹理。经过material(shader)的处理后输出到dest渲染到屏幕.pass默认是-1会调用所有pass,否则只调用给定顺序的pass。指定pass渲染很重要。

    基于颜色变化的边缘检测:Sobel卷积算法,对边缘点进行采样计算 和特定矩阵卷积相乘。
    高斯模糊:多次采样纹理混合 消耗较大
    Bloom效果:把较亮区域提取出来进行高斯模糊 模拟扩散效果,然后再与原纹理混合。
    运动模糊:将上一帧的屏幕图像存到renderTexture中,然后执行Graphics.Blit(src, renderTexture, material),shader将开启混合Blend SrcAlpha OneMinusSrcAlpha把src纹理和目标缓冲纹理renderTexture进行混合,然后再Blit输出到dst进行渲染。就得到了运动模糊效果。


    深度和法线纹理
    float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
    _CameraDepthNormalsTexture //unity中调用camera.depthTextureMode=DepthTextureMode.Depth/DepthNormal;这句话后可以通过该变量访问深度纹理或者深度和法线纹理,project空间
    float _depth=SAMPLE_DEPTH_TEXTURE(tex,uv)//对深度纹理进行采样,返回project空间下非线性深度值。和tex2D类似 只是封装了平台。自动将NDC坐标下的深度映射(0,1)需要转换到(-1,1)veiw空间去计算
    _depth 是非线性深度值
    LinearEyeDepth(_depth)负责把深度纹理的采样结果转换到视角view空间下的线性深度值 转换到nearclip到farclip范围
    Linear01Depth(_depth)则会返回一个范围在0,1的线性深度值,转换到0-1的范围。

    half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv);
    half2 centerNormal = DecodeViewNormalStereo(sample1);//center.xy存的法线的映射值,EncodeViewNormalStereo
    float centerDepth = DecodeFloatRG(center.zw);//zw深度,EncodeFloatRG
    DecodeDepthNormal(sample1,out centerNormal,out centerDepth)//获取采样的法线和深度

    Camera.worldToCameraMatrix //世界转相机矩阵 world2view
    Camera.cameraToWorldMatrix //相机转世界矩阵
    Camera.projectionMatrix //get投影矩阵viewToproject 视角空间到2维投影空间矩阵,set自定义的投影矩阵。如果你改变这个矩阵,相机的渲染不再基于它的fieldOfView更新,直到调用ResetProjectionMatrix
    默认把view2project矩阵叫成project矩阵,默认把World2view矩阵叫做view矩阵。比如ViewProject就是world 2 project矩阵
    全局雾效
    深度雾效:通过每个顶点的深度值计算出该点到摄像机的距离d,然后把距离d进行参与公式计算得到雾效图(远的雾浓 rgb值大,近的雾淡 rgb值小),再把原图和雾效图进行混合。一般用线性,指数,指数平方公式,ds采用指数平方。
    地面雾效:通过深度值和摄像机的方向向量计算该点到摄像机的偏移量,再加上摄像机的位置得到该顶点在世界空间中的坐标,然后把该坐标的y值参与雾效计算。如果用坐标z参与计算和深度雾类似。
    #pragma multi_compile_fog

    基于法线的边缘检测:防止阴影等信息干扰检测,判断临近的4个点的法线和深度值是否是近似,如果差距过大则是边缘roberts算法。(屏幕后处理)

    渲染轮廓线:基于法线方向扩散的边缘检测,第一个pass对顶点进行法线方向扩散渲染,第二个pass用真实渲染实际光照,覆盖第一次,对扩散的顶点未被覆盖的像素就产生了轮廓效果。(模型轮廓)
    主角遮挡轮廓线渲染:第一个pass 渲染Ztest greater不写深度,第二个pass ztest less正常渲染颜色。这里要注意:要想主角材质之间不渲染相互遮挡,所有RenderQueue必须设成相同,才能保证在同一批里边渲染 深度值是一样的,如果先后渲染顺序不同,深度值已经被写入过,就会出现内部互相遮挡渲染。

    噪声
    消融效果:怪物消失渐散的效果,把某个像素值小于阈值的裁剪掉,阈值附近的值用burncolor进行混合。阴影的pass里边算阴影时也把该项给clip掉,这样阴影就动态变化了//clip(burn.r - _BurnAmount);
    水面扰动效果:用时间去控制偏移距离,然后对该顶点的uv偏移两点的法线平均值来代替该点的法线值。水面=反射+折射+绕动,波浪效果要用正弦函数偏移顶点、
    ds水内的扰动UV=uv+0.03*offset, offset通过时间和噪声图计算,这样会产生顶点和旁边点的扰动0.03倍距离左右。
    float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);            
    / Get the normal in tangent space
    fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
    fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
    fixed3 bump = normalize(bump1 + bump2);
    全局(动态)雾效:通过时间控制噪声纹理的偏移距离,然后根据噪声颜色值来参与计算雾效浓度,然后计算雾效,就有了流动和淡浓的效果。

    线性空间:颜色的正常编码比如0,0.5,1编码到0,0.5,1.
    伽马空间:颜色编码存储在伽马空间,对颜色值一般进行Power(col,0.45)计算,比如0,0.22,1编码到0,0.5,1.把更多的颜色值用来存储眼睛容易辨别的较暗局域。该转换会使颜色看起来变亮了、我们的大多数图片颜色值存储在该空间。
    伽马矫正:指将线性空间衍射到伽马空间。2.2*0.45=1
    CRT显示伽马:由于历史和巧合,屏幕对颜色进行输出的时候会进行显示伽马操作,计算Power(col,2.2),所以输出会比原图变暗。power有很多好处,比如可以使线性硬边缘软化,看起来更柔和。
    Unity ColorSpace Setting:不支持移动平台,所以我们一般都选择Gamma Space
    Gamma Space:实际上就是“放任模式”,不会对shader的输入进行任何处理,即使输入可能是非线性的;也不会对输出像素进行任何处理,这意味着输出的像素会经过显示器的display gamma转换后得到非预期的亮度,通常表现为整个场景会比较昏暗。
    Linear Space:Unity会背地里把输入纹理设置为sRGB模式,这种模式下硬件在对纹理进行采样时会自动将其转换到线性空间中.也会设置一个sRGB格式的buffer,此时GPU会在shader写入color buffer前自动进行伽马校正再次转换到伽马空间。
    流程Gamma Space:原光照-伽马编码(图片存储的格式编码,变亮)-Unity不处理-CRT显示伽马(变暗)-输出原光照颜色(相比编码后的原图会变暗)
    流程Linear Space:Unity光照-Unity自动转换线性空间计算 然后再伽马矫正转换到伽马空间存储在缓冲中-CRT显示伽马-输出颜色正确,如果用gamma的话 计算完光照不会进行伽马矫正 所以经过CRT会变暗。linear的线性转换指图片采样 ,光照计算两者一样。
    ds2:图片格式在伽马空间,选择ColorSpace为Gamma,进行颜色采样时先进行float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 ); 伽马矫正,在线性空间计算光照屏幕后处理,最后在matcopy输出的时候再return fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);还原回去。最后经过CRT的伽马矫正输出。

    HDR高动态范围渲染:正常光照图像范围是0-1,但是实际光照可能超过1 ,HDR使用16位图进行渲染 使颜色值包括在更大的范围内计算 最后再通过Tonemapping衍射到0-1范围 ,使颜色更加真实。
        当camera上的hdr被激活时,OnRenderImage的source图会存储成16位的ARGBHalf格式,然后计算Bloom和Tonemapping生成的采样图也可以定义成16位格式,那么效果会更好。HDR对美术和硬件性能要求高,所以手游一般很少用,难达到那么好的效果。
    Tonemapping 色调映射:HDR使效果看起来更柔和,防止过亮和过暗, tonemapping根据当前的场景推算出场景的平均亮度,再根据这个平均亮度选取一个合适的亮度域,再将整个场景映射到这个亮度域得到正确的结果。用该公式把采用的hdr颜色转换到ldr范围值内。
        如果没有HDR也能简单的柔和。只有在相机启用HDR时才更有意义。这个特效需要显卡拥有像素着色器(2.0)或者OpenGL ES 2.0,但是我们采用的是简单的伽马参数控制的色调映射。同时在该算法里边计算亮度,饱和度和锐化修改。简单的tonemapping算法有很多种实现方式。
        算法千差万别,但是目的是一样的,就是通过公式,把输入值HDR颜色或正常颜色映射到0-1范围内去显示,可以画出公式曲线理解都在0-1范围内,并且根据曝光系数调整曲线 使大部分颜色输出到看起来更加接近该亮度的区域。
    Bloom :可通过HDR实现效果更佳,bloom本身可通过模糊实现,开启HDR后高斯采样图我们可以使用RenderTextureFormat.ARGBHalf格式的纹理 采用了更高精度的纹理后,再通过算法效果会更好。
    数据点云模型的作用:有时候求比如高斯模糊的时候,可以用工具按规则生成周围随机的一些偏移点,数据点云的模型效果有时候比直接计算效果好,如果提前知道模型和计算方式的话。

    延迟渲染:所谓延迟渲染只是预先把所有灯光的颜色算好后,叠加到主光上参与光照计算,比如ds会把预先把点光源光照计算出来,通过在光源处Camera.RenderToCubemap(rt)渲染周围全景图,用该全景图去计算光照,全部点光源计算完了 再去叠加计算主光的光照。而不用像延迟渲染那样每个灯光要渲染计算所有场景光照。

    全局光照GI:计算的是光的二次反射,有两种算法,漫反射为主:一般从该点法线方向按一定规则随机散射几条射线出去一定距离,只要计算到该射线点和该采样点相交的(该种射线方向可只采样一次对比),就把交点算入该点的颜色影响范围内,把几条射线的交点颜色按照距离衰减叠加到源颜色 就可以了。
        另一种是高光为主(ds)方法,从视线的反射方向方向射出几条射线,射出去计算交点和SSR一样,分8段计算深度对比,每段都有一定随机偏移,计算交点后出uv采样叠加到源颜色上。如果所有射线采样点深度都比实际采样点深度大,说明该方向没有对该点颜色有贡献。

    环境遮蔽AO:传统的AO实现方法通常是使用光线跟踪的来完成。通过从每个可视点发出若干条光线 ,并与全局的场景进行相交测试来得到相应点的AO值。用于计算某点除了光源以为被其他的物体遮挡导致的软阴影。所以开启会变暗。
    屏幕空间环境光遮蔽SSAO:屏幕后处理实现,根据点云模型(或者random采样),取周围规则随机点进行深度采样,然后将该点深度和周围采样点的深度进行对比,正常模型是对周围法线求半球积分 半球积分需要更多的采样次数效果才会比球积分好,为效率我们求得是对视角方向整球积分,半球积分需要对cos积分后进行二次积分,不然会导致半球面的同一个角度的圆圈上的点算出来的值是一样的,结果不正确。
        我们简化为向量P(当前点到周围采样点uv偏移对应的viewpos的方向向量)与viewdir的点乘(cos是sin的积分,全球积分),该SSAO各个角度看到的效果强弱不一样,当该点为凸点 周围的采样深度大于该点,当该点为凹点时周围深度小于该点,点乘后大于0为凹,小于0为凸,根据深度差与ViewDir的点乘就知道周围点对该点的AO贡献值。由于我们blend one one,而且是整球积分点乘后的AO值-1,1,大于0表示凹处被遮挡,小于0表示凸出不遮挡,转换到0,1后,减去0.5可只显示遮挡处,但是会导致夹角处半圆覆盖面积不够采样失真,所以我们用原值,整体场景会变亮很多,再通过其他方式把暗度调下
        SSAOPro采用的是向量P(当前点到周围采样点uv偏移对应的wordpos的方向向量)与当前点normal的点乘的半球积分max(0.0, dot(norm, v))(1/(1+d)),d是该点到采样点的距离,该SSAO强弱与视角无关,采样次数少的时候全球积分效果好,采样很多或者模型精度高的话半球积分效果好,目前手机上只能用全球积分可以达到效果。SSAO优化一个采用UIMASK裁剪被UI遮挡的地方,另外一个用深度裁剪 远处的地方不进行SSAO可以提高效率。另外对SSAO计算后的图进行Blur后再叠加,效果会更好但是指令数会增加。
        SSAO消耗=blur指令数
    blur像素+SSAO指令数*SSAO像素数量,把shader编译到汇编指令,数一下有多少条指令 可做参考,只选单平台可方便看。SSAOPro140个指令非常高了,像我们SSAO才50个。
        常用优化方法:降低分辨率,减少采样数,UIMask,隔像素计算。
    高斯模糊Blur:高斯公式计算了周围所有点对该点的叠加的影响值,越远影响越小,采样范围越大效果越正确。我们一般取周围2圈的附近代表点进行模拟,因为实际方式去采样一圈8个点效率太低、
        取该点周围1个像素和2个像素距离的四个点,再乘以一个倍数,然后再将多张图按高斯规律叠加,也就是根据高斯算法离中心点越近的所占该点像素值比率越大,因为高斯公式是2维的,所以正常采用2个pass分别对水平竖直方向一维采样模拟降维,但是效果要求不高的时候我们也可以用一个pass取模拟近似效果,9个点:float weight3 = {0.4026, 0.2442/2, 0.0545/2}保证所有采样点叠加在一起的权重和为1。如果不按高斯权重进行叠加颜色会得到其他类似模糊效果 
        另外cpu阶段传过来采样的图最好先进行降分辨率(参数控制分辨率降低倍数)处理再采样提高效率。然后CPU还要多次采样混合效果才好,但效率低。

    Bloom:把较亮区域提取出来进行高斯模糊 模拟扩散效果,然后再与原纹理混合。bloom的高斯模糊需要用精确的二维采样效果才能好。
    运动模糊Blur:指的的是物体运动的拖影模糊,但赛车拖影模糊一般用环境径向模糊,物体运动模糊可计算上一帧和当前帧进行混合采样 和 相机运动模糊可求取相机运动方向在uv里边进行偏移后混合采样。

    Dof景深DepthOfFeild:指目标渲染附近的一定深度范围内显示清晰图片,其他范围呈现模糊效果。游戏中一般用一个Dis,深度大于Dis的点经模糊处理 越远越模糊 形成景深效果。ds的方式是用DrawMesh 第一个pass把深度大于Dis的点模糊渲染到tex中,第二个pass去把上帧绘制的tex直接加到currentColor中。其实我们也可以采用一个pass 把深度大于DIs的直接模糊返回,小于的返回原值就好了。
    自发光:一般是用做边缘发光,正常就是在shader内部自定义光照参数,比如UI上显示的模型不经过光照可以自定义光照。
    抗锯齿AA:FXAA快速近似抗锯齿是屏幕后处理常用方法,取uv当前顶点和左上,左下,右上,右下的半个uv像素偏移点,求取5个点的亮度值(dot(col,float3(0.22,0.707,0.071))或其他公式Luminance()),然后分别求出5个点的最小和最大亮度值,如果最大亮度与最小亮度值的差小于某个阀值(比如0.5或者计算)判定为非边缘点直接返回当前颜色,
        然后对uv加减半个像素偏移(*Dir)采样出col0,col1,再对uv加减2个像素偏移(*Dir)采样出col2,col3(其中像素偏移值要和两对角线亮度之差的差and和组成的向量通过公式计算单位向量Dir后相乘取近似),然后把前2个颜色合并(col0/2+col1/2)得ColA如果该值亮度不在之前计算的最小值与最大值之间返回该值,否则把四个颜色进行合并来返回、
        求像素偏移倍数Dir是FXAA关键,该向量相乘后得到的uv点可以更加趋向边缘,比如四个采样点,左边和右边色差大,乘以Dir后取样的左上右下点会趋向上下方向,使得边缘更加真实。抗锯齿采样其实就是轻微的模糊处理,只是采样范围小点,亮度变化不大的不模糊就好了。
    多重采样抗锯齿MSAA:只对画面中物体的边缘进行放大、混合的抗锯操作,因为边缘是锯齿最明显的地方(注意不是所有的边缘)。提取边缘,主要是结合深度技术。MSAA是种硬件AA。我们一般说的4x、8x,就是放大倍数,放得越大,供混合的采样越充份,效果越好,但是处理速度也就越慢
    草随风动:
    水的泡沫:
    边缘光RimLight:有两种方法实现,传统的算法是求视角方向和顶点法向量,如果两者垂直说明该顶点边缘光,加上边缘光颜色就好,该种效果所有轮廓线边缘都会渲出。ds的办法是屏幕后处理用法线贴图的xy(节省效率可不用decodenormal)对一张masktexture进行采样,采样后的颜色乘以边缘光颜色返回即可。
           原理在于mask贴图左下角为原点,向上y变大,向右x变大(0-1),对于模型顶点的法向量也是向上y为正 向下y为负,向右x变大(-1,1),其未经decode的法线贴图xy值(0-1)方向刚好对应了mask采样方向,那么对应maxk的上半部分赋一个凹圆颜色值,其他地方为0值,那么采样的叠加的结果就是所有模型的上边缘会有边缘光因为normal.y>0采样、
           ds里边还采用了stencil开关开材质是否开启边缘光。然后相机离主角位置远近来动态设置边缘光颜色倍数,越远设置越大看起来边缘光更多。

    橡胶皮肤材质SSS次表面散射:光线进入材质后进入材质内部经反射折射影响周围点的亮度呈现橡胶透明效果。ds的ssss优化过用模拟点来找到周围的点的采样的亮度值再来计算这些点对该点的亮度影响值,然后叠加,影响值一般与距离平方成反比,越近影响越大。

    体积光:有三种实现方式,第一种是对顶点光源反方向进行拉丝偏移,第二种是对亮度较大的顶点uv进行光源反方向模糊偏移径向模糊处理。第三种是光线追踪、
    径向模糊:可实现赛车的运动周围环境模糊和太阳光光线射出的效果,太阳光将亮度高的图分离出来降分辨率进行模糊然后和原图叠加。从屏幕中心点或者太阳位置向四周各自方向进行uv偏移实现太阳光的体积光。先求中心点到当前uv点的方向dir(包含距离和方向信息),最后对该点进行多次偏移采样叠加,每次偏移uvoffset=diri_Dis就可以实现该效果。为了优化效率可以把采样模糊的图分辨率降低,模糊后和原图混合。

    水下:绘制Quad,通过blend 混合,判断世界空间该点的高度是否小于水面高度,小于水面高度部分进行扰动效果加雾效处理,当摄像机高于水面不渲染,低于渲染,处于水面之间的和水进行lerp操作。
          更加逼真的水下效果需要绘制水面的反射水滴和折射天空,水中更加体现距离差的雾效和水底的焦散效果。还有最基本的扰动。
          水下渲染最生硬的地方是当相机处于水之间的时候,那么水面上下可能都只在屏幕内,水下侧可以计算玩家位置上方水的屏幕y坐标向下插值blend减弱水效果,但是水上侧和场景融合就比较生硬 只能做相机碰撞解决 逻辑保证相机只能在水下或者水上。

    下雨:用数张雨滴的法线贴图,轮流采样作为纹理的法线方向就有了下雨效果,法线贴图内含有数个雨滴各种效果。通过drawmesh绘制quad。雨周围的反射可以用cubemap。一般小场景的反射用cubemap采样,大场景只能再计算一次反射效果了。
          float2 rainuv=frac(worldpos.xz*0.25);//用世界坐标的每隔四米采样一次rain图片。frac取小数部分。然后blend one one
    屏幕反射:绘制Quad,通过blend one one,从视角方向计算顶点的反射光线,首先对当前点顶点坐标通过噪声图随机偏移一点使之产生模糊效果(因为该算法得不到精确的镜面反射效果),然后从改点的反射光线方向射出一条射线比如for循环10次 每次在该反射方向加上一个偏移值进行对比计算,
          如果加偏移后的点的深度大于该点对应屏幕(实际背景)的深度值,说明射线已经射到模型内部了,那么取出该uv给原顶点用,同事对skytexture进行反射采样和射线反射进行叠加。其中射线的每次偏移量根据摄像机角度计算,远处偏移大,垂直角度偏移小。由于射线采样深度次数多,所以效率不高。
    垂直同步:显示器上的所有图像都是一线一线的扫描上去的,垂直同步就是等上一帧的线扫完了再画下一帧 ·防止画面撕裂,,高配置硬件开启垂直同步能使得游戏进程和显示器刷新率同步,使得画面平滑,使得画面稳定,低配置关闭垂直同步可以一定程度上减小显卡的负担,让画面更加流畅一些。

    噪音的作用:

    自阴影HorizonMapping:1.通过法线方向与光源方向是否一致可以判断该点是否是阴影点,2为解决自身遮挡的问题,计算每一个点的仰角(当前点向某一个方向射出的射线,使其恰好遮挡该点,没有遮挡则是0,用递归偏移去计算)然后把信息存在贴图里边,光照的时候就只计算该点的仰角和光源角度,光源仰角大于该点我们就照亮该点。
                          3.该方法只适合光源在某一个平面运动,如果光源在360空间运动,需要更麻烦的插值计算或者实时计算。

    OnPreCull () // 把这个赋给相机.每帧在相机裁剪场景前调用,裁剪决定哪些物体可见 会被渲染。
    OnPreRender ()// 把这个赋给相机.每帧在相机渲染场景之前被调用
    OnPostRender () // 把这个赋给相机.每帧在相机完成场景渲染之后被调用。
    void OnRenderImage(RenderTexture src, RenderTexture dest){}//屏幕后处理函数
    Graphics.Blit (src, dest,mat,pass);//屏幕后处理 src当前屏幕纹理
    Graphics.DrawMesh(aMesh, Vector3.zero, Quaternion.identity, aMaterial, 0);//给定材质绘制网格模型 只能在Update里边调用生效。

    mat.SetPass(0);//给当前渲染帧设置shader渲染,为渲染激活给定的pass 设置后如果blit没有使用材质也会使用该pass,Blit and DrawMeshNow.
    Graphics.SetRenderTarget(rtex)//设置当前的渲染纹理,如果设置该项后,DrawMeshNow再绘制的图像将保存在该纹理中,然后blit到dst就可以单独渲染显示了。这是渲染总的图形目标不同于相机视角的渲染目标。blit完后也需要重新设置SetRenderTarget。
    Graphics.SetRenderTarget(colorbuff,depthbuff)//把渲染好的纹理和深度设置好 再绘制DrawMeshNow 才会有叠加效果。一般情况下DrawMeshNow的rendertarget设置成相同的一个。
    Graphics.DrawMeshNow(aMesh, Vector3.zero, Quaternion.identity);//只可渲染静态物体,在相机上,用上一次(最近一次)处理的材质进行绘制,一般前边加mat.SetPass(1);只用该pass进行渲染。matrix 可以对mesh进行矩阵变换。一定要SetRenderTarget才会绘制在对应的buffer上
    Graphics 是unity的底层API,Camera是Unity的上层API
    手动绘制动态物体可用:CommandBuffer渲染,buff.DrawRender(render,mat);Graphics.ExcuteCommandBuffer(buff);该API会消耗2个drawcall,所以静态物体尽量用DrawMeshNow.

    Rendertexture rt=new Rendertexture(…)//w.h.depthbuffer,对于要渲染深度的rt,depthbuffer要设置至少16位。
    camera.targetTexture=rt //设置相机的rt 相机会把渲染内容设置到rt上,然后该相机game窗口不可见
    camera.clearFlags=CameraFlags.SolidColor;//记得设置的默认颜色
    camera.SetTargetBuffers(rt.colorBuffer,rt.depthBuffer)//设置当前相机的渲染目标RenderTarget设置后要么EncodeDepth
    camera.RenderWithShader(shader,“RenderType”)//找物体shader有"RenderType"字段 和shader有"RenderType"字段相等的shader的subshader进行替换渲染。每帧调用才会生效。也可以自定义如"Tag"=“MainChar”
    绘制到rt后再和其他相机rt混合然后blit输出,在该功能下的相机深度和颜色都是新的只满足条件的物体的。如果在场景中,多个camera需要把深度和颜色存起来,然后设进去。 
    用camera渲染和其他camera混合渲染的时候camera.clearFlags=CameraFlags.Nothing不然如果设置SolidColor会把以前的color 给Clear掉

    ds渲染colortexture和depthtexture,colortexture的colorbuffer用来存颜色和粗糙度,depthbuffer用来存系统深度,depthtexture的colorbuffer用来存深度和法线。
    RenderTexture的colorbuffer和depthbuffer的理解:colorbuffer用来存camera.SetTargetBuffers后的颜色值,其中的值可以自己定义 可以是颜色也可以是深度和法向量 然后自己用来计算 对应RenderTexture的rgba值,depthbuffer是系统用来写depth用的,还包含了stencil等信息,只决定前后关系,不会给我们参与计算。
    所以在SetTargetBuffers为多个RT时,shader里边的frag输出也是输出多个color值。
    Camera.SetReplacementShader(shader,tag)//调用这个函数之后,相机将使用替换的shader来渲染它的视图,一直替换渲染。当replacementTag为空时会替换视口中所有物体的shader。
    Camera.ResetReplacementShader()//从相机上移除shader替换。

    要分清楚camera的SetTargetBuffers和Graphics的SetRenderTarget。如果是相机渲染到某个纹理用camera的,但是如果要设置图形输出的纹理设置Graphics,不要搞混。

    ds渲染流程:shader写好后在colorbuff.cg里边运算和输出,屏幕后处理会根据平台和设置使用替换的shader如MRT.shader去渲染,然后MRT.cg去输出颜色。
    模型材质上使用的shader并不是最终渲染使用的shader,根据设置在precull阶段 会使用替换shader去渲染,比如流程用前向渲染和支持MRT的Shader渲染和低版本的DiffuseRoughness渲染。然后屏幕后处理阶段用替换shader去实现一些特殊效果。
    同一个shader的pass越多那么提交GPU绘制的批次就越多 也就是drawcall越多。所以主角轮廓线渲染怪物深度时要当心。 
    屏幕水波纹扰动效果:1.把上一帧的屏幕图像传给水波纹和maintex参与计算扰动,然后缩放水波纹大小就形成了水波纹大小。技能特效的扰动也用该方法。

    阴影:1.先用阴影相机变换到光源的位置渲染深度,将深度存储在自定义深度纹理中。由于深度z范围从nearclip到farclip,将z除以远裁剪值,以把深度转换到0-1空间进行存储。
          2.用正常相机渲染顶点 将顶点转换到阴影相机空间 求得深度值z,再除以farclip转换到0-1范围内 再和上面采样的depth做比较 判断该点是否是阴影。阴影相机的targetTexture=rendertexture一定要设置,保证获取阴影相机proj矩阵是按照阴影相机宽高渲染,不然project矩阵宽高会不对。
    阴影锯齿解决:      
          1.由于场景阴影分辨率不足,阴影计算锯齿会比较严重,为了使阴影效果更逼真 没有锯齿,可以采用周围四次采样深度值进行插值计算。普通场景可采用该方式提高效果。
          5.对于主角或一些较高要求的物件,可以采用另外的阴影相机去渲染一张高分辨率的深度图计算简单计算阴影,这样就不会有锯齿了,最多可能会显得比较自然硬。
          6.可以在normal方向或者光照方向偏移一点点的bias来减少锯齿,不能直接对z进行偏移。另外在画阴影的时候参与光照计算效果会更好,如果直接画阴影可能会
          4.渲染RT的new Rendertexture(…)的时候 一定要设置深度至少16位才有效。第三位参数,不然深度可能没写进去。
          5.一般采用计算光照渲染的时候乘以阴影系数的效果会比直接画阴影效果好
          6.在设置相机的buffers和RenderWithShader后必须调用Graphics.SetRenderTarget,否则GPU上后续pass会失去原rt的信息。因为camera.SetTargetBuffers会自动设置gpu的SetRenderTarget,但是调用完后会清除,所以必须重设。
          7.normal等信息最好来自depthTex采样,而不是vertex传过来,因为depthTex已经处理过了是否有bump等情况,会方便点。
          8.RenderWithShader计算的参数来自于原材质的shader,需要声明原变量就可以用,但是在计算主角的时候 我们直接采样RT 就是用pro_uv去采样参与计算、DiffuseRT经ColorBuffer.cg计算里边已经包含了粗糙度等基本信息 方便后续计算。
          9.关于阴影系数,正常就是0和1,对于多次采样有过度的可能有插值,对于单次采样的0-1表现可能会生硬,一般采用该系数乘以一个值转换到0-0.n或者0.n到1
          10.软阴影判断阴影处的强度,可以通过判定该点和周围采样的深度差值,如果完全处于阴影点,周围的深度和该点一样,如果处于阴影边缘点 周围点的深度和该点就会有较大差值。
    场景普通阴影(低分辨率)等级分级:
        0级:没有阴影处理
        1-级:采样1次深度进行简单的深度对比。(当相机分辨率足够的时候 用这个最精确,主角阴影绘制)
        1级:y方向采样2次深度进行对比插值,会有一点点边缘软阴影。
        2级:2个方向进行4次采样,再进行2次lerp,软阴影效果会更好一点。
        3级:周围9个点进行采样和多次插值,软阴影效果可以很棒了。
        4级:周围8或12个点按一定规则去采样,再和rot随机图进行矩阵乘法组合随机转向,保证相连点不采样到同一点,最后叠加的效果边缘模糊感更强。rot采样图很重要。ds暂时没用
        float depth = DecodeDepth(XY_Depth.xy)-newdepth>0;// depth=0 or 1

    常用矩阵变换
    Camera.worldToCameraMatrix //世界转相机矩阵 world2view   右手坐标系
    Camera.cameraToWorldMatrix //相机转世界矩阵
    Camera.projectionMatrix //get投影矩阵viewToproject 视角空间到2维投影空间矩阵
    transform.TransformDirection (Vector3.forward);//从自身坐标到世界坐标变换方向。返回的向量与direction有同样的长度。
    transform.InverseTransformDirection(0, 0, 1);//变换方向从世界坐标到自身坐标  important
    transform.TransformPoint(2, 0, 0);//顶点变换位置从自身坐标到世界坐标
    transform.InverseTransformPoint(0, 0, 0);//顶点变换位置从世界坐标到自身坐标,c#里相机的世界空间转view空间用这个,不需要用矩阵计算。
    transform.worldToLocalMatrix//Unity的左手坐标系,相机可用这个从world转换到view空间,这个与Camera.worldToCameraMatrix(右手坐标系)的区别在于z轴方向反向。Camera.worldToCameraMatrix与shader的UNITY_MATRIX_MV相同,用这个计算需要反转z值。transform是unity左手系,camera是右手系。
    GL.GetGPUProjectionMatrix(camera.projectionMatrix,true)//从Camera的投影矩阵得到GPU(图形处理器)的投影矩阵(该函数在跨平台是有用)。view2project
    vec3=Matrix4x4.MultiplyPoint(vec3) //通过该矩阵把vec3点变换位置,从某一空间投影到另一个空间。
    vec3=Matrix4x4.MultiplyVector(vec3) //通过该矩阵把vec3向量变换方向
    Camera.RenderToCubemap(rt)//用相机渲染360全景图,其中rt要设置isCubeMap=true,isPowerOfTwo=true; 这时候修改camera的旋转是没用的,一般用RenderTexture去渲染比CubeMap消耗少、
    Camera.WorldToScreenPoint§//wolrd to 屏幕空间 左下角0,0 右上角width,height。 z就是深度了
    Camera.WorldToViewportPoint§//world to view空间 左下角0,0 右上角1,1

    平台和宏定义
    Direct3D里面是左上角为原点;
    OpenGL和OpenGL ES是左下角为原点。
    三维坐标转换为齐次坐标 顶点把w分量设为1,向量把w分量设为0
    SHADER_API_OPENGL - desktop OpenGL
    SHADER_API_D3D9 - Direct3D 9
    SHADER_API_D3D11 - desktop Direct3D 11
    SHADER_API_GLES - OpenGL ES 2.0 (desktop or mobile), use presence of SHADER_API_MOBILE to determine.
    SHADER_API_GLES3

    顶点转换:
    #if UNITY_UV_STARTS_AT_TOP//dx   这个不管用了 无法识别Shadow_API_Metal 苹果的新设备支持的平台
            proj.y*=-1;
    #endif
    不完全等价:
    #if SHADER_API_GLES||SHADER_API_GLES3
    //大部分安卓手机和部分ios手机,一般用该宏定义
    #else
            proj.y*=-1;    //一般用这个 
    #endif
    uv转换:
    #if SHADER_API_MOBILE
    #if UNITY_UV_STARTS_AT_TOP
    #else
            uv.y = 1-uv.y;
    #endif
    #else
    #if UNITY_UV_STARTS_AT_TOP
            uv.y = 1-uv.y;
    #endif
    #endif
    -------Unity预编译宏--------
    1.在pass里边#define USE_Dynamic_Fog 1   然后写多个pass分级, 然后在cg里边    #if USE_Dynamic_Fog==1  …  #endif,也可以只定义不取值。

    2.#pragma multi_compile LOW Mid High,编译了3个宏,该方式宏只能多选一,不能选多个,用 material.shaderKeywords = [“LOW”];或者material.EnableKeyword(“LOW”),那么Shader里边只能判断三个状态,三个中的其中一个生效,如果没有Enable一个宏或者Enable了多个宏,则默认第一个宏打开。
    注意的是如果编译单个宏就是打开和关闭状态则用:该方法就可以在代码里边通过打开关闭开关分级。
    #pragma multi_compile R_Off R_On 也需要分两个宏来区分,与#define情况不同。或者
    #pragma multi_compile __ R_Off   与上等效(注意空格)。 等同于#pragma shader_feature R_Off       //#if  R_Off    …    #endif  这个和我们自己定义#define AO_QUILITY  3效果差不多


    矩阵乘法和投影矩阵:
    MNA:代表A先经过N矩阵变换再经过M矩阵变换。从右往左看。
    矩阵变换的顺序一般是:先缩放,再旋转再平移,MtMrMsP
    缩放矩阵:左对角线值
    旋转矩阵:左3x3矩阵
    平移矩阵:第四列
    Matrix4x4 cameraviewToshadowview=shadowcamera.worldToCameraMatrix
    camera.cameraToWorldMatrix//从一个相机的view空间转换到另一个shadow相机的view空间
    一般情况下矩阵乘法结合在CPU里边做好,在GPU阶段一般不这么做,一是效率不好,另外一个是shader的矩阵直接相乘意思和上面的不一样。
    world和View之间的矩阵变换是线性的,但是一旦转到project空间就是非线性的,所以要除以w进行归一化。
    常用变量计算方法:
    float4 projectpos/=projectpos.w;//归一化 归一化后的NDC坐标范围在-1,1内,projectpos先用MVP矩阵把顶点转换到project空间。
    float2 uv=projectpos.xy*0.5f+0.5f;//project(需要归一化)坐标的xy就是uv坐标的衍射 屏幕图像的uv,uv坐标也可以在顶点阶段用unity的接口ComputeScreenPos()计算,然后在frag里边去归一化就可以用了。
    projectpos.z存的是深度缓存0-1 但是unity将其进行了矫正 是非线性的,会导致近处精度高 远处精度低,那么远处效果就不会好,我们ds的深度值用的是view空间的z值0-far。
    float depth=-viewpos.z/_ProjectionParams.w;//view坐标的z值存的就是深度值,除以farclip衍射到0-1范围存储,z(<0表示在前方)坐标需要乘以-1衍射深度。但是使用我们自定义的viewmatrix不需要,采用不同坐标系已经定好了方向。projet的z是深度缓存。
    渲染深度的时候要记得先把背景图设置成白色,或者把相机clearFlag设置SolidColor然后颜色设置成白色,保证没有被渲染的地方深度为1

    关于像素深度值还原坐标:(因为每个点都要矩阵转换,所以效率不高)
    float3 viewpos=mul(project_Inverse,float3(projectpos.x,projectpos.y,depth));//若depth=projectpos.z 表示把该模型的顶点还原view空间,取depth代表屏幕处于同一点显示的前后坐标点将project空间转换到view空间、一般用来背景图的深度值还原其具体坐标。
    我们的计算方式:(project空间还原知道深度的像素view 坐标点 )
    float3 farpos=float3(projectpos.xy,1);//该值代表project空间远裁决上和当前屏幕xy重合的最远坐标点。ps里边计算projectpos的时候如果用系统提供的矩阵一定要在dx下projectpos.y=1-projectpos.y我们自己的矩阵好像是有转换过的。
    Vector3 _FarCorner;//c#里边计算的相机的farclip右上方坐标点(x,y,z),其对应的左下方就是(-x,-y,z);这两个坐标的衍射刚好对应于project空间xy的(-1,1)之间。
    float3 viewfarpos=farpos*_FarCorner;//该式直接将farpos映射到view空间的远裁决上和当前屏幕xy重合的最远坐标点。
    float3 viewpos=viewfarpos*depth;//利用相似三角形原理,在屏幕上重合的两个点,在相机perspective视角下呈现相似三角形,远裁剪上的点(proj.z=1)和求得点(z=depth)是一条线,直接相乘可得坐标点。

    ViewPos的理解,以相机为坐标系原点的坐标,显示结果是屏幕坐标中间是(0,0),向上y变大 向下y变小,右边x变大,坐标x变小为负、不要直接与世界坐标空间的一些位置参与计算。normalize(ViewPos)为相机到点方向与点到相机视角方向反向相反。
    float4 wpos=mul(View2World,float4(vpos,1));//矩阵转换空间的时候一定要带上四维坐标,如果只是三维的mul(View2World,vpos)代表值进行了三维运算的旋转和缩放,没有计算平移。如果要用w参与转换一定要先保证矩阵转换信息里边和原始值的w正确,像我们的farcorner的w是没用的,所以转换后w是无效的。

    关于我们屏幕后处理的DrawMesh:
    Graphics.DrawMesh(Quad,Vector3.zero,Quaternion.identity,0)//默认情况如果MVP转换该mesh位置在相机原点。视锥范围外 被剔除。
    o.pos=float4(v.verter.x2,v.vertex.y2,0,1);//简化直接转换到project空间的近裁剪位置。ZTest Less
    o.pos=float4(v.verter.x2,v.vertex.y2,1,1);//简化直接转换到project空间的远裁剪位置。ZTest Greater
    所以根据以上两个pos转换的位置不同,ZTest 也要不同。Quad的坐标是-0.5,0.5 乘2直接到proj空间的-1,1.然后proj.z值0-1代表近裁剪到远裁剪,proj.w=1 代表顶点。

    float3 dir=mul((float3x3)Project,vdir);//vdir是float3的时候 矩阵必须转换成三维矩阵,否则可能有些平台会报错。或者把vdir转换为float4也行,点w=1,向量w=0、
    float4 dir=mul(Project,float4(vdir,0));//or

    在给shader设置参数的时候:
    mat.SetTexture("",_tex);//尽量用这个,只给该材质设置参数
    Shader.SetGlobalTexture("",_tex)//全局变量,在cpu阶段查找所有材质,有该变量就设置该参数。效率影响在cpu 应该不是gpu

    环境光贴图
    Diffuse Environment CubeMap :使用网格表面的法线去采样
    specular environment CubeMap :使用反射矢量去采样

    常用光照模型和向量计算方法:
    1.漫反射Lambert:float3 diff=max(0,dot(i.normal,i.lightDir));//lightDir是光源方向的单位向量,normal是法向量。C是光源颜色。反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比。取max防止颜色为负
    2.Half-Lambert:float3 diff=dot(i.normal,i.lightDir)0.5f+0.5f;//将颜色空间映射到0-1而不是采用lambert的截断,lamber模型会使没有光照的地方全黑,可使用环境光避免,HalfLambert会使没有光照的地方显示微光。大部分情况用该模型。
    2.高光反射Phong:float3 spec=pow(saturate(dot(reflectdir,viewdir)),_Gross);//视角方向与反射方向点乘截取后的gloss次方。gloss光泽度越大 亮点越小,求反射光用reflect注意光源方向和入射方向相反。
    4.Blinn-Phong:float3= pow(max(0,dot(worldNormal,halfDir)),_Gross); float3 halfDir=normalize(worldlightdir,ViewDir);  //对视角方向和光照方向相加再归一化得到halfDir作为参数,再来和法向量点乘 再power。高光看起来更亮更大一些,大部分情况用这个模型
    3.输出颜色:float4 col=(diff+spec+ambient)C
    3.光源方向:常说的光源(光照)方向一般指点到光源的方向,而不是入射光线的方向。_WorldSpaceLightPos0.xyz,.w=0表示平行光,.w=1表示点光源或聚光灯,当然不同引擎可能存的方向也不一样,但是unity是这样定义的。
    4.视角方向:float worldViewDir=normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); //unity API: UnityWorldSpaceViewDir(worldPos.xyz)一般指点到相机的视角方向,该公式在世界空间下。
    5.View空间视角放向:float3 viewdir=viewpos;//view空间的坐标值等于相机到该点的视角方向。
    5.反射方向:float3 reflect = reflect(o.lightDir, o.worldNormal);//入射光线(不是光源方向),表面法线,一般用来求采样cubemap或者天空盒采样的反射信息。场景中的实时反射一般采用单独渲染反射纹理, 把坐标进行变换后合并。
    6.视角光线的反射方向:float3 o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);//用视角方向的反向量 反求入射光线(反向),视角方向求视角光线的反射方向,用该反射方向去采样纹理。
    7.折射方向: float3 refract  = refract(normalize(o.lightDir), normalize(o.worldNormal), _RefractRatio);//入射光线单位向量,表面法线单位向量,介质比
    8.视角光线的折射方向:float3  worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);//用视角方向的反向量 反求折射光线(反向)
    9.菲涅尔反射:float fresnel=pow(1-saturate(dot(worldViewDir,normal)),4)//视角方向和法线方向,他们之间的夹角越小,fresnel越弱 反射越越小 折射越强。另外也有反射和漫反射之间fresnel插值公式。为了效率可不执行power操作。
                  float3 finalcol=fresnel
    reflect+(1-fresnel)refract;//fresnel插值反射和折射,用来计算反射和折射之间根据视角方向各种占的比重
    10:光照衰减公式:float atten= 1/(_WorldSpaceLightPos0.xyz,wpos.xyz);//衰减物理公式与距离的平方成反比,有时候也用距离反比函数做衰减。      
    10.高度雾:float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);然后把该值和颜色进行插值。有线性雾,指数雾,指数平方雾。雾对噪声图进行动态采样,可以实现动态雾效的效果。
    11.PBR基于物理的着色:输入参数 粗糙度和金属性,ds的完美模式下采用PBR渲染,PBR()接口输入各种方向,返回diff和spec,内部实现了金属材质曲线的方式。但是快速的前向模式下,只是计算了简单的漫反射。    
       金属度大,漫反射变小,高光反射变大,金属材质高光反射率RGB分量可能不同
       金属度小,漫反射变大,高光反射变小,非金属材质高光反射率RGB分量是一样的(大多数很接近<0.05, 0.05, 0.05>)
       粗糙度大:光滑变暗,漫反射增多,镜面反射变小
       粗糙度小:越光滑,镜面反射效果增多 漫反射变小    
       复杂的PBR效果是叠加了环境高光贴图形成的,并不是单一的PBR公式。叠加的贴图可以是金属也可以是其他材质。
                传统光照模型:I=普通漫反射+高光
       Cook-Torrance光照模型:I=普通漫反射L+镜面反射光强R,光滑度控制鏡面反射,金屬度控制高光。
                L=Lambert
    saturate(1-f0
    f0);//float f0 = (1-RR)/(1+RR);
                镜面反射光强R=FDG/(VN)或R=FDG/((VN)(NL)),也可以省略成R=FDG,其中F是Frenel系数,D是微平面法线分布函数,G是几何遮挡系数
                F=f0+(1-f0)(1-vh)`5//f0代表入射角接近0 靠近法向量时的fresnel反射系数。该公式精确度1%。
                D复杂公式
                G几何遮挡公式,也可以简略为固定值比如0.25
        ds公式:float R=saturate(1-_SmoothBase)
            float3 diff=1/PI max(0,dot(lightdir,normal))saturate(1-power((1-RR)/(1+RR),2)); 
            float h=normalize(worldlightdir,ViewDir);
            float nh=saturate(dot(normal,h));
            float3 spec=R
    RRR/power((nhnh(RRRR-1)+1),2)(1+meatal3)/4PI
        Ps.normal转换到View空间参与计算,记得考虑坐标系变化,可以i.view_pos_nor.z=-i.view_pos_nor.z把z反向,因为Unity物体是左手坐标系,view space是右手坐标系。ds的EncodeNormal和DecodeNormal会自动转换坐标系,但是Unity的EncodeViewNormalStereo是不会转换坐标系的。顶点转换也一样要考虑坐标系。
            
    12.次表面散射其中一种实现方式环绕光照:diff = ( dot(normal, lightDir) + wrap )/( 1+wrap )     //wrap为环绕参数,最后再计算该点模型的厚度作为参数相乘、输出颜色=col
    diff*depthMinus;//diff乘以颜色和厚度(前后深度差)

    作者:枫叶林GD
    来源:CSDN
    原文:https://blog.csdn.net/gy373499700/article/details/71107339
    版权声明:本文为博主原创文章,转载请附上博文链接!@TOC

    欢迎使用Markdown编辑器

    你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

    新的改变

    我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

    1. 全新的界面设计 ,将会带来全新的写作体验;
    2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
    3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
    4. 全新的 KaTeX数学公式 语法;
    5. 增加了支持甘特图的mermaid语法1 功能;
    6. 增加了 多屏幕编辑 Markdown文章功能;
    7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
    8. 增加了 检查列表 功能。

    功能快捷键

    撤销:Ctrl/Command + Z
    重做:Ctrl/Command + Y
    加粗:Ctrl/Command + B
    斜体:Ctrl/Command + I
    标题:Ctrl/Command + Shift + H
    无序列表:Ctrl/Command + Shift + U
    有序列表:Ctrl/Command + Shift + O
    检查列表:Ctrl/Command + Shift + C
    插入代码:Ctrl/Command + Shift + K
    插入链接:Ctrl/Command + Shift + L
    插入图片:Ctrl/Command + Shift + G

    合理的创建标题,有助于目录的生成

    直接输入1次#,并按下space后,将生成1级标题。
    输入2次#,并按下space后,将生成2级标题。
    以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

    如何改变文本的样式

    强调文本 强调文本

    加粗文本 加粗文本

    标记文本

    删除文本

    引用文本

    H2O is是液体。

    210 运算结果是 1024.

    插入链接与图片

    链接: link.

    图片: Alt

    带尺寸的图片: Alt

    当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

    如何插入一段漂亮的代码片

    博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

    // An highlighted block
    var foo = 'bar';
    

    生成一个适合你的列表

    • 项目
      • 项目
        • 项目
    1. 项目1
    2. 项目2
    3. 项目3
    • 计划任务
    • 完成任务

    创建一个表格

    一个简单的表格是这么创建的:

    项目 Value
    电脑 $1600
    手机 $12
    导管 $1

    设定内容居中、居左、居右

    使用:---------:居中
    使用:----------居左
    使用----------:居右

    第一列 第二列 第三列
    第一列文本居中 第二列文本居右 第三列文本居左

    SmartyPants

    SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

    TYPE ASCII HTML
    Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
    Quotes "Isn't this fun?" “Isn’t this fun?”
    Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

    创建一个自定义列表

    Markdown
    Text-to-HTML conversion tool
    Authors
    John
    Luke

    如何创建一个注脚

    一个具有注脚的文本。2

    注释也是必不可少的

    Markdown将文本转换为 HTML

    KaTeX数学公式

    您可以使用渲染LaTeX数学表达式 KaTeX:

    Gamma公式展示 Γ(n)=(n1)!nN\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N 是通过欧拉积分

    Γ(z)=0tz1etdt&ThinSpace;. \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.

    你可以找到更多关于的信息 LaTeX 数学表达式here.

    新的甘特图功能,丰富你的文章

    Mon 06Mon 13Mon 20已完成 进行中 计划一 计划二 现有任务Adding GANTT diagram functionality to mermaid
    • 关于 甘特图 语法,参考 这儿,

    UML 图表

    可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::

    张三李四王五你好!李四, 最近怎么样?你最近怎么样,王五?我很好,谢谢!我很好,谢谢!李四想了很长时间,文字太长了不适合放在一行.打量着王五...很好... 王五, 你怎么样?张三李四王五

    这将产生一个流程图。:

    链接
    长方形
    圆角长方形
    菱形
    • 关于 Mermaid 语法,参考 这儿,

    FLowchart流程图

    我们依旧会支持flowchart的流程图:

    Created with Raphaël 2.2.0开始我的操作确认?结束yesno
    • 关于 Flowchart流程图 语法,参考 这儿.

    导出与导入

    导出

    如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

    导入

    如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
    继续你的创作。


    1. mermaid语法说明 ↩︎

    2. 注脚的解释 ↩︎

    展开全文
1
收藏数 5
精华内容 2