Chapter11让画面动起来——Shader入门精要学习笔记

一、Unity Shader中的内置变量(时间篇)

Unity Shader 提供了一系列时间变量来允许我们方便地在Shader中访问运行时间
在这里插入图片描述

二、纹理动画

1.序列帧动画

  • 原理:依次播放关键帧图像,形成连续动画
 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)) = 3
 }
  • _MainTex 就是包含了所有关键帧图像的纹理
  • _HorizontalAmount 和 _VerticalAmount 分别代表了该图像在水平方向和竖直方向包含的关键帧图像的个数
  • _Speed 用于控制序列帧动画的播放速度
SubShader
{
    Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}

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

        Zwrite Off
        Blend SrcAlpha OneMinusSrcAlpha
  • 序列帧通常是透明纹理,可以被当成一个半透明对象
v2f vert(a2v v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    return o;
}
  • 把顶点纹理坐标存储到了v2f中
            fixed4 frag(v2f i):SV_Target 
            {
                float time = floor(_Time.y * _Speed);
                float row = floor(time / _HorizontalAmount);
                float column = time - row * _HorizontalAmount;

 //				half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount);
//				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;
            }
  • 前三行计算了行列数
    • _Time.y * _Speed 相乘得到模拟的时间,floor函数用来对结果取整
    • time / _HorizontalAmount 商作为当前对应的行索引,余数是列索引
  • 使用行列索引值来构建真正的采样坐标 (注释的)
    • 我们可以首先把原纹理坐标 i.uv 按行数和列数进行等分, 得到每个子图像的纹理坐标范围
    • 使用当前的行列数对上面的结果进行偏移,得到当前子图像的纹理坐标范围
    • 竖直方向坐标偏移需要使用减法,因为Unity纹理坐标竖直方向顺序是从上到下,序列帧的图像是从下到上的,相反的
  • 将原纹理坐标 i.uv 加上一个向量 (column, -row),然后分别除以水平方向和竖直方向关键帧数量,得到当前子图像的纹理坐标范围。这种方法更加通用,即使关键帧图像大小不同也可以使用。

2.滚动背景

Properties
{
    _MainTex ("Base Layer (RGB)", 2D) = "white" {}
	_DetailTex ("2nd Layer (RGB)", 2D) = "white" {}
	_ScrollX ("Base layer Scroll Speed", Float) = 1.0
	_Scroll2X ("2nd layer Scroll Speed", Float) = 1.0
	_Multiplier ("Layer Multiplier", Float) = 1
}
  • _MainTex 表示第一层(较远),_DetailTex 表示第二层(较近)
  • _ScrollX 和 _Scroll2X 对应了各自的水平滚动速度
  • _Multiplier 控制整体亮度
v2f vert (a2v v) {
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	
	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);
	
	return o;
}
  • 最基本的顶点变换,把顶点变换到裁剪空间
  • 首先利用 TRANSFORM_TEX() 得到初始纹理坐标,再使用内置的_Time.y 变量在水平方向上对纹理坐标进行偏移
  • 把两张纹理存在一个uv中
fixed4 frag (v2f i) : SV_Target {
	fixed4 firstLayer = tex2D(_MainTex, i.uv.xy);
	fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw);
	
	fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);
	c.rgb *= _Multiplier;
	
	return c;
}
  • 使用 i.uv.xy 和 i.uv.zw 对两张背景纹理进行采样

  • 使用第二层纹理的透明通道来混合两张纹理

  • 远处的背景应该移动的比近处的慢

三、顶点动画

1.流动的河流

  • 原理就是使用正弦函数来模拟水流波动效果
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
}
  • _MainTex 是河流纹理, _Color 控制整体颜色,_Magnitude 控制水波流动的幅度,_Frequency 控制波动频率,_InvWaveLength 控制波长的倒数,_Speed 控制速度
SubShader {
	// Need to disable batching because of the vertex animation
	Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
  • “DisableBatching”=“True” 通过该标签来直接指明是否对该SubShader使用批处理(需要特殊处理的Shader就包含了模型空间的顶点动画),批处理会合并所有相关模型,而这些模型各自的模型空间就会丢失,在本例中,需要在物体模型空间下对顶点进行位置偏移,因此需要取消批处理操作
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;
}
  • 先计算顶点位移量,我们只希望顶点x方向进行位移,所以yzw都被设置为0
  • 利用_Frequency 属性和内置的 _Time.y 来控制正弦函数的频率
  • 为了让不同位置有不同的位移,我们对上述结果加上了模型空间下的位置分量,并×_InvWaveLength 来控制波长
  • 最后×_Magnitude 控制波动幅度,得到最终位移
  • 然后把位移量添加到顶点上,再进行顶点变换即可
fixed4 frag(v2f i) : SV_Target {
	fixed4 c = tex2D(_MainTex, i.uv);
	c.rgb *= _Color.rgb;
	
	return c;
} 
  • 直接对纹理进行采样再添加颜色即可

2.广告牌

  • 广告牌技术:会根据视角方向来旋转一个被纹理着色的多边形,使得好像总是面对着相机
  • 本质就是构建旋转矩阵
  • 一个变换矩阵需要3个基向量,广告牌技术使用的通常是 表面法线、指向上的方向、指向右的方向,除此之外,还需要指定一个 锚点(在旋转过程中固定不变的,以此来确认多边形在空间中的位置)
  • 计算基向量(相互正交的):
    • 法线方向: 通常为视角方向或固定方向
    • 指向上的方向: 通常为 (0, 1, 0) 或固定方向
    • 指向右的方向: 通过法线方向和指向上的方向计算得到 r i g h t = u p × n o r m a l right = up\times normal right=up×normal 叉积
    • r i g h t right right 归一化后,再由法线方向和指向右的方向计算出正交的指向上的方向 u p ′ = n o r a m l × r i g h t up' = noraml \times right up=noraml×right
      在这里插入图片描述
	Properties {
		_MainTex ("Main Tex", 2D) = "white" {}
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1 
	}
  • _MainTex 是广告牌的透明纹理,_Color 控制整体颜色,_VerticalBillboarding 调整是固定法线还是固定指向上的方向(0是向上,1是法线)
SubShader {
	// Need to disable batching because of the vertex animation
	Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
  • 在广告牌技术中,我们需要使用物体模型空间下的位置来作为模电进行计算,所以要取消批处理
v2f vert (a2v v) {
	v2f o;
	
	// Suppose the center in object space is fixed
	float3 center = float3(0, 0, 0);
	float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1));
	
	float3 normalDir = viewer - center;
	// If _VerticalBillboarding equals 1, we use the desired view dir as the normal dir
	// Which means the normal dir is fixed
	// Or if _VerticalBillboarding equals 0, the y of normal is 0
	// Which means the up dir is fixed
	normalDir.y =normalDir.y * _VerticalBillboarding;
	normalDir = normalize(normalDir);
	// Get the approximate up dir
	// If normal dir is already towards up, then the up dir is towards front
	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;
}
  • 所有的计算都是在模型空间下计算的 (下面解释顶点着色器内容)
  • 首先选择模型空间的原点作为广告牌的锚点,利用内置变量 mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1)) 获取模型空间下的视角位置
  • 然后开始计算三个正交基
float3 normalDir = viewer - center;
	// If _VerticalBillboarding equals 1, we use the desired view dir as the normal dir
	// Which means the normal dir is fixed
	// Or if _VerticalBillboarding equals 0, the y of normal is 0
	// Which means the up dir is fixed
	normalDir.y =normalDir.y * _VerticalBillboarding;
	normalDir = normalize(normalDir);
  • 根据观察位置和锚点计算目标法线方向,并使用_VerticalBillboarding 来控制垂直方向上的约束度
  • 1时法线方向固定为视角方向,0时向上方向固定为(0,1,0)
float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
  • 接着粗略地得到向上方向,为了放置法线方向和向上方向平行,对法线方向的y分量进行判断
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);
  • 最后把模型空间的顶点位置变换到裁剪空间
  • 片元着色器只需要对纹理进行采样,再与颜色相乘即可
fixed4 frag (v2f i) : SV_Target {
	fixed4 c = tex2D (_MainTex, i.uv);
	c.rgb *= _Color.rgb;
	
	return c;
}

3.注意事项

①批处理问题

  • 因为批处理会 合并模型,导致模型空间丢失,而顶点动画又需要依赖模型空间的位置进行计算
    • 合并模型:将所有使用同一 Shader 的材质的模型合并成一个批次进行渲染,每个模型都被转换到了一个统一的坐标系中
  • 解决方法
    • 禁用批处理:通过在 SubShader 标签中 使用 “DisableBatching” 标签,可以强制禁用对该 Shader 的批处理,从而避免动画效果被破坏,但这样做会降低性能,因为会增加 Draw Call
    • 使用相对位置和方向:尽量避免使用模型空间下的绝对位置和方向进行计算,例如,使用顶点颜色存储每个顶点到锚点的距离,而不是直接使用模型空间的中心作为锚点
      • 为模型的每个顶点设置一个顶点颜色值,该颜色值表示该顶点到模型中心点的距离,以将颜色值的 R 分量设置为距离值,G、B 分量设置为 0
      • 读取每个顶点的顶点颜色值,并从中提取出距离值

②阴影投射问题

  • 内置的 ShadowCaster Pass 无法处理顶点动画: Unity 的内置阴影投射 Pass 没有进行顶点动画的处理,因此会导致阴影与物体的实际位置不匹配
  • 解决方法:自定义ShadowCaster Pass (见书)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值