US4L5——翻页效果

书本翻页效果

在游戏开发中,书本翻页效果就是,字面意思,用 Shader 模拟出书本翻页时的动态效果
这种效果常用与游戏和动画中,比如用于制作一些 3D UI,制作一些书本交互功能等等

一句话概括它的基本原理:对顶点进行平移 —> 矩阵旋转 —> 再平移,并利用三角函数相关知识制做出起伏感

image

关键点:

  • 如何制作旋转

    我们将利用以前学习的 旋转矩阵 相关知识,来对顶点进行旋转变换,
    但是,旋转前我们需要先对顶点进行平移,否则模型将绕着模型中心点旋转。
    因为旋转之前进行了平移,因此旋转结束后,我们需要再将顶点平移回来

    如果不平移,旋转效果就变成了这样:

    image

    进行平移,才能让书页沿侧边一轴旋转,至于平移的距离,由书页本身宽度的一半决定:

    image

    旋转矩阵如下:

    绕 x 轴旋转 β 度,旋转矩阵为:

    [10000cosβsinβ00sinβcosβ00001]\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & cos\beta & -sin\beta & 0 \\ 0 & sin\beta & cos\beta & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

    绕 y 轴旋转 β 度,旋转矩阵为:

    [cosβ0sinβ00100sinβ0cosβ00001]\begin{bmatrix} cos\beta & 0 & sin\beta & 0 \\ 0 & 1 & 0 & 0 \\ -sin\beta & 0 & cos\beta & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

    绕 z 轴旋转 β 度,旋转矩阵为:

    [cosβsinβ00sinβcosβ0000100001]\begin{bmatrix} cos\beta & -sin\beta & 0 & 0 \\ sin\beta & cos\beta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix}

  • 如何制作起伏感

    只需要在旋转前利用三角函数 sin\sin 让顶点在 Y 轴上进行偏移即可
    并且 0 ~ 90 ~ 180 度之间变换时,0 和 180 度不需要起伏感,90 度时起伏感最大(页面最弯曲)

    1
    2
    3
    float 波形权重 = 1 - abs(90 - 旋转角度) / 90;                    // 波形权重,用于控制起伏程度,90度是起伏程度最大,0和180时不起伏
    v.vertex.y += sin(v.vertex.x * 波长) * 波形权重 * 起伏程度; // y轴产生上下位移顶点起伏
    v.vertex.x -= v.vertex.x * 波形权重 * 收缩程度; // x轴收缩,实现起伏时缩短的效果,让翻页更真实

书本翻页效果的具体实现

  1. 新建 Shader,命名为 PageTurning​,删除无用代码

  2. 属性声明 属性映射

    • 正面纹理:_MainTex
    • 背面纹理:_BackTex
    • 翻页进度 (0~180度):_AngleProgress
    • x 轴收缩程度 (0~1):_WeightX
    • y 轴弯曲程度 (0~1) _WeightY
    • 波长 (0~3):_WaveLength
    • 平移距离:_MoveDis
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Properties
    {
    _MainTex("Texture", 2D) = "white"{} // 书页正面纹理
    _BackTex("BackTex", 2D) = "white"{} // 书页背面纹理
    _AngleProgress("AngleProgress", Range(0, 180)) = 0 // 翻页进度
    _WeightX("WeightX", Range(0, 1)) = 0 // x轴收缩程度权重
    _WeightY("WeightY", Range(0, 1)) = 0 // y轴弯曲程度权重
    _WaveLength("WaveLength", Range(0, 3)) = 0 // 波长
    _MoveDis("MoveDis", Float) = 0 // 平移距离,由书页宽度决定
    }
  3. 由于要双面渲染 Cull Off

    1
    2
    3
    4
    5
    6
    SubShader
    {
    Tags { "RenderType"="Opaque" }
    Cull Off // 正反都要渲染
    Pass { /*...*/ }
    }
  4. 由于要使用 VFACE​ 语义,加入编译指令 #pragma target 3.0

    1
    2
    3
    4
    5
    6
    7
    8
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    #pragma target 3.0

    #include "UnityCG.cginc"
    // ...
    ENDCG
  5. 结构体

    顶点和UV

    1
    2
    3
    4
    5
    struct v2f
    {
    float2 uv : TEXCOORD0;
    float4 vertex : SV_POSITION;
    };
  6. 顶点着色器

    • 先实现基本的翻页效果

      1. 利用 sincos()​ 函数得到当前翻页精度对应的 sin​ 和 cos​ 值
      2. 基于得到的值,构建旋转矩阵
      3. 旋转前先平移
      4. 基于旋转矩阵进行顶点旋转
      5. 旋转结束再平移回去
      6. 将处理完毕的顶点转换到裁剪空间中
      7. UV 赋值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      v2f o;
      // 利用sincos函数,配合自定义参数,翻页的角度(进度)来得到对应的sin和cos值
      float sinValue;
      float cosValue;
      sincos(radians(_AngleProgress), sinValue, cosValue);
      // 构建Z轴旋转矩阵
      float4x4 rotationM = {
      cosValue, -sinValue, 0, 0,
      sinValue, cosValue, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
      };
      // 因为是基于左侧绕Z轴旋转的,因此我们将其按照X轴方向进行平移,旋转完毕后在平移回来
      v.vertex += float4(_MoveDis, 0, 0, 0);
      float4 pos = mul(rotationM, v.vertex);
      pos -= float4(_MoveDis, 0, 0, 0);

      o.vertex = UnityObjectToClipPos(pos);
      o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    • 然后实现起伏效果,将起伏相关的计算加入到旋转矩阵之前

      1
      2
      3
      4
      // 进行起伏效果
      float weight = 1 - abs(90 - _AngleProgress) / 90; // 起伏权重计算
      v.vertex.y += sin(v.vertex.x * _WaveLength) * weight * _WeightY; // Y轴上下起伏
      v.vertex.x -= v.vertex.x * weight * _WeightX; // X轴权重
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    v2f vert (appdata_base v)
    {
    v2f o;
    // 利用sincos函数,配合自定义参数,翻页的角度(进度)来得到对应的sin和cos值
    float sinValue;
    float cosValue;
    sincos(radians(_AngleProgress), sinValue, cosValue);
    // 构建Z轴旋转矩阵
    float4x4 rotationM = {
    cosValue, -sinValue, 0, 0,
    sinValue, cosValue, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
    };

    // 进行起伏效果
    float weight = 1 - abs(90 - _AngleProgress) / 90; // 起伏权重计算
    v.vertex.y += sin(v.vertex.x * _WaveLength) * weight * _WeightY; // Y轴上下起伏
    v.vertex.x -= v.vertex.x * weight * _WeightX; // X轴权重

    // 因为是基于左侧绕Z轴旋转的,因此我们将其按照X轴方向进行平移,旋转完毕后在平移回来
    v.vertex += float4(_MoveDis, 0, 0, 0);
    float4 pos = mul(rotationM, v.vertex);
    pos -= float4(_MoveDis, 0, 0, 0);

    o.vertex = UnityObjectToClipPos(pos);
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);

    return o;
    }
  7. 片元着色器

    通过 VFACE​ 语义判断正反面进行对应的采样即可

    1
    2
    3
    4
    5
    6
    fixed4 frag (v2f i, fixed face : VFACE) : SV_Target
    {
    // 通过face来进行正反面判断,因为face的语义是VFACE,因此Unity Shader会自动传入对应的参数
    fixed4 color = face > 0 ? tex2D(_MainTex, i.uv) : tex2D(_BackTex, i.uv);
    return color;
    }

显示效果(旋转角度60度,x轴收缩程度权重,y轴弯曲程度权重都是0.25,波长为0.75,平移距离为5):

image

可见,随着旋转角度的变化,书页本身呈现出翻页旋转效果,且具有起伏感,之后可以通过 C# 代码去控制翻页角度