C for Graphic:删格绘制直线和多边形

        最近年底没什么事情,要过年了嘛,放个大年假。我又捡起基础数学书看了,本来一直在看微分定积分偏微分计算,不过目前对于我来说应用场景不多,比如定积分和偏微分,在求面体积和数值分析方面大有可为,领域偏向于工业和研究领域,我还没想到怎么应用到我的日常开发中,如果我做教学软件就好了,数学施教类应该用的挺多(当然高级别的着色效果也用到微积分,主要还是我太水,知识深度和应用深度都还不够,估计还得要几年吧,反正我也不急,慢慢混,哈哈)。

        刚刚拿着我的三角尺和自动铅笔听小说发呆,突然想起来怎么用shader写一个绘制三角形的功能,或者我们就通用一点,写个绘制多边形的功能。平时我们用数学描叙一个三角形或者多边形,就描叙其顶点,眨一看挺简单的。

       不过图形学中,我们目前使用光删渲染管线,要实际显示在像素矩阵显示屏上,不仅仅是描叙数学符号而已。一个最实际的问题:如果一个网格的顶点和三角面通过MVP和透视除法和视图变化到屏幕像素矩阵,怎么绘制出来,我们假设屏幕二维坐标系中已经变换好了三(n)个顶点的坐标,怎么绘制出来三角(多边)形,这个也是删格化的处理方式。

      

       我们前面已经知道显示屏绘制就是逐行逐列扫描,那么意味着我们可以判断像素点(0,0)到(m,n)是否在多边形内即可,当然我们肯定要优化一下算法的,判断从全屏幕改为多边形的外接矩形。

        前面我们也写过了判断点在多边形中的计算方法,这里为了顺便扩展一下数学计算,我们用另外的判断方法:判断点在线段的两侧的哪一侧,我们假设P点在多边形内部,那么P点在多边形任意一条边(顺时针或逆时针)的同一侧,画个图:

                   

        假设我们按照顺时针,那么P点就在任意一条线段的右侧。

        那么我们只需要判断点于直线的两侧关系了,再来一张图:

      

       可以看出几种判断方法,如果P在OE线段所在直线的右侧:

       1.角POX小于角EOX

       2.OE和OP的差积朝向纸内(右手定则)

       3.建立OE的直线方程L:Ax+By+C=0,将P.x和P.y带入L,得到结果D>0或D<0则表示P在哪两侧(以x轴为参考,一四象限内:D<0在右侧,二三象限内:D>0则在右侧,但是如果P于E不在同一象限,则判断情况根据y轴正负划分)

       4.OP的斜率<OE的斜率(不过存在特殊情况,如果OE在一象限,OP在三象限OE的右侧,则OP的斜率>OE的斜率,余下不同象限多种判断情况)

       这样我们就建立了计算方法了(我就用向量差积z分量去判断吧),下面开始写shader:

Shader "Custom/GridDrawImageEffectShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma exclude_renderers d3d11 gles
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

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

            sampler2D _MainTex;

            uniform vector points[8];
            uniform int pointCount;

            //isright是否正确
            //只计算差积z分量就ok
            bool isRight(float2 o, float2 e, float2 p)
            {
                float3 oe = float3(e.x - o.x, e.y - o.y, 0);
                float3 op = float3(p.x - o.x, p.y - o.y, 0);
                float z = oe.x * op.y - oe.y * op.x;
                if (z < 0)
                    return true;
                return false;
            }

            float minfloat(float farr[8],int len)
            {
                float min = 10000;
                for(int i =0;i<len;i++)
                {
                    if(min>farr[i])
                    {
                        min = farr[i];
                    }
                }
                return min;
            }

            float maxfloat(float farr[8],int len)
            {
                float max = 0;
                for(int i =0;i<len;i++)
                {
                    if(max < farr[i])
                    {
                        max = farr[i];
                    }
                }
                return max;
            }


            //uv pixel是否在外接矩形内
            bool isRectangle(float2 uv)
            {
                float2 pp = float2(uv.x * _ScreenParams.x,uv.y*_ScreenParams.y);
                float xarr[8];
                float yarr[8];
                for(int i = 0;i<pointCount;i++)
                {
                    float2 p = points[i];
                    xarr[i] = p.x;
                    yarr[i] = p.y;
                }
                float left = minfloat(xarr,pointCount);
                float right = maxfloat(xarr,pointCount);
                float bottom = minfloat(yarr,pointCount);
                float top = maxfloat(yarr,pointCount);
                if(pp.x<left || pp.x>right || pp.y<bottom || pp.y>top)
                {
                    return false;   
                }
                return true;
            }

            //顺时针判断uv pixel是否在多边形内
            bool isInside(float2 uv)
            {
                float2 pp = float2(uv.x * _ScreenParams.x,uv.y*_ScreenParams.y);
                for(int i = 0;i<pointCount;i++)
                {
                    float2 fp = float2(points[i].x,points[i].y);
                    float2 tp;
                    if(i<(pointCount-1))
                    {
                        tp = float2(points[i+1].x,points[i+1].y);
                    }else
                    {
                        tp = float2(points[0].x,points[0].y);    
                    }
                    if(!isRight(fp,tp,pp))
                    {
                        return false;
                    }
                }
                return true;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col;
                if(isRectangle(i.uv) )
                {
                    if(isInside(i.uv))
                        col = fixed4(0,1,0,1);
                    else
                        col = fixed4(0,0,1,1);
                }
                else
                {
                    col = fixed4(0,0,0,1);
                }
                return col;
            }
            ENDCG
        }
    }
}

        c#代码(相当于提交dc):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GridEffect : MonoBehaviour
{
    [SerializeField] private Material material;

    void Start()
    {
        List<Vector4> points = new List<Vector4>
        {
            new Vector4(400,80,0,0),
            new Vector4(200,300,0,0),
            new Vector4(500,800,0,0),
            new Vector4(700,400,0,0),
            new Vector4(600,70,0,0),
        };
        material.SetInt("pointCount", points.Count);
        material.SetVectorArray("points", points.ToArray());
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, destination, material);
    }
}

       效果如下:

 

        解释一下:

        1.计算点与线段两侧关系

       2.计算外接矩形,绘制外接矩形蓝色

       3.逐行逐列扫描uv(也就是像素)绘制多边形绿色

       这个也是删格化的常用算法之一(我代码肯定写的不如大佬们高效,哈哈)。

       下面继续,既然多边形绘制完毕,再尝试一下线段,其实线段的绘制和多边形一样就是删格化绘制,不同的是, 线段只有两个顶点,我们需要将顶点扩展成矩形,因为是删格化所以我们线段有宽度(不仅仅只是数学符号),就可以计算出线段组成的矩形的四个顶点,如图:

 

       从P0P1两点扩展出adcb顺时针四个矩形顶点,求出四个点的坐标然后再绘制多边形区域就行了,计算垂线以前也算过,不清楚的话可以返回之前查看,这里直接上代码:

void Start()
    {
        

        Vector4 from = new Vector4(400, 80, 0, 0);
        Vector4 to = new Vector4(700, 400, 0, 0);

        Vector4 sta = new Vector4(from.x, from.y);
        Vector4 end = new Vector4(to.x, to.y);
        float k = (end.y - sta.y) / (end.x - sta.x);
        float m = halfWidth / Mathf.Sqrt(1 + k * k);
        float n = Mathf.Abs(k) * halfWidth / Mathf.Sqrt(1 + k * k);
#if UNITY_EDITOR
        Debug.LogFormat("n={0} m={1}", n, m);
#endif
        Vector4 a = sta + new Vector4(-n, m);
        Vector4 b = sta + new Vector4(n, -m);
        Vector4 c = end + new Vector4(n, -m);
        Vector4 d = end + new Vector4(-n, m);
#if UNITY_EDITOR
        Debug.LogFormat("a={0} b={1} c={2} d={3}", a, b, c, d);
#endif
        //顺时针
        List<Vector4> points = new List<Vector4>
        {
            a,d,c,b
        };
        material.SetInt("pointCount", points.Count);
        material.SetVectorArray("points", points.ToArray());

    }

         这里我直接用c# start计算直线顶点了,如果需要动态变化就使用update或者shader中计算,不然就不需要消耗那么多性能,效果如图:

   

         这些二维计算有助于我们理解删格化,同时应用范围也算是挺广的,ok,继续撸数学。

         ps:增加斜率不同情况下的线段矩形顶点计算

         首先,从左下角0,0到右上角width,height重新确定一下from和to的顶点坐标,然后根据横线/竖线/斜线(斜率从负到正)算出顺时针的顶点。

    //获取从00-11的conner参数
    private Vector4 GetOriginCorner(Vector4 f, Vector4 t)
    {
        float fx, fy, tx, ty;
        if (Mathf.Approximately(f.x, t.x))   //判断x是否相似,则为竖线
        {
            fy = f.y > t.y ? t.y : f.y;
            ty = f.y > t.y ? f.y : t.y;
            tx = f.y > t.y ? t.x : f.x;
            fx = f.y > t.y ? f.x : t.x;
        }
        else if (Mathf.Approximately(f.y, t.y))  //判断y相似,则为横线
        {
            fx = f.x > t.x ? t.x : f.x;
            tx = f.x > t.x ? f.x : t.x;
            fy = f.x > t.x ? t.y : f.y;
            ty = f.x > t.x ? f.y : t.y;
        }
        else  //斜线,斜率范围+到-
        {
            fx = f.x > t.x ? t.x : f.x;
            tx = f.x > t.x ? f.x : t.x;
            fy = f.x > t.x ? t.y : f.y;
            ty = f.x > t.x ? f.y : t.y;
        }
#if UNITY_EDITOR
        Debug.LogFormat("vector = {0}", new Vector4(fx, fy, tx, ty));
#endif
        return new Vector4(fx, fy, tx, ty);
    }

    private void Update()
    {
        Vector4 from = new Vector4(100, 600, 0, 0);
        Vector4 to = new Vector4(500, 200, 0, 0);

        //以00为原点,从左至右
        Vector4 corner = GetOriginCorner(from, to);
        float lx = corner.x;
        float ly = corner.y;
        float rx = corner.z;
        float ry = corner.w;
        Vector4 sta = new Vector4(lx, ly);
        Vector4 end = new Vector4(rx, ry);
        float k = (end.y - sta.y) / (end.x - sta.x);
        float n, m, ik;
        if (k == 0)         //横线
        {
            n = 0;
            m = halfWidth;
            ik = 0;
        }
        else if (float.IsInfinity(k))  //竖线
        {
            n = halfWidth;
            m = 0;
            ik = -1;
        }
        else            //斜线
        {
            n = Mathf.Abs(k) * halfWidth / Mathf.Sqrt(1 + k * k);
            m = halfWidth / Mathf.Sqrt(1 + k * k);
            ik = (-1 / k) > 0 ? 1 : -1;
        }
#if UNITY_EDITOR
        Debug.LogFormat("n={0} m={1}", n, m);
#endif
        Vector4 a = sta + new Vector4(ik * n, m);
        Vector4 b = sta + new Vector4(-ik * n, -m);
        Vector4 c = end + new Vector4(-ik * n, -m);
        Vector4 d = end + new Vector4(ik * n, m);
#if UNITY_EDITOR
        Debug.LogFormat("a={0} b={1} c={2} d={3}", a, b, c, d);
#endif
        //顺时针
        List<Vector4> points = new List<Vector4>
        {
            a,d,c,b
        };
        material.SetInt("pointCount", points.Count);
        material.SetVectorArray("points", points.ToArray());
    }

            

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/104007555