哈哈SE7 发表于 2021-2-11 15:36

使用Unity写一个那啥APP

前言:前些天在网上偶然找到了一个国外的AI抠图网站,发现抠图效果贼好。随便上传一张人物图,发现连头发丝都抠出来了,相当的方便又惊艳。后面又找到了它家的视频AI抠像,效果就要稍微差点,对视频画面相对严苛一些。于是我这时有了想法:或许是时候写个当年一直想要的那个APP了。
项目地址:
不要想多了,看下下面的视频就明白了(工作的时候运行,赏心悦目,也不吃资源)。
动图版的效果,镂空gif使用的是国外某AI视频抠图网站
其实就是一个在屏幕指定位置置顶显示图像视频的一个工具。但是它有一个必须满足的功能:能置顶并且能完全控制自己显示的内容(不想显示的部分能全透),另外最好能方便的控制自己的显示效果,比如添加特效,动画等。其实满足硬需求的话可能使用winform更好(更省资源,原生窗体),但是需要后面的附加特性,那我第一个想到的就是Unity了。
废话不多说,开始正文:
实现这个APP,主要分为3步:
Unity窗口去边框,指定区域全透,鼠标穿透效果,置顶。
首先要实现的当然是最核心的功能。去边框,鼠标穿透,置顶比较简单,直接使用的win32api。参考下面:
//去边框
private const int WS_BORDER = 0x00800000;
private const int WS_CAPTION = 0x00C00000;
private const int GWL_STYLE = -16;

SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_BORDER & ~WS_CAPTION);

//置顶
if (Input.GetKeyDown(KeyCode.T))
      {
            if (isTopMost)
            {
                syswin.TopMost = false;
                isTopMost = false;
                ApManager.Instance.Tip = "取消置顶";
            }
            else
            {
                syswin.TopMost = true;
                isTopMost = true;
                AppManager.Instance.Tip = "置顶";
            }
      }

//鼠标穿透
public void MakePenetrate()
{
    SetWindowLong(this._hwnd, -20, (int)(0x00080000));
    SetWindowLong(this._hwnd, -20, (int)(0x00000020) | (int)(0x00080000));
}这里重点提下指定区域全透明的实现。我第一时间想到的是winform的颜色键:只需要指定要抠除的目标颜色,就可以从窗口中将颜色为这个颜色的区域抠除,并且鼠标能够穿透。但是试了后发现效果并不好——抠不干净,效果比较糟糕。于是我重新查找方案,后来还真找到了,效果相当不错!代码很简单:
var margins = new MARGINS() { cxLeftWidth = -1 };
DwmExtendFrameIntoClientArea(hwnd, ref margins);它使用的是Dwmapi.dll(Desktop Window Manager)下的方法。详细资料见(写的很详细):
执行前面部分的操作后,我们只需要控制Unity的颜色输出就能精确控制窗口最终显示的内容(后面会讲到)。
这一步的所有API,包括其它一些常用的Win32api我都封装到了dll中,方便后面调用。
这个dll中我封装了一些常用的win32api,方便调用
Unity控制逻辑。
控制逻辑部分。作为这么一个用来愉悦身心的工具,一些基本的功能当然是少不了的。包括外部图片加载,图片切换,各种效果开关,窗口位置调整,透明度调整等。同时,为了扩展用途,我额外还添加了对gif和视频文件(视频暂时不能透明)的支持。其中特效的切换和卡片特效留下了相应的接口方便后面扩展(毕竟要愉悦身心,特效啥的是重点)。
特效部分。
特效部分是这个工具的一个重点。不过当前只是粗略的完成了几个简单的效果,并留了相应的接口。后续特效部分会持续加入。
淡入淡出,飞入飞出两个切换特效比较简单,不再赘述(具体参看最前面的工程链接)。接下来提下这个半透阴影的实现。如图:
鼠标移动时,阴影会相应改变方向
前面讲到,要控制窗口的部分透明只需要控制Unity输出的颜色值。在这之前需要先设置一下Camera的属性:
设置相机为纯色背景,背景色(0,0,0,0)
我们要显示的图片已经是经过AI抠图完成后的带透明通道的图像,所以简单输出就能实现前景的效果。而后面的半透的阴影其实是在图片显示之前用另一个Pass绘制出来的(没有使用任何光照,省!)。具体参看下面的代码:
c#部分:
设置鼠标相对于图片的位置mpos
shader部分关键代码:
Shader "Custom/Unlit/Transparent"
{
        Properties
        {
                _MainTex ("Texture", 2D) = "black" {}
                _Mask("Mask",2D) = "white"{}
                _Color("Color",Color) =(1,1,1,1)
        }
        SubShader
        {
                Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
                LOD 100
                Blend SrcAlpha OneMinusSrcAlpha

                CGINCLUDE

                #include "UnityCG.cginc"

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

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

                sampler2D _MainTex;
                sampler2D _Mask;
                float4 _MainTex_ST;
                fixed4 _Color;
                uniform float flipflag;
                uniform float cutoutflag;
                uniform float4 mpos;

                v2f vertback(appdata v)
                {
                        v2f o;
                        //AVPro播放视频时画面会翻转,这里进行校正
                        if (flipflag > 0)
                        {
                                v.uv.y = 1 - v.uv.y;
                        }

                        //mpos:鼠标位置相对于图片的位置,以图片中心为零点
                        float2 vec = v.vertex.xy - mpos.xy;
                        //对显示图片的网格顶点进行缩放
                        v.vertex.xy = mpos.xy + vec * 1.05;
                        o.vertex = UnityObjectToClipPos(v.vertex);
                        o.uv = v.uv;
                        return o;
                }

                v2f vert(appdata v)
                {
                        v2f o;

                        if (flipflag > 0)
                        {
                                v.uv.y = 1 - v.uv.y;
                        }

                        o.vertex = UnityObjectToClipPos(v.vertex);
                        o.uv = v.uv;
                        return o;
                }

                fixed4 fragBack(v2f i):SV_Target
                {
                        fixed4 col = tex2D(_MainTex, i.uv);
                        fixed mask = tex2D(_Mask, i.uv).r;
                        col.a *= mask;
                        //alpha为0的地方全黑
                        col.rgb = lerp(fixed3(0,0,0),fixed3(0.05, 0.05, 0.05),col.a);
                        col.a *= 0.6;
                        return col * _Color;
                }

                fixed4 frag(v2f i) : SV_Target
                {
                        fixed4 col = tex2D(_MainTex, i.uv);
                        fixed mask = tex2D(_Mask, i.uv).r;
                        col.a *= mask;
                        return col * _Color;
                }
                ENDCG

                //绘制阴影
                Pass
                {
                        offset 1,1
                        CGPROGRAM
                        #pragma vertex vertback
                        #pragma fragment fragBack

                        ENDCG

                }

                //绘制彩图
                Pass
                {
                        CGPROGRAM
                        #pragma vertex vert
                        #pragma fragment frag

                        ENDCG
                       
                }
        }
}

楚一帆 发表于 2021-2-11 15:38

确实,不考虑3D动效,还是winform最简单
页: [1]
查看完整版本: 使用Unity写一个那啥APP