1.简介
在日常开发中会遇到诸如判断某张图的红色百分比占多少的问题,由于gpu运算并行的原因并不能对其进行累加操作。网上一些针对此类问题
的做法是将一张大图分成多个小块逐步处理并逐步合并:
但我在思考一种更简便的方法,于是想到在顶点shader里做判断检测,在像素shader里获取结果这样一个形式:
用一组顶点去读单个像素,判断失败的顶点坐标提交到屏幕外,而判断成功的顶点坐标放在屏幕内。
最后在CPU中获取是否有屏幕内顶点这样一个结果,来进行简单的识别操作。
而在开启透明之后,还可以用透明度叠加来获取更复杂的结果。
2.实践
首先实践结果并没有想象的那么好,因为如果纯用三角面来做顶点部分的判断未免太费效率了。
所以我改成了传入顶点判断并生成面的方式,并且缩小了传入图片的像素大小。
毕竟更多的运用场合是用来做刮刮卡或者擦除的识别。只需要检测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;
};
v2f vert(uint vid : SV_VertexID)
{
v2f o = (v2f)0;
o.vertex = 0; int roll = vid % 4; if(roll == 0)
o.color = float4(0.05, 0, 0, 0); if (roll == 1)
o.color = float4(0, 0.05, 0, 0); if (roll == 2)
o.color = float4(0, 0, 0.05, 0); if (roll == 3)
o.color = float4(0, 0, 0, 0.05); return o;
}
[maxvertexcount(4)] void geom(point v2f vertElement[1], inout TriangleStream<v2f> triStream)
{ 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
}
}
}FooShader.shader
using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Rendering;namespace Hont
{ public class Foo : MonoBehaviour
{ void Start()
{ var mat = new Material(Shader.Find("Hidden/FooShader"));
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, 10, 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);
}
}
}Foo.cs
跑了一下代码之后我发现了两个问题,也是没解决的问题,一个是计算结果有误差
o.color = float4(0.05, 0, 0, 0);
输出是0.05结果却有一些出入。
特别是当返回颜色小于0.1之后,我尝试改变图像格式或者RT等参数依旧没能解决
第二个问题是开启透明后,透明图片的叠加是有上限的,毕竟深度有限。
对于第一个问题,目前还不需要太精确所以没解决但也能用。第二个问题可以用一些方法来缓解
比如在顶点shader中增加运算量,把返回值分散到rgba四个通道上去。
int roll = vid % 4;if(roll == 0) o.color = float4(0.05, 0, 0, 0);if (roll == 1) o.color = float4(0, 0.05, 0, 0);if (roll == 2) o.color = float4(0, 0, 0.05, 0);if (roll == 3) o.color = float4(0, 0, 0, 0.05);return o;
把更多的像素遍历放入顶点中,这样顶点shader处理的图片大小是n/1:
v2f vert(uint vid : SV_VertexID)
{
v2f o = (v2f)0;
o.vertex = 0;
half2 image_size = half2(_SampleFilter.x * _LoopImageSize.x, _SampleFilter.y * _LoopImageSize.y);
half y = floor(vid / _LoopImageSize.x);
half x = (vid - y * _LoopImageSize.x) / _LoopImageSize.x;
y = y / _LoopImageSize.y; for (half rx = 0; rx < _SampleFilter.x; rx++)
{ for (half ry = 0; ry < _SampleFilter.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-
通过这个小Trick其实可以在像素里返回更多的信息,简单的场合这么还是比较方便的,当然一些复杂的情况分块+computer shader来做其实更合适。
共同学习,写下你的评论
评论加载中...
作者其他优质文章





