一轮整理:2021年6月18日17:46:02

二轮整理:2021年11月2日11:37:08

作者:聪头

Ch9.更复杂的光照

LightMode标签 !!!

《Shader入门精要》中级篇笔记 - 图1

9.1 前向渲染路径

原理

《Shader入门精要》中级篇笔记 - 图2

3种处理光照的方式

  • 逐顶点处理
  • 逐像素处理
  • 球谐函数(Spherical Harmonics,SH)

判断规则

《Shader入门精要》中级篇笔记 - 图3

两个Pass

  • 说明详见P183

《Shader入门精要》中级篇笔记 - 图4

内置光照变量和函数!!!

  • 仅前向渲染可访问

《Shader入门精要》中级篇笔记 - 图5

《Shader入门精要》中级篇笔记 - 图6

延迟渲染路径(了解)

介绍

《Shader入门精要》中级篇笔记 - 图7

两个Pass

《Shader入门精要》中级篇笔记 - 图8

补充:只有通过深度测试的片元,即可见的片元,才写入G-buffer中

《Shader入门精要》中级篇笔记 - 图9

内变量

  • 位于UnityDeferredLibrary.cginc

《Shader入门精要》中级篇笔记 - 图10

9.2 光源类型

Unity支持4种光源类型:平行光、点光源、聚光灯和面光源(烘焙时有效,不列入本书讨论范围)

光源5个重要属性:位置、方向、颜色、强度及衰减

BasePass中:通常计算一次逐像素平行光、一次自发光、一次环境光及其他多个逐顶点和SH光源

  • 不影响ProjectSetting中逐像素光源数量,即排除此逐像素光源

AdditionalPass中:一个逐像素光源(除BasePass的逐像素平行光)计算一次

9.3 光照衰减

法1.使用光照衰减纹理

  • Unity在内部使用一张名为_LightTexture0的纹理来计算光源衰减(注意,如果对光源使用了cookie,那么衰减查找纹理是_LightTextureB0,此情况这里不作讨论)。我们通常只关心_LightTexture0对角线上的纹理颜色值。这些值表明了在光源空间中不同位置的点的衰减值
    • (0,0)表示与光源位置重合点的衰减值,(1,1)表示在光源空间中距离最远的点的衰减值

核心代码

  1. float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;//世界->光源
  2. float atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
  3. //采样衰减纹理,利用宏UNITY_ATTEN_CHANNEL获得衰减值所在分量

法2.数学公式计算

  • 公式太easy,不推荐
float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
atten = 1.0 / distance;//linear attenuation

9.4 阴影

阴影原理详见P197

  • LightMode标记为ShadowCaster的Pass会被阴影映射纹理识别,就可以投射阴影了
  • 接收其他物体的阴影,必须在Shader中对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照结果相乘来产生阴影效果

ShadowCaster的Pass实现原理详见P199

阴影三剑客

包含新的内置文件:AutoLight.cginc,因为下面计算阴影时所用的宏都是在这个文件中声明的

  • SHADOW_COORDS
    • v2f内
    • 声明一个对阴影纹理采样的坐标(名为_ShadowCoord),这个宏的参数是下一个可用的插值寄存器的索引值
  • TRANSFER_SHADOW
    • 顶点着色器内
    • 这个宏用于在顶点着色中计算上一步中声明的阴影纹理坐标
    • 若屏幕空间的阴影映射技术可用(通过UNITY_NO_SCREENSPACE_SHADOWS来得到),则调用ComputeScreenPos函数来计算。否则使用传统阴影映射技术,把顶点坐标从模型空间变换到光源空间后存储到_ShadowCoord中
  • SHADOW_ATTENUATION
    • 片元着色器内
    • 使用_ShadowCoord对相关的纹理进行采样,得到阴影信息

前提:a2v的顶点坐标输入必须命名vertex,v2f顶点位置变量必须为pos

统一光照和衰减

  • UNITY_LIGHT_ATTENUATION
    • 片元着色器内,将原来的SHADOW_ATTENUATION替换成该宏即可
    • 计算阴影和衰减
    • Unity针对不同光源类型,是否启用cookie等不同情况声明了多个版本的UNITY_LIGHT_ATTENUATION
      //参数①:UNITY_LIGHT_ATTENUATION会帮我们声明atten。用于存储光照衰减和阴影值相乘后的结果
      //参数②:结构体v2f。这个参数会传递给SHADOW_ATTENUATION来计算阴影值
      //参数③:世界空间下的坐标。用于计算光源空间下的坐标,再对光照衰减纹理采样得到光照衰减值 同P196
      UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
      

实现思路

《Shader入门精要》中级篇笔记 - 图11

阴影补充

Additional Pass中添加阴影效果

  • 使用#pragma multi_compile_fwdadd_fullshadows来代替#pragma multi_compile_fwdadd指令

透明度物体的阴影

  • 透明度测试:使用Fallback Transparent/Cutout/VertexLit
    • 必须使用名为_Cutoff的属性来进行透明度测试才能得到正确的结果
  • 透明度混合:目前无法很好实现
    1. 不实现阴影
    2. 可以把Fallback设为VertexLit等强制产生阴影

透明度混合不易实现阴影的原因:需要关闭深度写入,由此带来的问题也影响了阴影的生成。想要为这些半透明物体产生正确的阴影,需要在每个光源空间下仍然严格按照从后往前的顺序进行渲染,这会使阴影处理变得非常复杂,影响性能

实战9-1:标准光照着色器

  • 全部转到世界空间下计算
  • 基于Blinn-Phong
Shader "Unity Shaders Book/Chapter 9/CT_Bumped Specular"
{
  Properties {
    _Color ("Color Tint", Color) = (1, 1, 1, 1)
      _MainTex ("Main Tex", 2D) = "white" {}
    _BumpMap ("Normal Map", 2D) = "bump" {}
    _Specular ("Specular Color", Color) = (1, 1, 1, 1)
      _Gloss ("Gloss", Range(8.0, 256)) = 20
  }
  SubShader {
    Tags { "RenderType"="Opaque" "Queue"="Geometry"}

    Pass { 
      Tags { "LightMode"="ForwardBase" }

      CGPROGRAM

        #pragma multi_compile_fwdbase    

        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"
        #include "Lighting.cginc"
        #include "AutoLight.cginc"

        fixed4 _Color;
      sampler2D _MainTex;
      float4 _MainTex_ST;
      sampler2D _BumpMap;
      float4 _BumpMap_ST;
      fixed4 _Specular;
      float _Gloss;

      struct a2v {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        float4 tangent : TANGENT;
        float4 texcoord : TEXCOORD0;
      };

      struct v2f {
        float4 pos : SV_POSITION;
        float4 uv : TEXCOORD0;
        float4 TtoW0 : TEXCOORD1;  
        float4 TtoW1 : TEXCOORD2;  
        float4 TtoW2 : TEXCOORD3; 
        SHADOW_COORDS(4) //声明一个对阴影纹理采样的坐标(_ShadowCoord)
      };

      v2f vert(a2v v) {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);

        o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
        o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

        float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
        fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
        fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
        fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 

        o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
        o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
        o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  

        TRANSFER_SHADOW(o); //计算声明的阴影纹理坐标

        return o;
      }

      fixed4 frag(v2f i) : SV_Target {
        float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
        fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
        fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

        fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
        //将法线从切线空间变换到世界空间(这里是正交矩阵,逆转置矩阵等于其本身)
        float3x3 TtoW = float3x3(i.TtoW0.x, i.TtoW1.x, i.TtoW2.x,
                                 i.TtoW0.y, i.TtoW1.y, i.TtoW2.y,
                                 i.TtoW0.z, i.TtoW1.z, i.TtoW2.z);
        bump = normalize(mul(TtoW, bump));

        fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;

        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

        fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));

        fixed3 halfDir = normalize(lightDir + viewDir);
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);

        UNITY_LIGHT_ATTENUATION(atten, i, worldPos); //根据阴影纹理坐标采样得到结果值

        return fixed4(ambient + (diffuse + specular) * atten, 1.0);
      }

      ENDCG
    }

    Pass { 
      Tags { "LightMode"="ForwardAdd" }

      Blend One One

        CGPROGRAM

        #pragma multi_compile_fwdadd
        // Use the line below to add shadows for point and spot lights
        //            #pragma multi_compile_fwdadd_fullshadows

        #pragma vertex vert
        #pragma fragment frag

        #include "Lighting.cginc"
        #include "AutoLight.cginc"

        fixed4 _Color;
      sampler2D _MainTex;
      float4 _MainTex_ST;
      sampler2D _BumpMap;
      float4 _BumpMap_ST;
      float _BumpScale;
      fixed4 _Specular;
      float _Gloss;

      struct a2v {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        float4 tangent : TANGENT;
        float4 texcoord : TEXCOORD0;
      };

      struct v2f {
        float4 pos : SV_POSITION;
        float4 uv : TEXCOORD0;
        float4 TtoW0 : TEXCOORD1;  
        float4 TtoW1 : TEXCOORD2;  
        float4 TtoW2 : TEXCOORD3;
        SHADOW_COORDS(4)
      };

      v2f vert(a2v v) {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);

        o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
        o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

        float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
        fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
        fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
        fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 

        o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
        o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
        o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  

        TRANSFER_SHADOW(o);

        return o;
      }

      fixed4 frag(v2f i) : SV_Target {
        float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
        fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
        fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

        fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
        //将法线从切线空间变换到世界空间(这里是正交矩阵,逆转置矩阵等于其本身)
        float3x3 TtoW = float3x3(i.TtoW0.x, i.TtoW1.x, i.TtoW2.x,
                                 i.TtoW0.y, i.TtoW1.y, i.TtoW2.y,
                                 i.TtoW0.z, i.TtoW1.z, i.TtoW2.z);
        bump = normalize(mul(TtoW, bump));

        fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;

        fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));

        fixed3 halfDir = normalize(lightDir + viewDir);
        fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);

        UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

        return fixed4((diffuse + specular) * atten, 1.0);
      }

      ENDCG
    }
  } 
  FallBack "Specular"
}

《Shader入门精要》中级篇笔记 - 图12

Ch10.高级纹理

10.1 立方体纹理

  • 立方体纹理(Cubemap)是环境映射的一种实现方式。环境映射可以模拟物体周围的环境,而使用了环境映射的物体看起来像镀了层金属一样反射出周围的环境
  • 和之前使用二维坐标采样不同,对立方体纹理采样我们需要提供一个三维的纹理坐标,这个三维纹理坐标表示了我们在世界空间下的一个3D方向
  • 应用
    • 天空盒子:在Unity中,天空盒子是在所有不透明物体之后渲染的
    • 环境映射:可以模拟出金属质感的材质,常用于反射和折射。创建环境映射纹理的方法有以下三种:
      1. 直接由一些特殊布局的纹理创建,例如,类似立方体展开图的交叉布局、全景布局等。然后把该纹理的Texture Type设置为Cubemap即可。在基于物理的渲染中,通常会使用HDR图像生成高质量Cubemap。同时可以对纹理数据进行压缩,而且支持边缘修正、光滑反射等,推荐
      2. Unity创建一个Cubemap。把6张纹理拖拽到它的面板中
      3. 利用脚本创建,立方体纹理更具个性化。这是通过Camera.RenderToCubemap函数来实现的,该函数可以把任意位置观察到的场景图像存储到6张图像中,从而创建出该位置上对应的立方体纹理

本节使用方法3:

  1. 确保项目中存在RenderCubemapWizard.cs脚本
  2. 创建一个立方体(带有Transform信息的物体都可)
  3. 创建一个Cubemap(Create->Legacy->Cubemap),并勾选Readable选项
  4. 菜单栏中选择GameObject->Render into Cubemap,依次赋值,点击Render!即可

反射

  • 我们只需要通过入射光线和表面法线来计算反射方向,再利用反射方向对立方体纹理采样即可

反射关键代码

  • 顶点着色器
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal);
  • 片元着色器
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb;

反射向量没有归一化,是因为texCUBE内部会默认归一化

《Shader入门精要》中级篇笔记 - 图13

折射

斯涅耳定律

  • 当光从介质1沿着和表面法线夹角为θ1的方向斜射入介质2时,我们可以使用如下公式来计算折射光线与法线的夹角θ2

《Shader入门精要》中级篇笔记 - 图14

其中《Shader入门精要》中级篇笔记 - 图15《Shader入门精要》中级篇笔记 - 图16分别是两个介质的折射率(index of refraction)

  • 真空折射率:1
  • 玻璃:1.5

《Shader入门精要》中级篇笔记 - 图17

折射关键代码

  • 顶点着色器:计算折射方向
//参数①:入射光线方向,必须归一化
//参数②:表面法线,必须归一化
//参数③:入射光线所在介质的折射率 和 折射光线所在介质的折射率 之比
//如从空气-->玻璃:1/1.5
//返回值:折射方向,摸与入射光线相同
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
  • 片元着色器:利用折射方向对立方体纹理采样
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;

折射向量没有归一化,因为对立方体纹理的采样只需提供方向即可

《Shader入门精要》中级篇笔记 - 图18

菲涅尔反射

  • 基于视角方向控制反射程度
  • 菲涅尔反射描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅尔等式计算

《Shader入门精要》中级篇笔记 - 图19

菲涅尔反射关键代码

  • 片元着色器
  • 使用Schlick菲涅尔近似等式
  • 一些实现也会直接把fresnel和反射光照相乘后叠加到漫反射光照上,模拟边缘光照效果
//使用Schlick菲涅尔近似等式计算
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5);
...
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)) * atten;

《Shader入门精要》中级篇笔记 - 图20

10.2 渲染纹理

  • 渲染目标纹理(Render Target Texture, RTT):现代GPU允许我们把整个三维场景渲染到一个中间缓冲中
  • 多重渲染目标(Multiple Render Target,MRT):这种技术指的是GPU允许我们把场景同时渲染到多个渲染目标纹理中,而不再需要为每个渲染目标单独渲染完整的场景
  • 渲染纹理(Render Texture):Unity为渲染目标纹理定义了一种专门的纹理类型
    • 创建法1:在Project目录下创建,然后把某个摄像机的渲染目标设置成该渲染纹理
    • 创建法2:在屏幕后处理时使用GrabPass命令或OnRenderImage函数来获取当前屏幕图像。Unity会把这个屏幕图像放到一张和屏幕分辨率等同的渲染纹理中

10.3 程序纹理

两种程序纹理的生成方式:

  • 通过脚本生成程序纹理
  • 使用程序材质(以.sbsar为后缀)。它使用的程序纹理是通过Substance Designer在Unity外部生成的
    • 好处:多变性,我们可以通过调整程序纹理的属性来控制纹理的外观

实战10-1:玻璃效果

  • 在使用GrabPass的时候,我们往往需要把物体的渲染队列设置成透明队列(即”Queue”=”Transparent”)。这样可以确保渲染该物体时,所有不透明物体都已经被绘制在屏幕上,从而获取正确的屏幕图像
  • 原理:首先使用一张法线纹理来修改模型的法线信息,然后使用之前介绍的反射方法,通过一个Cubemap来模拟玻璃的反射,而在模拟折射时,则使用了GrabPass获取玻璃后面的屏幕图像,并使用切线空间下的法线对屏幕纹理坐标偏移后,再对屏幕图像进行采样来模拟近似的折射效果
    • 本质,就是通过法线贴图偏扰uv,采样GrabPass抓取的屏幕纹理

折射:使用切线空间的法线,偏扰屏幕uv坐标,进而采样屏幕纹理即可

反射:使用反射向量采样立方体贴图

Shader "Unity Shaders Book/Chapter 10/CT_GlassRefreaction"
{
    Properties {
        _MainTex ("Main Tex", 2D) = "white"{}//玻璃的材质纹理
        _BumpMap ("Normal Map", 2D) = "bump"{}//法线纹理
        _Cubemap ("Environment Cubemap", Cube) = "_Skybox"{}//模拟反射的环境纹理
        _Distortion ("Distortion", Range(0, 100)) = 100//扭曲程度
        _RefractAmount ("Refract Amount", Range(0.0, 1.0)) = 1.0//0只包含反射效果;1只包括折射效果
    }
    SubShader {
        //把Queue设置成Transparent可以确保该物体渲染时,其他所有不透明物体已渲染在屏幕上
        //设置RenderType则是为了在使用着色器替换时,该物体可以在需要时被正确渲染,具体之后的13章会学到
        Tags {"Queue" = "Transparent" "RenderType" = "Opaque"}
        GrabPass {"_RefractionTex"}//决定抓取得到的屏幕图像将会被存入哪个纹理中
        Pass {        
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            samplerCUBE _Cubemap;
            float _Distortion;
            fixed _RefractAmount;
            sampler2D _RefractionTex;//对应了GrabPass指定的纹理名称
            //该纹理的纹素大小,例如一个大小为256 x 512的纹理,纹素大小(1/256, 1/512),我们需要对屏幕图像采样坐标进行偏移时使用
            float4 _RefractionTex_TexelSize;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT; 
                float2 texcoord: TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float4 scrPos : TEXCOORD0;
                float4 uv : TEXCOORD1;
                float4 TtoW0 : TEXCOORD2;  
               float4 TtoW1 : TEXCOORD3;  
               float4 TtoW2 : TEXCOORD4; 
            };

            v2f vert (a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                //得到对应被抓取屏幕图像的采样坐标
                o.scrPos = ComputeGrabScreenPos(o.pos);

                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 

                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  

                return o;
            }

            fixed4 frag (v2f i) : SV_Target {        
                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                // Get the normal in tangent space
                fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));    

                // Compute the offset in tangent space
                //选择切线空间的法线方向进行偏移,因为该空间下的法线可以反映顶点局部空间下的法线方向
                //why?没说为何这样偏移?!盲猜经验总结。偏移值通过法线贴图的xy,_Distortion,纹素共同决定
                float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
                i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
                //使用透视除法得到真正的屏幕坐标,在使用该坐标对抓取的屏幕图像进行采样
                fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;

                // Convert the normal to world space
                bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
                fixed3 reflDir = reflect(-worldViewDir, bump);
                fixed4 texColor = tex2D(_MainTex, i.uv.xy);
                fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;
                //将反射和折射颜色进行混合
                fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;

                return fixed4(finalColor, 1);
            }

            ENDCG
        }
    }
}

《Shader入门精要》中级篇笔记 - 图21

Ch11.让画面动起来

11.1 内置时间变量!!!

《Shader入门精要》中级篇笔记 - 图22

实战11-1:序列帧动画

  • 序列帧图像通常是透明纹理,关闭深度写入
Shader "Unity Shaders Book/Chapter 11/CT_ImageSequenceAnimation"
{
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Image Sequence", 2D) = "white" {}
        _HorizontalAmount ("Horizontal Amount", Float) = 4
        _VerticalAmount ("Vertical Amount", Float) = 4
        _Speed ("Speed", Range(1, 100)) = 30
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

        Pass {
            Tags { "LightMode"="ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM

            #pragma vertex vert  
            #pragma fragment frag

            #include "UnityCG.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _HorizontalAmount;
            float _VerticalAmount;
            float _Speed;

            struct a2v {  
                float4 vertex : POSITION; 
                float2 texcoord : TEXCOORD0;
            };  

            struct v2f {  
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };  

            v2f vert (a2v v) {  
                v2f o;  
                o.pos = UnityObjectToClipPos(v.vertex);  
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);  
                return o;
            }  

            fixed4 frag (v2f i) : SV_Target {
                float time = floor(_Time.y * _Speed);
                float row = floor(time / _HorizontalAmount);//商为行索引
                float column = time - row * _HorizontalAmount; //余数为列索引

                //在Unity中,纹理通常按DirectX的笛卡尔坐标系设计(原点在左上角),而采样原点在左下角,注意转换
                //先缩放再偏移
//                half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount); //uv缩小为只采样一块区域
//                uv.x += column / _HorizontalAmount; //列偏移量
//                uv.y -= row / _VerticalAmount; //行偏移量

                //先偏移再缩放
                half2 uv = i.uv + half2(column, -row);
                uv.x /=  _HorizontalAmount;
                uv.y /= _VerticalAmount;

                fixed4 c = tex2D(_MainTex, uv);
                c.rgb *= _Color;
                return c;
            }

            ENDCG
        }  
    }
    FallBack "Transparent/VertexLit"
}

《Shader入门精要》中级篇笔记 - 图23

实战11-2:滚动的背景

关键代码

  • 顶点着色器
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y);

完整代码

Shader "Unity Shaders Book/Chapter 11/CT_ImageSequenceAnimation"
{
  Properties {
    _Color ("Color Tint", Color) = (1, 1, 1, 1)
      _MainTex ("Image Sequence", 2D) = "white" {}
    _HorizontalAmount ("Horizontal Amount", Float) = 4
      _VerticalAmount ("Vertical Amount", Float) = 4
      _Speed ("Speed", Range(1, 100)) = 30
  }
  SubShader {
    Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

    Pass {
      Tags { "LightMode"="ForwardBase" }

      ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM

        #pragma vertex vert  
        #pragma fragment frag

        #include "UnityCG.cginc"

        fixed4 _Color;
      sampler2D _MainTex;
      float4 _MainTex_ST;
      float _HorizontalAmount;
      float _VerticalAmount;
      float _Speed;

      struct a2v {  
        float4 vertex : POSITION; 
        float2 texcoord : TEXCOORD0;
      };  

      struct v2f {  
        float4 pos : SV_POSITION;
        float2 uv : TEXCOORD0;
      };  

      v2f vert (a2v v) {  
        v2f o;  
        o.pos = UnityObjectToClipPos(v.vertex);  
        o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);  
        return o;
      }  

      fixed4 frag (v2f i) : SV_Target {
        float time = floor(_Time.y * _Speed);
        float row = floor(time / _HorizontalAmount);//商为行索引
        float column = time - row * _HorizontalAmount; //余数为列索引

        //在Unity中,纹理通常按DirectX的笛卡尔坐标系设计(原点在左上角),而采样原点在左下角,注意转换
        //先缩放再偏移
        //                half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount); //uv缩小为只采样一块区域
        //                uv.x += column / _HorizontalAmount; //列偏移量
        //                uv.y -= row / _VerticalAmount; //行偏移量

        //先偏移再缩放
        half2 uv = i.uv + half2(column, -row);
        uv.x /=  _HorizontalAmount;
        uv.y /= _VerticalAmount;

        fixed4 c = tex2D(_MainTex, uv);
        c.rgb *= _Color;
        return c;
      }

      ENDCG
    }  
  }
  FallBack "Transparent/VertexLit"
}

《Shader入门精要》中级篇笔记 - 图24

实战11-3:河流

关键代码

  • DisableBatching:设为True,可以取消对该Shader的批处理工作。因为该Shader包含了模型空间的顶点动画,批处理会合并所有相关模型,而这些模型各自的模型空间就会丢失,所以们要关闭批处理
  • 顶点着色器
offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude; //修改的波动方程
o.pos = UnityObjectToClipPos(v.vertex + offset);

《Shader入门精要》中级篇笔记 - 图25

模型类似一个x轴向下的面片

完整代码

  • 需要加入阴影详见书P240
Shader "Unity Shaders Book/Chapter 11/CT_Water"
{
    Properties {
        _MainTex ("Main Tex", 2D) = "white" {}
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _Magnitude ("Distortion Magnitude", Float) = 1
         _Frequency ("Distortion Frequency", Float) = 1
         _InvWaveLength ("Distortion Inverse Wave Length", Float) = 10
         _Speed ("Speed", Float) = 0.5
    }
    SubShader {
        // Need to disable batching because of the vertex animation
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}

        Pass {
            Tags { "LightMode"="ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            CGPROGRAM  
            #pragma vertex vert 
            #pragma fragment frag

            #include "UnityCG.cginc" 

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            float _Magnitude;
            float _Frequency;
            float _InvWaveLength;
            float _Speed;

            struct a2v {
                float4 vertex : POSITION;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            v2f vert(a2v v) {
                v2f o;

                float4 offset;
                offset.yzw = float3(0.0, 0.0, 0.0);
                offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;
                o.pos = UnityObjectToClipPos(v.vertex + offset);

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv +=  float2(0.0, _Time.y * _Speed);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                fixed4 c = tex2D(_MainTex, i.uv);
                c.rgb *= _Color.rgb;

                return c;
            } 

            ENDCG
        }
    }
    FallBack "Transparent/VertexLit"
}

《Shader入门精要》中级篇笔记 - 图26

实战11-4:广告牌

思路:设视角方向为新法线方向,通过与向上方向叉积得向右方向,再通过向右方向和新法线方向叉积得新向上方向

Shader "Unity Shaders Book/Chapter 11/CT_Billboard"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color Tint", Color) = (1,1,1,1)
        _VerticalBillboarding ("Vertical Restraints", Range(0,1)) = 1//用于调整是固定法线方向还是固定指向上的方向
    }
    SubShader
    {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}

        Pass
        {
            Tags {"LightMode"="ForwardBase"}
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            fixed _VerticalBillboarding;

            struct a2v
            {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
            };



            v2f vert (a2v v)
            {
                v2f o;

                //假设原点为锚点
                float3 center = float3(0,0,0);
                float3 viewer = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));//获得视角位置
                float3 normalDir = viewer - center;//模型->视角

                //如果_VerticalBillboarding = 1,我们使用想要的视角方向作为法线方向
                //这意味着法线方向是固定的
                //或者如果_VerticalBillboarding = 0,则normal的y为0
                //这意味着向上方向是固定的
                normalDir.y = normalDir.y * _VerticalBillboarding;
                normalDir = normalize(normalDir);

                float3 upDir = abs(normalDir.y) > 0.999 ? float3(0,0,1) : float3(0,1,0);
                float3 rightDir = normalize(cross(upDir, normalDir));
                upDir = normalize(cross(normalDir, rightDir));


                // Use the three vectors to rotate the quad
                float3 centerOffs = v.vertex.xyz - center;
                float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;

                o.pos = UnityObjectToClipPos(float4(localPos, 1));
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 c = tex2D (_MainTex, i.uv);
                c.rgb *= _Color.rgb;

                return c;
            }
            ENDCG
        }
    }
}

《Shader入门精要》中级篇笔记 - 图27