个人博客查看。
原文查看。


通过一个小Trick实现shader的像素识别/统计操

1.简介

将一张大图分成多个小块逐步处理并逐步合并,保留关键像素的向下采样:

但我在思考一种更简便的方法,于是想到在顶点shader里做判断检测,在像素shader里获取结果这样一个形式:

用一组顶点去读单个像素,判断失败的顶点坐标提交到屏幕外,而判断成功的顶点坐标放在屏幕内。

最后在CPU中获取是否有屏幕内顶点这样一个结果,来进行简单的识别操作。

而在开启透明之后,还可以用透明度叠加来获取更复杂的结果。

2.实践

首先实践结果并没有想象的那么好,因为如果纯用三角面来做顶点部分的判断未免太费效率了。

所以我改成了传入顶点判断并生成面的方式,并且缩小了传入图片的像素大小。

Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, 1);

毕竟更多的运用场合是用来做刮刮卡或者擦除的识别。只需要检测mask图片。

上代码:

Shader "Hidden/FooShader"
{Properties{}SubShader{Blend One Onetags{"Queue" = "Transparent""RenderType" = "Transparent"}Pass{CGPROGRAM#pragma target 4.0#pragma vertex vert#pragma geometry geom#pragma fragment frag#include "UnityCG.cginc"struct v2f{float4 color : COLOR;float4 vertex : SV_POSITION;};sampler2D _Image;float4 _ImageSize;v2f vert(uint vid : SV_VertexID){v2f o = (v2f)0;half y = floor(vid / _ImageSize.x);half x = (vid - y * _ImageSize.x) / _ImageSize.x;y = y / _ImageSize.y;o.vertex = 0;float4 image_col = tex2Dlod(_Image, half4(x,y,0,0));if (all(image_col.rgb == half3(0, 0, 1)))//if (all(image_col.rgb == half3(0, 1, 1)))    /*error*/{o.color = 1;}else{o.color = 0;}return o;}[maxvertexcount(4)]void geom(point v2f vertElement[1], inout TriangleStream<v2f> triStream){if (vertElement[0].color.r <= 0) return;float size = 10;float4 v1 = vertElement[0].vertex + float4(-size, -size, 0, 0);float4 v2 = vertElement[0].vertex + float4(-size, size, 0, 0);float4 v3 = vertElement[0].vertex + float4(size, -size, 0, 0);float4 v4 = vertElement[0].vertex + float4(size, size, 0, 0);v2f r = (v2f)0;r.vertex = mul(UNITY_MATRIX_VP, v1);r.color = vertElement[0].color;triStream.Append(r);r.vertex = mul(UNITY_MATRIX_VP, v2);r.color = vertElement[0].color;triStream.Append(r);r.vertex = mul(UNITY_MATRIX_VP, v3);r.color = vertElement[0].color;triStream.Append(r);r.vertex = mul(UNITY_MATRIX_VP, v4);r.color = vertElement[0].color;triStream.Append(r);}fixed4 frag(v2f i) : SV_Target{return i.color;}ENDCG}}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;namespace Hont
{public class Foo : MonoBehaviour{void Start(){var blueTex = new Texture2D(64, 64);for (int x = 0; x < blueTex.width; x++)for (int y = 0; y < blueTex.height; y++)blueTex.SetPixel(x, y, Color.blue);blueTex.Apply();var mat = new Material(Shader.Find("Hidden/FooShader"));mat.SetTexture("_Image", blueTex);mat.SetVector("_ImageSize", new Vector4(blueTex.width, blueTex.height));mat.SetPass(0);var tempRT = RenderTexture.GetTemporary(16, 16, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1);tempRT.filterMode = FilterMode.Point;tempRT.autoGenerateMips = false;tempRT.anisoLevel = 0;tempRT.wrapMode = TextureWrapMode.Clamp;var cacheRT = RenderTexture.active;RenderTexture.active = tempRT;Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, 1);var tex2D = new Texture2D(16, 16, TextureFormat.ARGB32, false, false);tex2D.wrapMode = TextureWrapMode.Clamp;tex2D.anisoLevel = 0;tex2D.filterMode = FilterMode.Point;tex2D.ReadPixels(new Rect(0, 0, 16, 16), 0, 0);var firstPixel = tex2D.GetPixel(0, 0);Debug.Log("firstPixel: " + firstPixel);RenderTexture.active = cacheRT;RenderTexture.ReleaseTemporary(tempRT);}}
}

跑了一下代码之后我发现了三个问题,也是没解决的问题,一个是计算结果有误差

o.color = float4(0.05, 0, 0, 0);

输出是0.05结果却有一些出入。

特别是当返回颜色小于0.1之后,我尝试改变图像格式或者RT等参数依旧没能解决

第二个问题是开启透明后,透明图片的叠加是有上限的,毕竟深度有限,堆叠二十多层后,后面层会丢失。

第三个问题是传入图片尺寸过大直接导致带宽爆炸,以至于unity直接假死了,512x512的图片就是26万多的像素要处理,也就是26万多的顶点。

第三个问题很好解决,控制图片尺寸+让单个顶点采样更多像素即可。

对于第一个问题,目前还不需要太精确所以没解决但也能用。第二个问题可以用一些方法来缓解

比如在顶点shader中增加运算量,把返回值分散到rgba四个通道上去。

uint roll = (roll_width + roll_height) % 4;if (roll == 0)result = float4(GAIN_VALUE, 0, 0, 0);if (roll == 1)result = float4(0, GAIN_VALUE, 0, 0);if (roll == 2)result = float4(0, 0, GAIN_VALUE, 0);if (roll == 3)result = float4(0, 0, 0, GAIN_VALUE);

把更多的像素遍历放入顶点中,这样处理图片的顶点数量是原大小/n:

v2f vert(uint vid : SV_VertexID)
{v2f o = (v2f)0;o.vertex = 0;half2 image_size = half2(GRID_SIZE_X * LOOP_IMAGE_SIZE_X, GRID_SIZE_Y * LOOP_IMAGE_SIZE_Y);half y = floor(vid / LOOP_IMAGE_SIZE_X);half x = (vid - y * LOOP_IMAGE_SIZE_X) / LOOP_IMAGE_SIZE_X;y = y / LOOP_IMAGE_SIZE_Y;//将vid转化为x,y坐标for (half rx = 0; rx < GRID_SIZE_X; rx++){for (half ry = 0; ry < GRID_SIZE_Y; ry++){half xx = x + rx;half yy = y + ry;float4 r = Statistics_sample(_Image, _Rec_Color, half4(xx, yy, 0, 0), image_size);o.color += r;}}//一个顶点处理多个像素return o;
}

3.测试结果

最终达到了一个比较不错的结果,我把相关函数封装成了一个类。

我写了一个涂抹效果demo来测试一下,它通过识别白色像素的数量来判断是否为全部涂完:

工程文件我丢在了github上: https://github.com/hont127/Image-Rec-Base-unity-shader-

UGUI 中通过改变像素实现擦除

这种方法需要改精灵的设置,如下:

话不多说,直接上代码:

using System.Collections.Generic;
using System.Reflection.Emit;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Profiling;
using UnityEngine.UI;public class ChangeTexturePixel : MonoBehaviour, IDragHandler
{/// <summary> 擦除的像素数量 </summary>private int m_PixelAcount = 0;/// <summary> 是否擦除成功 </summary>private bool m_IsDrag = false;/// <summary> 擦除范围大小 </summary>[SerializeField][Range(10,100)]private int Radius = 10;/// <summary> 擦除完成度(不超过1)</summary>[SerializeField][Range(0,1)] private float m_Complete; private RawImage m_UITex;private Texture2D m_MyTex;[SerializeField]private Color m_Col = Color.clear;private int[][] m_PixelArray;private Dictionary<int, TexturePixel> m_TexPixelDic = new Dictionary<int, TexturePixel>();void Start(){m_IsDrag = false;m_UITex = GetComponent<RawImage>();var tex = m_UITex.texture as Texture2D;m_MyTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32,false); m_MyTex.SetPixels(tex.GetPixels());m_MyTex.Apply();m_UITex.texture = m_MyTex;int value = 0;m_PixelArray = new int[m_MyTex.width][];for (int i = 0; i < m_PixelArray.Length; i++){m_PixelArray[i] = new int[m_MyTex.height];for (int j = 0; j < m_MyTex.height; j++){m_PixelArray[i][j] = value;m_TexPixelDic.Add(value, new TexturePixel(m_MyTex, i, j));value++;}}}/// <summary>///  改变Texture2D像素点颜色/// </summary>/// <param name="x">Texture2D像素点X轴位置</param>/// <param name="y">Texture2D像素点Y轴位置</param>/// <param name="radius">改变像素的范围</param>/// <param name="col">改变后的颜色</param>void ChangePixelColorByCircle(int x, int y, int radius, Color col){for (int i = -Radius; i < Radius; i++){var py = y + i;if (py < 0 || py >= m_MyTex.height){continue;}for (int j = -Radius; j < Radius; j++){var px = x + j;if (px < 0 || px >= m_MyTex.width){continue;}if (new Vector2(px - x, py - y).magnitude > Radius){continue;}Profiler.BeginSample("text1");TexturePixel tp; //= texPixelDic[pixelArray[MyTex.width - 1][py]];if (px == 0){tp = m_TexPixelDic[m_PixelArray[m_MyTex.width - 1][py]];tp.Scratch(m_Col);}tp = m_TexPixelDic[m_PixelArray[px][py]];if (!tp.GetPixel()){m_PixelAcount++;}tp.Scratch(m_Col);Profiler.EndSample();}}Profiler.BeginSample("text2");m_MyTex.Apply();Profiler.EndSample();Profiler.BeginSample("text3");Profiler.EndSample();}/// <summary>///  擦除点/// </summary>/// <param name="mousePos">鼠标位置</param>/// <returns>擦除点</returns>Vector2 ScreenPoint2Pixel(Vector2 mousePos){float imageWidth = m_UITex.rectTransform.sizeDelta.x;float imageHeight = m_UITex.rectTransform.sizeDelta.y;Vector3 imagePos = m_UITex.rectTransform.anchoredPosition3D;//求鼠标在image上的位置float HorizontalPercent =(mousePos.x - (Screen.width / 2 + imagePos.x - imageWidth / 2)) / imageWidth; //鼠标在Image 水平上的位置  %float verticalPercent =(mousePos.y - (Screen.height / 2 + imagePos.y - imageHeight / 2)) / imageHeight; //鼠标在Image 垂直上的位置  %float x = HorizontalPercent * m_MyTex.width;float y = verticalPercent * m_MyTex.height;return new Vector2(x, y);}/// <summary>///  拖拽中。。。/// </summary>/// <param name="eventData">拖拽数据</param>public void OnDrag(PointerEventData eventData){if (!m_IsDrag){var posA = ScreenPoint2Pixel(eventData.position);ChangePixelColorByCircle((int) posA.x, (int) posA.y, Radius, m_Col);SetAllPixelFadeAlpha();}}/// <summary>///  擦除完成时调用/// </summary>public void SetAllPixelFadeAlpha(){if (++m_PixelAcount >= m_MyTex.height*m_MyTex.width*m_Complete){  m_UITex.color = Color.clear;m_IsDrag = true;Debug.Log("擦除完成");}}
}
sing System.Collections;
using System.Collections.Generic;
using UnityEngine;public class TexturePixel
{public Texture2D myTex;//float alpha = 1;        //当前透明度// int scratchedTime = 0;//被刮的次数private int x;      //像素坐标Xprivate int y;      //像素坐标Y//private bool scratcedPrevious = false;//private bool scratcedCurrent = false;public TexturePixel(Texture2D tex,int x,int y){myTex = tex;this.x = x;this.y = y;}public void Scratch( Color targetCol){myTex.SetPixel(x,y,targetCol);//  scratcedCurrent = true;//Debug.Log("x:"+x+"  y:"+y+"  a "+ targetCol.a);}public bool GetPixel(){Color color =  myTex.GetPixel(x, y);return color.a <= 0;}

以上方法是通过改变Texture2D像素点颜色实现擦除,主要内容:

 // 设置像素点myTex.SetPixel(x,y,targetCol);// 获取像素点myTex.GetPixel(x,y,targetCol);

项目文件我放在了gitee: https://gitee.com/ondaly/eraser_-master.git
项目案例查看。

Unity 中实现擦除功能相关推荐

  1. [Unity] Unity中实现羽化功能的shader

    GLSL->ShaderLab,原GLSL代码来自http://blog.csdn.net/panda1234lee/article/details/52199296, 由于shaderLab的 ...

  2. Photon在unity中的使用

    ps:期末老师要求的一篇3000字的玩具级别论文,基本是官网和api手册原本就有的.基本我就是个搬运工,不知道有什么用,就先扔上来了.用的是writage,让word转为markdown,可能会出现一 ...

  3. Unity中发送邮件

    Unity中发送邮件 在Unity中发送邮件的功能: 最近在项目中需要使用到把指定的文件发送到目标邮箱的功能,在这里记录一下,方便以后使用. 注意事项 我们在Unity中发送邮件时,需要一个邮箱和对应 ...

  4. Unity中实现涂鸦和橡皮擦功能

    一.目的 1.想知道:Unity中实现涂鸦和橡皮擦功能 二.参考 1.Unity 实现橡皮擦效果 https://www.cnblogs.com/lzzhentou/p/11634696.html 总 ...

  5. 【Unity】在Unity中实现扫描二维码 生成二维码功能

    在Unity中使用二维码扫描功能需要我们在Unty中导入扫描库 下载地址:https://github.com/micjahn/ZXing.Net/releases 然后编写扫描脚本: 先在脚本上添加 ...

  6. 小功能⭐️Unity中利用材质自发光实现物体闪烁效果

    文章目录 本文基于VDer的文章<Unity中利用材质自发光实现物体闪烁效果>延伸开发 在实现了具有一个Material的物体闪烁发光之后,延伸开发了具有多个Material的自闪烁效果, ...

  7. 在unity中内置一个查询物流信息功能

    项目需求,在unity中内置查询物流信息的功能 需要用到查询物流 的API 在这选择的是快递100的API 首先需要申请快递100的API,官方会给你一个KEY,使用该KEY,就可以进行物流查询了 u ...

  8. 在unity中如何实现视频播放暂停停止重播功能

    在Unity中实现视频播放.暂停.停止和重播功能,可以通过以下步骤实现: 将视频文件导入Unity项目中,并将其设置为资源. 创建一个新的Unity游戏对象,并将Video Player组件添加到该对 ...

  9. Unity中常用的单例模式、对象池的脚本模板,连按退出和滑动翻页或放大缩小的功能实现,以及属性在代码中的灵活使用

    1.单例模式的脚本模板: Unity中针对一些常用的manager可以使用单例模式,用于统一的功能管理: //普通单例,不需要继承MonoBehavior,不用挂载在GameObject上 publi ...

最新文章

  1. 【图像分类案例】(1) ResNeXt 交通标志四分类,附Tensorflow完整代码
  2. 自定义ProgressBar
  3. 更新pip到指定版本
  4. 路由器无法访问目标网络_初设路由器,无法访问管理页面的解决办法_网络设备技术应用...
  5. html表格高度适应屏幕,Table的自适应高度
  6. mac 不能连接wi-fi_如何在Mac OS X中查看当前的Wi-Fi连接速度
  7. Python 小白从零开始 PyQt5 项目实战(1)安装与环境配置
  8. 如何使用VS2017将客户端库添加到ASP.NET CORE 2.2(简单方法)
  9. 淘宝潜规折射出的人性
  10. Systemd管理示例
  11. 标记集合 java编译_深入理解Java虚拟机读书笔记-java编译期和运行期优化
  12. 安卓java模拟器按键精灵,安卓按键精灵实现后台
  13. python泰坦尼克号生存预测论文_python泰坦尼克号生存预测
  14. NXP JN5169 ZigBee 3.0开发环境搭建
  15. ios 越狱后 重启springboard 命令
  16. win10系统cpu内核或逻辑核心缺少缺少,解决办法
  17. 实习生快速入手项目php,2019.7最惨的三次面试经历-----百度PHP实习生面经
  18. 新能源汽车——动力电池
  19. 2022危险化学品经营单位主要负责人考试试题及在线模拟考试
  20. 计算机钥匙英语,计算机加锁--把U盘变成打开电脑的钥匙 - 信息科学 - 小木虫 - 学术 科研 互动社区...

热门文章

  1. backtracking及其应用2
  2. SET社会工程学攻击
  3. IT 行业最宜居的城市是哪里?
  4. 新西兰计算机预科学费多少钱,留学新西兰预科学费
  5. 奶爸日记-好好弹钢琴的保证书
  6. nvm环境安装和 node 的基本使用
  7. [译]借助函数完成可组合的数据类型(软件编写)(第十部分)
  8. 2020校招美团点评笔试
  9. 南邮 OJ 1055 叙拉古猜想
  10. redis基础篇——内存回收