Unity翻页Shader

参考链接:Android自定义View——从零开始实现书籍翻页效果(一)

一,区域划分

二,关键点定义

三,关键点公式

a是可滑动,的定义为 a = (x,y)
f点固定不变的,定义为f = (width,height)
g定义为a和f的中点,定义为g = ((a.x+f.x)/2,(a.y+f.y)/2))
eh我们设置为af的垂直平分线,有
e = (g.x - (f.y - g.y) * (f.y - g.y) / (f.x - g.x), f.y)
h = (f.x, g.y - (f.x - g.x) * (f.x - g.x) / (f.y - g.y))
曲线cdb是起点为c,控制点为e,终点为b的二阶贝塞尔曲线,有
c = (e.x - (f.x - e.x) / 2, f.y)
b = get_intersection_point(line(ae), line(cj))
d = ((c.x + 2e.x + b.x) / 4, (c.y + 2e.y + b.y) / 4
曲线kij是起点为k,控制点为h,终点为j的二阶贝塞尔曲线,有
j = (f.x, h.y - (f.y - h.y) / 2)
k = get_intersection_point(line(ah), line(cj))
i = ((j.x + 2 h.x + k.x) / 4, (j.y + 2h.y +k.y) / 4)

四,区域公式

B区域
最下一层,最先绘制

area(B) = line(上边界) + line (右边界) + line (下边界) + line(左边界)
C区域
  1. 区域C理论上应该是由点a,b,d,i,k连接而成的闭合区域,但由于d和i是曲线上的点,我们没办法直接从d出发通过path绘制路径连接b点(i,k同理)
  2. 区域C是 由直线ab,bd,dj,ik,ak连接而成的区域 减去 与区域A交集部分 后剩余的区域
    3.所以先绘制C,然后用A覆盖掉C
area(C) = line(id) + line (db) + line (ba) + line(a,k) + line(k,i)
A区域

可以通过曲线定返回,最后绘制

area(A) = line(fc) + quad (cb,e) + line(ba) + line(ak) + quad(kj,h) +line(jf)

五,Shader

Shader "Custom/PageTurnShader"
{
    Properties
    {
        main_tex ("Texture", 2D) = "white" {}
        progress_x ("Progress_x", Range(0, 1)) = 0
        progress_y ("Progress_y", Range(0, 1)) = 0
        is_up ("Is Up", Int) = 0
    }
    SubShader
    {
        Tags
        {
            "Queue"="Transparent" "RenderType"="Transparent"
        }
        LOD 200

        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2_f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D main_tex;
            float progress_x, progress_y;
            int is_up;

            v2_f vert(appdata v)
            {
                v2_f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            //获取两条相交线段的相交点
            float2 get_intersection_point(float2 line_one_v1, float2 line_one_v2, float2 line_two_v1, float2 line_two_v2)
            {
                const float x1 = line_one_v1.x;
                const float y1 = line_one_v1.y;
                const float x2 = line_one_v2.x;
                const float y2 = line_one_v2.y;
                const float x3 = line_two_v1.x;
                const float y3 = line_two_v1.y;
                const float x4 = line_two_v2.x;
                const float y4 = line_two_v2.y;

                float denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
                // 检查直线是否平行
                if (denominator == 0)
                {
                    denominator = 0.00001;
                }

                const float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
                float point_x = x1 + ua * (x2 - x1);
                float point_y = y1 + ua * (y2 - y1);
                return float2(point_x, point_y);
            }

            //二次贝塞尔曲线上给定参数 t(取值范围为 0 到 1)对应的点位置。它接受三个控制点 p0(起点),p2(终点),p1(控制点)通过计算贝塞尔曲线方程得出相应位置的点。
            float2 quadratic_bezier_curve(const float2 start, const float2 control, const float2 end, const float index)
            {
                const float u = 1.0 - index;
                const float tt = index * index;
                const float uu = u * u;

                float2 p = uu * start;
                p += 2.0 * u * index * control;
                p += tt * end;

                return p;
            }

            //判断点是否在线上
            bool intersects(float2 line_p1, float2 line_p2, float2 p)
            {
                const float dy = line_p2.y - line_p1.y;
                if (dy == 0)
                {
                    // 线段是水平的,判断是否在两端点之间
                    return p.y == line_p1.y && p.x >= min(line_p1.x, line_p2.x) && p.x <= max(line_p1.x, line_p2.x);
                }

                const float dx = line_p2.x - line_p1.x;
                const float slope = dx / dy;

                if (line_p1.y > p.y != line_p2.y > p.y && p.x < (p.y - line_p1.y) * slope + line_p1.x)
                {
                    return true;
                }

                return false;
            }

            //判断点是否在区域A
            bool is_in_a(const float2 f, const float2 c, const float2 b, const float2 e, const float2 a, const float2 k, const float2 j,
                         const float2 h, const float2 uv_size)
            {
                // 在区域A内的点数
                uint num_points_inside = 0;

                if (intersects(f, c, uv_size))
                {
                    num_points_inside++;
                }
                // 计算曲线上的像素位置
                float2 pos = c;
                for (float index = 0.0; index <= 1.0; index += 0.01)
                {
                    const float2 pos1 = quadratic_bezier_curve(c, e, b, index);
                    if (intersects(pos, pos1, uv_size))
                    {
                        num_points_inside++;
                    }
                    pos = pos1;
                }

                if (intersects(b, a, uv_size))
                {
                    num_points_inside++;
                }

                if (intersects(a, k, uv_size))
                {
                    num_points_inside++;
                }

                pos = k;
                for (float index = 0.0; index <= 1.0; index += 0.01)
                {
                    const float2 pos1 = quadratic_bezier_curve(k, h, j, index);
                    if (intersects(pos, pos1, uv_size))
                    {
                        num_points_inside++;
                    }
                    pos = pos1;
                }

                if (intersects(j, f, uv_size))
                {
                    num_points_inside++;
                }

                return num_points_inside % 2 == 0;
            }

            // 判断是否在区域B
            bool is_in_b(const float2 f, const float2 c, const float2 j, const float2 uv_size)
            {
                uint num_points_inside = 0;

                if (intersects(j, f, uv_size))
                {
                    num_points_inside++;
                }
                if (intersects(f, c, uv_size))
                {
                    num_points_inside++;
                }
                if (intersects(c, j, uv_size))
                {
                    num_points_inside++;
                }

                return num_points_inside % 2 == 1;
            }

            //判断是否在区域C
            bool is_in_c(const float2 i, const float2 d, const float2 b, const float2 a, const float2 k, const float2 uv_size)
            {
                uint num_points_inside = 0;

                if (intersects(i, d, uv_size))
                {
                    num_points_inside++;
                }
                if (intersects(d, b, uv_size))
                {
                    num_points_inside++;
                }
                if (intersects(b, a, uv_size))
                {
                    num_points_inside++;
                }
                if (intersects(a, k, uv_size))
                {
                    num_points_inside++;
                }
                if (intersects(k, i, uv_size))
                {
                    num_points_inside++;
                }

                return num_points_inside % 2 == 1;
            }

            fixed4 frag(const v2_f current) : SV_Target
            {
                // 获取纹理坐标
                const float2 uv = current.uv;
                fixed4 col = tex2D(main_tex, uv);
                fixed4 no_change_col = col;
                //获取屏幕坐标
                float2 screen_size = _ScreenParams.xy;
                const float screen_width = screen_size.x;
                const float screen_height = screen_size.y;
                const float2 uv_size = float2(uv.x * screen_width, uv.y * screen_height);

                /////////////////////////////////////////////////
                //通过改变a点来翻页
                const float ax = progress_x;
                const float ay = progress_y;

                float2 a, f, g, e, h, c, j, b, k, d, i;

                a = float2(screen_width * ax, screen_height * ay);

                f = screen_size;
                if (is_up > 0)
                {
                    f = float2(screen_width, 0);
                }

                g = (a + f) / 2.0;
                float fg_x = f.x - g.x;
                if (fg_x == 0)
                {
                    fg_x = 0.0001; // 避免除数为 0 的情况
                }
                e.x = g.x - (f.y - g.y) * (f.y - g.y) / fg_x;
                e.y = f.y;
                h.x = f.x;
                float fg_y = f.y - g.y;
                if (fg_y == 0)
                {
                    // 避免除数为 0 的情况,
                    if (f.y == 0)//f.y这时不能大于0
                    {
                        fg_y = -0.0001;
                    }
                    else
                    {
                        fg_y = 0.0001;
                    }
                }
                h.y = g.y - (f.x - g.x) * (f.x - g.x) / fg_y;
                c.x = e.x - (f.x - e.x) / 2.0;
                c.y = f.y;
                j.x = f.x;
                j.y = h.y - (f.y - h.y) / 2.0;
                b = get_intersection_point(a, e, c, j);
                k = get_intersection_point(a, h, c, j);
                d.x = (c.x + 2.0 * e.x + b.x) / 4.0;
                d.y = (2.0 * e.y + c.y + b.y) / 4.0;
                i.x = (j.x + 2.0 * h.x + k.x) / 4.0;
                i.y = (2.0 * h.y + j.y + k.y) / 4.0;

                
                const bool is_in_region_a = is_in_a(f, c, b, e, a, k, j, h, uv_size);
                const bool is_in_region_b = true;
                const bool is_in_region_c = is_in_c(i, d, b, a, k, uv_size);

                if (is_in_region_b)
                {
                    col = fixed4(0.0, 0.0, 0.0, 0.0);
                }

                if (is_in_region_c)
                {
                    col = fixed4(1.0, 1.0, 1.0, 1.0);
                }

                if (is_in_region_a)
                {
                    col = no_change_col;
                }
                return col;
            }
            ENDCG
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,022评论 6 528
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,760评论 3 412
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,687评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,318评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,112评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,645评论 1 320
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,724评论 3 435
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,885评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,387评论 1 330
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,368评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,506评论 1 365
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,063评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,759评论 3 343
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,150评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,414评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,080评论 3 386
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,494评论 2 370

推荐阅读更多精彩内容