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的像素识别/统计操作的更多相关文章

  1. 一个小 Trick

    平方变两次 一个状态 \(S\) 有一个贡献,所有状态 \(S\) 组成集合 \(U\) . 然后我们要统计下面这个东西 \[ans=\sum_{S\in U}f^2(S) \] 然后我们就可以看作是 ...

  2. Python2 下 Unicode 的一个小bug

    关于Python的编码问题已经是老生常谈了,此处主要是介绍一个罕见的问题,也算是Python2的一个bug了(Python3不会有此问题). 在有时候我们去爬取网页或者调用一些第三方库获取文本的时候, ...

  3. 教你用webgl快速创建一个小世界

    收录待用,修改转载已取得腾讯云授权 作者:TAT.vorshen Webgl的魅力在于可以创造一个自己的3D世界,但相比较canvas2D来说,除了物体的移动旋转变换完全依赖矩阵增加了复杂度,就连生成 ...

  4. 一些计数小Trick

    一些计数小Trick 虽然说计数问题如果不是特别傻逼的话想做出来基本随缘. 但是掌握一些基本的计数方法还是十分有必要的. 想到了就更新. 1. 对于排列的DP问题,一般是不能够按照位置一个一个放的,一 ...

  5. 语法上的小trick

    语法上的小trick 构造函数 虽然不写构造函数也是可以的,但是可能会开翻车,所以还是写上吧.: 提供三种写法: ​ 使用的时候只用: 注意,这里的A[i]=gg(3,3,3)的"gg&qu ...

  6. file_put_contens小trick

    file_put_contents tricks 0x01 trick1 来自于P神的实例: <?php $text = $_GET['text']; if(preg_match('[<& ...

  7. 像素 PIXEL 图片的基本单位 像素非常小 图片是成千上万的像素组成 显示/屏幕分辨率 (DPI 屏幕分辨率)

    像素 PIXEL 图片的基本单位 像素非常小 图片是成千上万的像素组成 显示/屏幕分辨率 (DPI 屏幕分辨率) 图像分辨率 (PPI) 1920*1080是像素点长度1920个像素点 X1080个像 ...

  8. Python multiprocessing 基础使用和小trick

    最近进行数据预处理时(噪声插入),单进程严重影响实验周期,故学习了multiprocessing并发执行不同数据集的处理,加快执行效率.现于此进行一些简单记录以供日后参考. 1. 基础: From m ...

  9. Redola.Rpc 的一个小目标

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Complete requests: 20000 ...

随机推荐

  1. ASP.NET MVC 常用路由总结

    1.URL模式 路由系统用一组路由来实现它的功能,这些路由共同组成了应用系统URL架构或方案,这种URL架构是应用程序能够识别并能对之做出响应的一组URL,当处理一个输入 请求时,路由系统的工作是将这 ...

  2. SQL 分隔字符串

    ALTER FUNCTION dbo.fn_Split ( ), ) ) RETURNS @table_Value TABLE ( SortNo ,) NOT NULL, Value ) COLLAT ...

  3. 在Eclipse中使用Junit进行单元测试

    单元测试与Junit4 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证.单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离 ...

  4. 鼠标hover时图片效果

    <!DOCTYPE html><head><meta http-equiv="Content-Type" content="text/htm ...

  5. 146. 大小写转换 II

    146. Lowercase to Uppercase II Description Implement an upper method to convert all characters in a ...

  6. [iOS]应用与视图的生命周期和方法调用

    1.应用程序的生命周期: AppDelegate类在应用生命周期的不同阶 会回调不同的方法. 视图push到了子界面,然后子界面pop回原界面的时候,会启用viewWillAppear以及之后的几个生 ...

  7. BZOJ.5285.[AHOI/HNOI2018]寻宝游戏(思路 按位计算 基数排序..)

    BZOJ LOJ 洛谷 话说vae去年的专辑就叫寻宝游戏诶 只有我去搜Mystery Hunt和infinite corridor了吗... 同样按位考虑,假设\(m=1\). 我们要在一堆\(01\ ...

  8. Node.js API快速入门

    Node.js API 快速入门 一.事件EventEmitter const EventEmitter = require('events'); class MyEmitter extends Ev ...

  9. 设置 IntelliJ IDEA 智能提醒时忽略大小写

    1.Ctrl + Alt + S 打开设置界面(或者从菜单File - settings 进来) 2.在搜索框输入Completion定位到Code Completion功能项 3.在右侧配置界面选中 ...

  10. 解决IDEA Springboot项目sql文件打开提示No data sources are configured to run this SQL and provide advanced的问题

    Idea2018的Springboot项目,如果里面有.sql文件,打开后,会提示"No data sources are configured to run this SQL and pr ...