当一个圆在地面上沿直线匀速滚动时,圆上固定点的运动轨迹称为旋轮线(或摆线、圆滚线)。本文实现的卷轴特效使用了旋轮线相关理论。
以下是卷轴特效原理及公式推导,将屏幕坐标 (x) 映射到纹理坐标 (u)。

注意:屏幕坐标 x 值域为 [0, ScreenWidth],这里已归一化到 [0, 1]。
本文代码资源见→Unity3D Shader卷轴滚动特效。
RollEffect.cs
- using UnityEngine;
-
- [RequireComponent(typeof(Camera))] // 屏幕后处理特效一般都需要绑定在像机上
- public class RollEffect : MonoBehaviour {
- public float radius = 0.05f; // 圆半径
- public float rollSpeed = 0.8f; // 圆滚动角速度
- private Texture rollTex; // 滚动轴纹理
- private Texture backTex; // 底部背景纹理
- private float rollTime = 0; // 滚动时间
- private float maxRollTime; // 最长滚动时间
- private float rollDirection = 1; // 滚动方向(1: 向右, -1: 向左)
- private Material rollMaterial; // 滚动特效材质
- private bool enableRoll = false; // 滚动特效开关
-
- private void Awake() {
- rollMaterial = new Material(Shader.Find("Custom/Curl/Roll"));
- rollMaterial.hideFlags = HideFlags.DontSave;
- rollTex = Resources.Load
("RollTex"); - backTex = Resources.Load
("BackTex"); - }
-
- private void Update() {
- if (Input.GetMouseButton(0)) {
- rollTime = 0;
- maxRollTime = 1 / rollSpeed / radius;
- enableRoll = true;
- }
- }
-
- private void OnRenderImage (RenderTexture source, RenderTexture destination) {
- if (enableRoll) {
- rollMaterial.SetTexture("_RollTex", rollTex);
- rollMaterial.SetTexture("_BackTex", backTex);
- rollMaterial.SetFloat("_theta", rollSpeed);
- rollMaterial.SetFloat("_r", radius);
- rollMaterial.SetFloat("_t", rollTime);
- IncreaseTime();
- Graphics.Blit (source, destination, rollMaterial);
- } else {
- Graphics.Blit (source, destination);
- }
- }
-
- private void IncreaseTime() { // 时间自增
- rollTime += rollDirection * Time.deltaTime;
- if (rollTime > maxRollTime) {
- rollTime = maxRollTime;
- rollDirection = -rollDirection; // 反向卷轴
- } else if (rollTime < 0) {
- rollTime = 0;
- rollDirection = -rollDirection;
- }
- }
- }
说明: RollEffect 脚本组件需要挂在相机上。
Roll.shader
- Shader "Custom/Curl/Roll"
- {
- Properties
- {
- _MainTex ("mainTex", 2D) = "white" {}
- _RollTex ("rollTex", 2D) = "white" {}
- _BackTex ("backTex", 2D) = "white" {}
- }
-
- SubShader
- {
- Pass
- {
- ZTest Always
- Cull Off
- ZWrite Off
- Fog { Mode off }
-
- CGPROGRAM
-
- #pragma vertex vert_img // UnityCG.cginc中定义了vert_img方法, 对vertex和texcoord进行了处理, 输出v2f_img中的pos和uv
- #pragma fragment frag
- #pragma fragmentoption ARB_precision_hint_fastest
-
- #include "UnityCG.cginc"
-
- sampler2D _MainTex;
- sampler2D _RollTex; // 滚动轴纹理
- sampler2D _BackTex; // 底部背景纹理
- float _theta; // 圆滚动角速度
- float _r; // 圆半径
- float _t; // 滚动时间
-
- fixed4 roll(float rho, float v)
- { // 滚动变换, 将屏幕坐标映射到纹理坐标
- float trt = _theta * _r * _t;
- if (rho < trt - _r)
- {
- return tex2D(_BackTex, float2(rho, v));
- }
- else if (rho < trt)
- {
- float a = trt - rho;
- float phi = acos(a / _r);
- float u = trt - (UNITY_HALF_PI + phi) * _r;
- if (u > 0)
- {
- return tex2D(_RollTex, float2(u, v)) * pow(sin(phi), 2);
- }
- u = trt - (UNITY_HALF_PI - phi) * _r;
- return tex2D(_MainTex, float2(u, v)); // 刚开始卷动时会触发
- }
- else if (rho < trt + _r)
- {
- float a = rho - trt;
- float phi = acos(a / _r);
- float u = trt - (3 * UNITY_HALF_PI - phi) * _r;
- if (u > 0)
- {
- return tex2D(_RollTex, float2(u, v)) * pow(sin(phi), 2);
- }
- return tex2D(_MainTex, float2(rho, v)); // 刚开始卷动时会触发
- }
- else
- {
- return tex2D(_MainTex, float2(rho, v));
- }
- }
-
- fixed4 frag(v2f_img i) : SV_Target // uv坐标的计算不能在顶点着色器中进行, 因为屏后处理的顶点只有屏幕的4个角顶点
- {
- return roll(i.pos.x / _ScreenParams.x, i.uv.y);
- }
-
- ENDCG
- }
- }
-
- Fallback off
- }
