通过一个小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 ...
随机推荐
- html 知识点
web服务本质 import socket def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind ...
- python urllib 库
urllib模块中的方法 1.urllib.urlopen(url[,data[,proxies]]) 打开一个url的方法,返回一个文件对象,然后可以进行类似文件对象的操作.本例试着打开google ...
- 如何在github上搭建网站?
3年前就想写这篇文章了,一直没写,拖到现在,迟到总比不到好,哈哈.github pages只支持静态博客(html,css,js),不支持服务端(php,physon). 一.尝试一下 1.在电脑上安 ...
- DOS简单文件指令
DOS和Windows最大的不同在于DOS命令方式操作,所以使用者需要记住大量命令及其格式使用方法,DOS命令分为内部命令和外部命令, 内部命令是随每次启动的COMMAND.COM装入并常驻内存,而外 ...
- mybatis自动生成service、dao、mapper
1.config.properties ## 数据库连接参数 jdbc.driverClass = com.mysql.jdbc.Driver jdbc.url = jdbc:mysql://loca ...
- 潭州课堂25班:Ph201805201 django框架 第四课 模板常用标签,模板继承与引用,自定义过渡器 (课堂笔记)
if 语句 判断传入的 name 值 建好这些文件后,对 url 进行配置 在浏览器中访问 for 循环 页面跳转: 通过 name 跳转时要在 urls 文件中为该 path 设置 name 带参 ...
- 摆脱CSS浏览器私有属性-moz, -ms, -webkit
为了兼容各个浏览器之间的私有属性,前端开发人员在写css的时候需要给一些css属性添加多个私有前缀,非常麻烦.这里给大家分享一个简单的方法可以让你以后无需手动给CSS添加私有属性. -moz代表fir ...
- 游戏UI规范
在满足效果的前提下,尽量做到UI资源做到复用和最小化 1. 背景1和背景2分开切,可以组合成各种不同的面包背景图 2. 背景1和背景2在没有花纹的情况下,中间纯色的部分切4个像素做就公共个缩放就可 ...
- keystone 安装随笔
keystone 代码库 git clone https://git.openstack.org/openstack/keystone.git cd keystone keystone配置文件 etc ...
- jQuery 学习03——HTML:捕获、设置、添加元素、删除元素、CSS类、CSS()方法、尺寸
jQuery - 获取内容text().html() 以及 val()和属性attr() jQuery 中非常重要的部分,就是操作 DOM 的能力. DOM = Document Object Mod ...