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

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

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

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

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

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

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

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

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

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

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

       可以看出几种判断方法,如果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);
    }
}

       效果如下:

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

        解释一下:

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

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

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

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

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

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

       从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中计算,不然就不需要消耗那么多性能,效果如图:

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

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

      

            

C for Graphic:删格绘制直线和多边形C for Graphic:删格绘制直线和多边形 羊羊2035 发布了100 篇原创文章 · 获赞 101 · 访问量 10万+ 私信 关注
上一篇:storm的并发度


下一篇:StarWink UV顶点动画制作详解