通过一个小Trick实现shader的像素识别/统计操作
2018/12/14日补充:后来发现compute shader里用AppendStructuredBuffer可以解决这类问题,请看这里:https://www.cnblogs.com/hont/p/10122129.html
1.简介
在日常开发中会遇到诸如判断某张图的某颜色像素百分比占多少的问题,由于gpu运算并行的原因并不能对其进行累加操作。网上一些针对此类问题
的做法是将一张大图分成多个小块逐步处理并逐步合并,保留关键像素的向下采样:

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

用一组顶点去读单个像素,判断失败的顶点坐标提交到屏幕外,而判断成功的顶点坐标放在屏幕内。
最后在CPU中获取是否有屏幕内顶点这样一个结果,来进行简单的识别操作。
而在开启透明之后,还可以用透明度叠加来获取更复杂的结果。
2.实践
首先实践结果并没有想象的那么好,因为如果纯用三角面来做顶点部分的判断未免太费效率了。
所以我改成了传入顶点判断并生成面的方式,并且缩小了传入图片的像素大小。
Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, );
毕竟更多的运用场合是用来做刮刮卡或者擦除的识别。只需要检测mask图片。
上代码:
Shader "Hidden/FooShader"
{
Properties
{
}
SubShader
{
Blend One One tags
{
"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); half y = floor(vid / _ImageSize.x);
half x = (vid - y * _ImageSize.x) / _ImageSize.x;
y = y / _ImageSize.y; o.vertex = ; float4 image_col = tex2Dlod(_Image, half4(x,y,,)); if (all(image_col.rgb == half3(, , )))
//if (all(image_col.rgb == half3(0, 1, 1))) /*error*/
{
o.color = ;
}
else
{
o.color = ;
} return o;
} [maxvertexcount()]
void geom(point v2f vertElement[], inout TriangleStream<v2f> triStream)
{
if (vertElement[].color.r <= ) return; float size = ; float4 v1 = vertElement[].vertex + float4(-size, -size, , );
float4 v2 = vertElement[].vertex + float4(-size, size, , );
float4 v3 = vertElement[].vertex + float4(size, -size, , );
float4 v4 = vertElement[].vertex + float4(size, size, , ); v2f r = (v2f); r.vertex = mul(UNITY_MATRIX_VP, v1);
r.color = vertElement[].color;
triStream.Append(r); r.vertex = mul(UNITY_MATRIX_VP, v2);
r.color = vertElement[].color;
triStream.Append(r); r.vertex = mul(UNITY_MATRIX_VP, v3);
r.color = vertElement[].color;
triStream.Append(r); r.vertex = mul(UNITY_MATRIX_VP, v4);
r.color = vertElement[].color;
triStream.Append(r);
} fixed4 frag(v2f i) : SV_Target
{
return i.color;
}
ENDCG
}
}
}
FooShader.shader
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering; namespace Hont
{
public class Foo : MonoBehaviour
{
void Start()
{
var blueTex = new Texture2D(, );
for (int x = ; x < blueTex.width; x++)
for (int y = ; 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();
var tempRT = RenderTexture.GetTemporary(, , , RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, );
tempRT.filterMode = FilterMode.Point;
tempRT.autoGenerateMips = false;
tempRT.anisoLevel = ;
tempRT.wrapMode = TextureWrapMode.Clamp;
var cacheRT = RenderTexture.active;
RenderTexture.active = tempRT;
Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, );
var tex2D = new Texture2D(, , TextureFormat.ARGB32, false, false);
tex2D.wrapMode = TextureWrapMode.Clamp;
tex2D.anisoLevel = ;
tex2D.filterMode = FilterMode.Point;
tex2D.ReadPixels(new Rect(, , , ), , );
var firstPixel = tex2D.GetPixel(, );
Debug.Log("firstPixel: " + firstPixel);
RenderTexture.active = cacheRT;
RenderTexture.ReleaseTemporary(tempRT);
}
}
}
Foo.cs
跑了一下代码之后我发现了三个问题,也是没解决的问题,一个是计算结果有误差
o.color = float4(0.05, , , );
输出是0.05结果却有一些出入。
特别是当返回颜色小于0.1之后,我尝试改变图像格式或者RT等参数依旧没能解决
第二个问题是开启透明后,透明图片的叠加是有上限的,毕竟深度有限,堆叠二十多层后,后面层会丢失。
第三个问题是传入图片尺寸过大直接导致带宽爆炸,以至于unity直接假死了,512x512的图片就是26万多的像素要处理,也就是26万多的顶点。
第三个问题很好解决,控制图片尺寸+让单个顶点采样更多像素即可。
对于第一个问题,目前还不需要太精确所以没解决但也能用。第二个问题可以用一些方法来缓解
比如在顶点shader中增加运算量,把返回值分散到rgba四个通道上去。
uint roll = (roll_width + roll_height) % ; if (roll == )
result = float4(GAIN_VALUE, , , ); if (roll == )
result = float4(, GAIN_VALUE, , ); if (roll == )
result = float4(, , GAIN_VALUE, ); if (roll == )
result = float4(, , , GAIN_VALUE);
把更多的像素遍历放入顶点中,这样处理图片的顶点数量是原大小/n:
v2f vert(uint vid : SV_VertexID)
{
v2f o = (v2f); o.vertex = ; 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 = ; rx < GRID_SIZE_X; rx++)
{
for (half ry = ; ry < GRID_SIZE_Y; ry++)
{
half xx = x + rx;
half yy = y + ry; float4 r = Statistics_sample(_Image, _Rec_Color, half4(xx, yy, , ), image_size); o.color += r;
}
}
//一个顶点处理多个像素 return o;
}
3.测试结果
最终达到了一个比较不错的结果,我把相关函数封装成了一个类。
我写了一个涂抹效果demo来测试一下,它通过识别白色像素的数量来判断是否为全部涂完:

工程文件我丢在了github上: https://github.com/hont127/Image-Rec-Base-unity-shader-
通过这个小Trick其实可以在像素里返回更多的信息,简单的场合这么还是比较方便的,当然一些复杂的情况分块或者配合computer shader来做其实更合适。
通过一个小Trick实现shader的像素识别/统计操作的更多相关文章
- 一个小 Trick
平方变两次 一个状态 \(S\) 有一个贡献,所有状态 \(S\) 组成集合 \(U\) . 然后我们要统计下面这个东西 \[ans=\sum_{S\in U}f^2(S) \] 然后我们就可以看作是 ...
- Python2 下 Unicode 的一个小bug
关于Python的编码问题已经是老生常谈了,此处主要是介绍一个罕见的问题,也算是Python2的一个bug了(Python3不会有此问题). 在有时候我们去爬取网页或者调用一些第三方库获取文本的时候, ...
- 教你用webgl快速创建一个小世界
收录待用,修改转载已取得腾讯云授权 作者:TAT.vorshen Webgl的魅力在于可以创造一个自己的3D世界,但相比较canvas2D来说,除了物体的移动旋转变换完全依赖矩阵增加了复杂度,就连生成 ...
- 一些计数小Trick
一些计数小Trick 虽然说计数问题如果不是特别傻逼的话想做出来基本随缘. 但是掌握一些基本的计数方法还是十分有必要的. 想到了就更新. 1. 对于排列的DP问题,一般是不能够按照位置一个一个放的,一 ...
- 语法上的小trick
语法上的小trick 构造函数 虽然不写构造函数也是可以的,但是可能会开翻车,所以还是写上吧.: 提供三种写法: 使用的时候只用: 注意,这里的A[i]=gg(3,3,3)的"gg&qu ...
- file_put_contens小trick
file_put_contents tricks 0x01 trick1 来自于P神的实例: <?php $text = $_GET['text']; if(preg_match('[<& ...
- 像素 PIXEL 图片的基本单位 像素非常小 图片是成千上万的像素组成 显示/屏幕分辨率 (DPI 屏幕分辨率)
像素 PIXEL 图片的基本单位 像素非常小 图片是成千上万的像素组成 显示/屏幕分辨率 (DPI 屏幕分辨率) 图像分辨率 (PPI) 1920*1080是像素点长度1920个像素点 X1080个像 ...
- Python multiprocessing 基础使用和小trick
最近进行数据预处理时(噪声插入),单进程严重影响实验周期,故学习了multiprocessing并发执行不同数据集的处理,加快执行效率.现于此进行一些简单记录以供日后参考. 1. 基础: From m ...
- Redola.Rpc 的一个小目标
Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Complete requests: 20000 ...
随机推荐
- C# 委托简单例子
public delegate double Delegate_Prod(int a,int b); class Class1 { static double fn_Prodvalues(int va ...
- 如何在github上搭建网站?
3年前就想写这篇文章了,一直没写,拖到现在,迟到总比不到好,哈哈.github pages只支持静态博客(html,css,js),不支持服务端(php,physon). 一.尝试一下 1.在电脑上安 ...
- 版本控制系统-SVN(1)
1. SVN介绍 1.1. 简介 SVN(subversion),版本管理工具,与CVS一样,SVN是一个可跨平台的开源版本控制系统,Subversion管理随时间变化的数据.这些数据都被放置在 ...
- word表分页表头
选中表,右键-表格属性. 调出表格属性对话框. 选择行,在各页顶端以标题行形式重复出现划上钩确定.
- 上一篇是copy了整理网上的内容,这篇是一个推荐
因为公司用的 轮播组件是 aui 的 ui库 这个轮播有样式单一等等很多的问题.... 包括滑动BUG(自己也踩了一个小坑,高度和宽度问题...需要设定好,滑动提示按钮点 丑陋............ ...
- React Native小白入门学习路径——四
上一周一直在弄着老师布置的各种任务,再加上要做的都还没做完.所以上周的总结到现在才写完发出来..真的觉得老师太高估我们这些菜鸡了. 也许是我太着急了,在自己还没有足够储备去完成自己的项目,总觉得先找一 ...
- 更换本地Git全局账户命令
git config --global user.name gstok git config --global user.email @qq.com
- 在npm上发布一个自己的包
1.首先你要在npm上创建一个账号,这里需要输入邮箱的,注意激活邮箱否则无法publish自己的包 2.在本地创建一个文件夹,输入npm init初始化项目,这里是我使用npm init创建的pack ...
- Set集合架构和常用实现类的源码分析以及实例应用
说明:Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的). (01) Set 是继承于Collection的接口.它是一个不允许 ...
- JAVA分词包
自然语言处理 中文分词 词性标注 命名实体识别 依存句法分析 关键词提取 自动摘要 短语提取 拼音 简繁转换 http://www.hankcs.com/nlp/ https://github.com ...