CSharpGL(24)用ComputeShader实现一个简单的图像边缘检测功能
CSharpGL(24)用ComputeShader实现一个简单的图像边缘检测功能
效果图
这是红宝书里的例子,在这个例子中,下述功能全部登场,因此这个例子可作为使用Compute Shader的典型示例。
★用imageLoad从纹理中读取数据。★
★用imageStore将数据写入纹理。★
★用vertex/fragment shader显示出compute shader的计算结果。★
下面是3个测试用例。



下载
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
Image Processing
渲染结果
先解决简单的问题:把compute shader计算后的结果(一个纹理)显示出来。这用到如下的vertex shader和fragment shader,非常简单。
#version core in vec3 vert;
in vec2 uv;
out vec2 passUV;
uniform mat4 mvp; void main(void)
{
gl_Position = mvp * vec4(vert, 1.0f);
passUV = uv;
}
vertex shader
#version core layout (location = ) out vec4 color;
in vec2 passUV;
layout (binding = ) uniform sampler2D output_image; void main(void)
{
color = texture(output_image, passUV);
}
fragment shader
其模型用一个四边形即可。


边缘检测算法
理论
在一个图像上,什么是边缘?如果相邻的两个像素颜色差别很大,就可以算是边缘。差别越大,就越能被视作边缘。
这个例子实现了一个简单的边缘检测算法,使用一个边缘检测滤波器对输入的图像(作为纹理)进行卷积操作。这个例子中的滤波器是可分离的(separable filter),就是说,可以对多维度空间的各个维度都单独处理。这里,我们将它应用到2维图像上,首先对水平维度进行处理,然后对垂直维度进行处理。
为了实现这个算法,compute shader的每个请求都要处理输入图像的一个像素。它需要读取输入图像的内容,然后减去该像素旁边的采样值。这意味着一个请求要从输入图像中读取2次。
为避免多于的内存访问,这里用一个shared数组来存储输入图形的一行。我们在每个请求中读取输入图像的目标像素,然后存储到shared数组。当所有请求都读取输入图像后,这个shared数组就含有输入图像当前行的所有像素值。之后每个请求都可以直接从此shared数组中读取像素值,这个读取速度是非常快的。
Compute Shader
实现边缘检测算法的compute shader如下。
#version core
// 最大支持宽度为512的图像
layout (local_size_x = , local_size_y = , local_size_z = ) in;
// 要进行检测的图像
layout (rgba32f, binding = ) uniform image2D input_image;
// 检测结果
layout (rgba32f, binding = ) uniform image2D output_image;
// 共享数组,存储当前行的像素
shared vec4 scanline[]; void main(void)
{
// 请求的位置
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
// 读取当前位置的像素
scanline[pos.x] = imageLoad(input_image, pos);
// 等待所有请求都走到这里
barrier();
// 计算边缘值,存储到output_image
vec4 result = scanline[min(pos.x + , )] - scanline[max(pos.x - , )];
// pos.yx:把输出图像翻转,这样就可以使用同一compute shader进行2维卷积。
imageStore(output_image, pos.yx, result);
}
执行
可以看到,上面的compute shader的一个local work group只能处理图像的一个维度上的一行。这一点由这一行代码决定:
layout (local_size_x = , local_size_y = , local_size_z = ) in;
为了处理此维度上的全部行,在调用此compute shader时要这样:
GL.GetDelegateFor<GL.glDispatchCompute>()(, , );
即指定在Y轴上执行512个local work group。这样就完成了在X轴维度上的计算。这时我们得到了一个中间图像intermediate_image。
★从这里可以看到设定local work group和global work group的理由:shader里的local_size_*大小有限,借助glDispatchCompute才能实现更大规模的计算,且更灵活。★
然后要对这个intermediate_image的Y轴维度执行算法。这时你注意到,在上面的compute shader里,我们用
imageStore(output_image, pos.yx, result);
而不是
imageStore(output_image, pos.xy, result);
这是把原图翻转了一下。因此,如果继续对intermediate_image执行上面的compute shader,实际上就实现了对原图在第二个维度上执行此算法。
因此总的计算过程如下。
computeProgram.Bind();
glBindImageTexture(, input_image[], , false, , GL.GL_READ_WRITE, GL.GL_RGBA32F);
glBindImageTexture(, intermediate_image[], , false, , GL.GL_READ_WRITE, GL.GL_RGBA32F);
// 在X轴上执行边缘检测算法
glDispatchCompute(, , );
// 确保所有compute shader请求都执行完成
glMemoryBarrier(GL.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); glBindImageTexture(, intermediate_image[], , false, , GL.GL_READ_WRITE, GL.GL_RGBA32F);
glBindImageTexture(, output_image[], , false, , GL.GL_READ_WRITE, GL.GL_RGBA32F);
// 在Y轴上执行边缘检测算法
glDispatchCompute(, , );
glMemoryBarrier(GL.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
总结
经过这个例子,开始正视创建纹理过程中的各项参数。
原CSharpGL的其他功能(3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。
欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL)
CSharpGL(24)用ComputeShader实现一个简单的图像边缘检测功能的更多相关文章
- CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator
CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator 我还没有用过Compute Shader,所以现在把红宝书里的例子拿来了,加入CSharpGL中. ...
- 完成一段简单的Python程序,用于实现一个简单的加减乘除计算器功能
#!/bin/usr/env python#coding=utf-8'''完成一段简单的Python程序,用于实现一个简单的加减乘除计算器功能'''try: a=int(raw_input(" ...
- 基于PHP实现一个简单的在线聊天功能(轮询ajax )
基于PHP实现一个简单的在线聊天功能(轮询ajax ) 一.总结 1.用的轮询ajax 二.基于PHP实现一个简单的在线聊天功能 一直很想试着做一做这个有意思的功能,感觉复杂的不是数据交互和表结构,麻 ...
- 使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能。并且在界面上有radio 的选择内容也要上传
使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能.并且在界面上有radio 的选择内容也要上传 uploadify 插件的 下载和文档地址 ...
- 数字集成电路设计-8-一个简单sobel图像边缘检测加速器的设计,实现,仿真与综合
引言 图像视频处理等多媒体领域是FPGA应用的最主要的方面之一,边缘检测是图像处理和计算机视觉中的基本问题,所以也是最常用的,随着数据量的不断增加以及对实时性的要求,一般软件已经不能满足实际需要,这时 ...
- EasilyUI的一个简单的拖拽功能
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Main.aspx.cs&quo ...
- 通过WebSocket实现一个简单的聊天室功能
WebSocket WebSocket是一个协议,它是是基于TCP的一种新的网络协议,TCP协议是一种持续性的协议,和HTTP不同的是,它可以在服务器端主动向客户端推送消息.通过这个协议,可以在建立一 ...
- jQuery照片伸缩效应,这不是一个简单的图像缩放,它不影响其它元素的布局
之前在网上看到这样的效果,但我没有收藏夹网址,后来被我不知道如何来实现这种效果. 如今,互联网已收集有关专门.真是功夫不负有心人,被我发现. 我也努力过自己尝试着写: 但仅仅是单纯的图片放大.并且还影 ...
- JavaScript实现一个简单的密码输入功能
常见的密码输入框当输入字符后会被替换成‘*’,而且旁边会有个小眼睛可以查看原本的字符,虽然input标签有这个功能,但这只是自己正在看正则表达式的时候突然想到的,就当做个练习,自己手动实现下: < ...
随机推荐
- 匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密
0x00 前言 在匹夫的上一篇文章<匹夫细说C#:不是“栈类型”的值类型,从生命周期聊存储位置>的最后,匹夫以总结和后记的方式涉及到一部分迭代器的知识.但是觉得还是不够过瘾,很多需要说清楚 ...
- js复杂对象和简单对象的简单转化
var course = { teacher :{ teacherId:001, teacherName:"王" }, course : { courseId : 120, cou ...
- 探索ASP.NET MVC5系列之~~~3.视图篇(下)---包含常用表单和暴力解猜防御
其实任何资料里面的任何知识点都无所谓,都是不重要的,重要的是学习方法,自行摸索的过程(不妥之处欢迎指正) 汇总:http://www.cnblogs.com/dunitian/p/4822808.ht ...
- video.js
1.github地址 2.常用API: class : video-js: video-js应用视频所需的风格.js功能,比如全屏和字幕. vjs-default-skin: vjs-default- ...
- 学习ASP.NET Core, 怎能不了解请求处理管道[5]: 中间件注册可以除了可以使用Startup之外,还可以选择StartupFilter
中间件的注册除了可以借助Startup对象(DelegateStartup或者ConventionBasedStartup)来完成之外,也可以利用另一个叫做StartupFilter的对象来实现.所谓 ...
- Android 7.1 - App Shortcuts
Android 7.1 - App Shortcuts 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Shortcuts 文中如有纰漏,欢迎大家留言 ...
- 编写高质量代码:改善Java程序的151个建议(第6章:枚举和注解___建议88~92)
建议88:用枚举实现工厂方法模式更简洁 工厂方法模式(Factory Method Pattern)是" 创建对象的接口,让子类决定实例化哪一个类,并使一个类的实例化延迟到其它子类" ...
- 和我一起看API(一)你所不知道的LinearLayout补充
楼主英语水平差,翻译的不好的话请多多指正,嘿嘿... A Layout that arranges its children in a single column or a single row. T ...
- 【python之路5】学习小结
一.编程语言 java C语言 C++ C# Python 二.python语言的种类 Cpython:python的官方版本,使用最为广泛,实现将python(py文件)转换为字节码文件(pyc文件 ...
- 第14章 Linux启动管理(2)_启动引导程序grub
2. 启动引导程序grub 2.1 Grub配置文件 (1)grub中分区的表示 硬盘 分区 Linux设备文件名 Grub中设备文件名 第1块SCSI硬盘 第1个主分区 /dev/sda1 hd(0 ...