Unity翻页Shader

2024-05-03  本文已影响0人  CZKGO

参考链接: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
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读