ES基础入门,边界检测

作者:新闻中心

正文

这次的学习重点是Metal的shader语言Metal shading language,主要有两个用途图形渲染和通用计算。Metal着色语言支持部分C 特性,比如说重载(除了声明为图形渲染和通用计算入口的函数);Metal着色语言不支持递归函数调用、new和delete操作符、虚函数、异常处理、函数指针等特性。同样,Metal有自己的标准库,不能用C 11的标准库。Metal中常用的数据结构有向量、矩阵、原子数据类型、缓存、纹理、采样器、数组、用户自定义结构体等,C 的数据结构double, long, unsigned long, long long,unsigned long long, long double均不支持。常用的基础数据类型:

  • half 是16bit是浮点数,表达方式0.5h
  • float 是32bit的浮点数,表达方式0.5f
  • size_t 是64bit的无符号整数,通常用于sizeof的返回值
  • ptrdiff_t 是64bit的有符号整数,通常用于指针的差值

常用的向量数据类型:

  • 一维向量:half2、half3、half4、float2、float3、float4等。
  • 二维向量:half4x4、half3x3、float4x4、float3x3等;

对于向量的访问,比如说vec=float4(1.0f, 1.0f, 1.0f, 1.0f),其访问方式可以是vec[0]、vec[1],也可以是vec.x、vec.y,也可以是vec.r、vec.g。(.xyzw和.rgba,前者对应三维坐标,后者对应RGB颜色空间)同时只取部分、乱序取均可,比如说我们常用到的float4 color=texture.bgra

Metal关键函数用到的指针参数要用地址空间修饰符,如下面的buffer参数

vertex RasterizerData // 返回给片元着色器的结构体vertexShader(uint vertexID [[ vertex_id ]], // vertex_id是顶点shader每次处理的index,用于定位当前的顶点 constant LYVertex *vertexArray [[ buffer { // buffer表明是缓存数据,0是索引

Metal内存访问模式主要有两种:Device和Constant。

  • Device模式,通用的访问模式,使用限制比较少;
  • Constant模式,快速访问只读模式,参数对应buffer大小不能改变;

需要注意的是,有些GPU支持**前置深度测试(early depth testing),其允许在fragment shader之前进行深度测试。如果某个像素点没有被显示,那么可以放弃渲染,以减少运算。Metal同样支持前置深度测试,实现方式是在fragment关键字前面加上[[early_fragment_tests]],且前置深度测试要求不能对像素的深度值进行写操作。

深度测试讲解

基于Sobel算子,对图像进行边界检测。自定义计算shader,接受图像的输入并输出检测后的结果,效果如下:

新葡京8455 1

Sobel算子的实现需要访问像素周边的8个像素的值,在compute shader中,我们可以通过修改grid的xy坐标进行操作。在拿到位置的坐标后,通过sourceTexture.read读取像素值,分别算出横向和竖向的差别h和v,统一转亮度值。最后求h和v的向量和,再写回纹理中。

constant int sobelStep = 2;constant half3 kRec709Luma = half3(0.2126, 0.7152, 0.0722); // 把rgba转成亮度值kernel voidsobelKernel(texture2d<half, access::read> sourceTexture [[texture(LYFragmentTextureIndexTextureSource)]], texture2d<half, access::write> destTexture [[texture(LYFragmentTextureIndexTextureDest)]], uint2 grid [[thread_position_in_grid]]){ /* 行数 9个像素 位置 上 | * * * | | 左 中 右 | 中 | * * * | | 左 中 右 | 下 | * * * | | 左 中 右 | */ half4 topLeft = sourceTexture.read(uint2(grid.x - sobelStep, grid.y - sobelStep)); // 左上 half4 top = sourceTexture.read(uint2(grid.x, grid.y - sobelStep)); // 上 half4 topRight = sourceTexture.read(uint2(grid.x   sobelStep, grid.y - sobelStep)); // 右上 half4 centerLeft = sourceTexture.read(uint2(grid.x - sobelStep, grid.y)); // 中左 half4 centerRight = sourceTexture.read(uint2(grid.x   sobelStep, grid.y)); // 中右 half4 bottomLeft = sourceTexture.read(uint2(grid.x - sobelStep, grid.y   sobelStep)); // 下左 half4 bottom = sourceTexture.read(uint2(grid.x, grid.y   sobelStep)); // 下中 half4 bottomRight = sourceTexture.read(uint2(grid.x   sobelStep, grid.y   sobelStep)); // 下右 half4 h = -topLeft - 2.0 * top - topRight   bottomLeft   2.0 * bottom   bottomRight; // 横方向差别 half4 v = -bottom - 2.0 * centerLeft - topLeft   bottomRight   2.0 * centerRight   topRight; // 竖方向差别 half grayH = dot(h.rgb, kRec709Luma); // 转换成亮度 half grayV = dot(v.rgb, kRec709Luma); // 转换成亮度 // sqrt(h^2   v^2),相当于求点到的距离,所以可以用length half color = length(half2(grayH, grayV)); destTexture.write(half4(color, color, color, 1.0), grid); // 写回对应纹理}

demo中以Camera为图像输入源,实时对每一帧的图像进行处理

新葡京8455 2效果展示

Vertex shader接收多个参数

上边的vertex shader仅接收顶点的位置信息,因此像素颜色都是在fragment shader中写固定的(红色)。而在下边的vertex shader中,通过SourceColor传递像素颜色。

attribute vec4 Position;    // position of vertex
attribute vec4 SourceColor; // color of vertex

varying vec4 DestinationColor; // will pass out to fragment shader

void main(void) {
    DestinationColor = SourceColor;
    gl_Position = Position;
}

1、未声明为attribute的变量即为输出变量(如DestinationColor),将传递给fragment shader。
2、varying表示依据两个顶点的颜色,平滑地计算出顶点之间每个像素的颜色。

对应的fragment shader为:

varying lowp vec4 DestinationColor;

void main(void) {
    gl_FragColor = DestinationColor;
}

这里,fragment shader接收来自vertex shader的变量DestinationColor,赋值给gl_FragColor,再输出至OpenGLES。即每个像素的颜色由DestinationColor决定,这样可在代码中精确控制每个像素的颜色。

通过接收顶点着色器传递过来的纹理坐标v_TexCoord,加上坐标偏移vec2(dx,dy),完成邻近像素点的采样。

1.顶点着色器(vertex_shader)

示例代码:

uniform mat4 uMVPMatrix;                             // 应用程序传入顶点着色器的总变换矩阵
attribute vec4 aPosition;                            // 应用程序传入顶点着色器的顶点位置
attribute vec2 aTextureCoord;                        // 应用程序传入顶点着色器的顶点纹理坐标
attribute vec4 aColor                                // 应用程序传入顶点着色器的顶点颜色变量
varying vec4 vColor                                  // 用于传递给片元着色器的顶点颜色数据
varying vec2 vTextureCoord;                          // 用于传递给片元着色器的顶点纹理数据
void main()
{
    gl_Position = uMVPMatrix * aPosition;            // 根据总变换矩阵计算此次绘制此顶点位置
    vColor = aColor;                                 // 将顶点颜色数据传入片元着色器
    vTextureCoord = aTextureCoord;                   // 将接收的纹理坐标传递给片元着色器
}

顶点着色器是一个可编程的处理单元,执行顶点变换、纹理坐标变换、光照、材质等顶点的相关操作,每顶点执行一次。替代了传统渲染管线中顶点变换、光照以及纹理坐标的处理,开发人员可以根据自己的需求自行开发,大大增加了程序的灵活性。

顶点着色器主要是传入相应的Attribute变量、Uniforms变量、采样器以及临时变量,经过顶点着色器后生成Varying变量。如下图所示:

新葡京8455 3

顶点着色器

(1)attribute变量(属性变量)只能用于顶点着色器中,不能用于片元着色器。 一般用该变量来表示一些顶点数据,如:顶点坐标、纹理坐标、颜色等。

(2)uniforms变量(一致变量)用来将数据值从应用程其序传递到顶点着色器或者片元着色器。 该变量有点类似C语言中的常量(const),即该变量的值不能被shader程序修改。一般用该变量表示变换矩阵、光照参数、纹理采样器等。

(3)varying变量(易变变量)是从顶点着色器传递到片元着色器的数据变量。顶点着色器可以使用易变变量来传递需要插值的颜色、法向量、纹理坐标等任意值。 在顶点与片元shader程序间传递数据是很容易的,一般在顶点shader中修改varying变量值,然后片元shader中使用该值,当然,该变量在顶点及片元这两段shader程序中声明必须是一致的 。例如:上面代码中应用程序中由顶点着色器传入片元着色器中的vColor变量。

(4)gl_Position 为内建变量,表示变换后点的空间位置。 顶点着色器从应用程序中获得原始的顶点位置数据,这些原始的顶点数据在顶点着色器中经过平移、旋转、缩放等数学变换后,生成新的顶点位置。新的顶点位置通过在顶点着色器中写入gl_Position传递到渲染管线的后继阶段继续处理。

前言

Metal入门教程图片绘制Metal入门教程三维变换Metal入门教程摄像头采集渲染Metal入门教程灰度计算Metal入门教程视频渲染

前面的教程既介绍了Metal的图片绘制、三维变换、视频渲染,也引入MetalPerformanceShaders处理摄像头数据以及用计算管道实现灰度计算,这次尝试实现sobel边界检测。

Metal系列教程的代码地址;OpenGL ES系列教程在这里;

你的star和fork是我的源动力,你的意见能让我走得更远

顶点着色器

Vertex shader – 在你的场景中,每个顶点都需要调用的程序,称为“顶点着色器”。假如你在渲染一个简单的场景:一个长方形,每个角只有一个顶点。于是vertex shader 会被调用四次。它负责执行:诸如灯光、几何变换等等的计算。得出最终的顶点位置后,为下面的片段着色器提供必须的数据。

vertex shader可通过可编程的方式实现对顶点的操作,如坐标空间转换,颜色及纹理坐标。最简单的Vertex shader如下

attribute vec4 Position;

void main(Void) {
    gl_Position = Position; // must set gl_Position for vertex shader
}

1、attribute声明vertex shader接收的变量,针对每一个顶点的数据。属性可理解为针对每一个顶点的输入数据,只有在vertex shader中才有,在fragment shader中没有。vec4表示由4部分组成的矢量。这里的Position用来传入顶点vertex的位置数据。
2、main是shader脚本的入口。
3、gl_Position是vertex shader内建的输出变量,传递给fragment shader,必须设置。这里将Position直接传递给fragment shader。

可见,dot运算与直接相乘在性能几乎没区别。那么,dot运算由底层图形硬件加速的观点并没在iPad Air 2真机上体现。

着色语言(Shader)

着色语言是一种类C的编程语言,但不像C语言一样支持双精度浮点型(double)、字节型(byte)、短整型(short)、长整型(long),并且取消了C中的联合体(union)、枚举类型(enum)、无符号数(unsigned)以及位运算等特性。 着色语言中有许多内建的原生数据类型以及构建数据类型,如:浮点型(float)、布尔型(bool)、整型(int)、矩阵型(matrix)以及向量型(vec2、vec3等)等。总体来说,这些数据类型可以分为标量、向量、矩阵、采样器、结构体以及数组等。 shader支持下面数据类型:

float  bool  int    基本数据类型
vec2                包含了2个浮点数的向量
vec3                包含了3个浮点数的向量
vec4                包含了4个浮点数的向量
ivec2               包含了2个整数的向量
ivec3               包含了3个整数的向量
ivec4               包含了4个整数的向量
bvec2               包含了2个布尔数的向量
bvec3               包含了3个布尔数的向量
bvec4               包含了4个布尔数的向量
mat2                2*2维矩阵
mat3                3*3维矩阵
mat4                4*4维矩阵
sampler1D           1D纹理采样器
sampler2D           2D纹理采样器
sampler3D           3D纹理采样器

总结

Metal shading language的重要性不言而喻,Metal入门教程灰度计算重在如何搭建计算shader的通道,而sobel的实现相对灰度计算略为复杂,更有益于实践练习,后续还会补上直方图均衡的demo。

片元着色器

Fragment shader – 在你的场景中,大概每个像素都会调用的程序,称为“片段着色器”。在一个简单的场景,也是刚刚说到的长方形。这个长方形所覆盖到的每一个像素,都会调用一次fragment shader。片段着色器的责任是计算灯光,以及更重要的是计算出每个像素的最终颜色

Fragment是可以被渲染到屏幕上的像素点,fragment shader即用于计算每个像素的颜色等属性。最简单的Fragment shader如下

precision mediump float;

void main(void) {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // must set gl_FragColor for fragment shader
}

1、precision mediump float设置float的精度为mediump,还可设置为lowp和highp,主要是出于性能考虑。
2、gl_FragColor是fragment shader唯一的内建输出变量,设置像素的颜色。这里设置所有像素均为红色。

s00、s10等变量的坐标由下图所示,在此按纹理坐标的定义理解即可,原点在左下角,故在待计算的像素点的左边则是x轴少一个单位。

与OpenGL ES1.x渲染管线相比,OpenGL ES 2.0渲染管线中“顶点着色器”取代了OpenGL ES 1.x渲染管线中的“变换和光照”;“片元着色器”取代了OpenGL ES 1.x渲染管线中的“纹理环境和颜色求和”、“雾”以及“Alpha测试”。 这使得开发人员在使用OpenGL ES 2.0API进行开发时,可以通过编写顶点及片元着色器程序,来完成一些顶点变换和纹理颜色计算工作,实现更加灵活、精细化的计算与渲染。

Vertex shader与Fragment shader的差异

1、shader脚本中有三种级别的精度:lowp,mediump,highp。如precision highp float; 。在vertex shader中,int和float都默认为highp级别。而fragment shader中没有默认精度,必须设置精度描述符,一般设为mediump即可。
2、attribute只作用于vertex shader中,表示接收的变量。在vertex shader中,若没有attribute则为输出变量(输出至fragment shader)。
3、vertex shader的默认输出变量至少应该有gl_Position,另外有两个可选的gl_FrontFacing和gl_PointSize。而fragment shader只有唯一的varying输出变量gl_FragColor。
4、Uniform是全局变量,可用于vertex shader和fragment shader。在vertex shader中通常是变换矩阵、光照参数、颜色等,。在fragment shader中通常是雾化参数、纹理参数等。OpenGLES 2.0规定所有实现应该支持的最大vertex shader的uniform变量个数不能少于128个,而最大fragment shader的uniform变量个数不能少于16个。
5、simpler是一种特殊的uniform,用于呈现纹理,可用于vertex shader和fragment shader.

本文主要参考:http://learnopengl-cn.readthedocs.io/zh/latest/
http://blog.csdn.net/icetime17/article/details/50436927

1、sobel_filter操作

新葡京8455,2.片着色器(fragment_shader)
precision mediump float;                           // 设置工作精度
varying vec4 vColor;                               // 接收从顶点着色器过来的顶点颜色数据
varying vec2 vTextureCoord;                        // 接收从顶点着色器过来的纹理坐标
uniform sampler2D sTexture;                        // 纹理采样器,代表一幅纹理
void main()
{                                                                                   
    gl_FragColor = texture2D(sTexture, vTextureCoord) * vColor;// 进行纹理采样
}

此片元着色器的主要功能为根据接收的记录片元纹理坐标的易变变量中的纹理坐标,调用texture2D内建函数从采样器中进行纹理采样,得到此片元的颜色值。最后,将采样到的颜色值传给gl_FragColor内建变量,完成片元的着色。

片元着色器是一个处理片元值及其相关联数据的可编程单元,片元着色器可执行纹理的访问、颜色的汇总、雾化等操作,每片元执行一次。片元着色器替代了纹理、颜色求和、雾以及Alpha测试,这一部分是需要开发者自己开发的。

新葡京8455 4

片着色器

(1)varying指的是从顶点着色器传递到片元着色器的数据变量

(2)gl_FragColor为内置变量,用来保存片元着色器计算完成的片元颜色值,此颜色值将送入渲染管线的后继阶段进行处理。

GLSL

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。

着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。如果你不知道什么是uniform也不用担心,我们后面会进行讲解。

一个典型的着色器有下面的结构:

#version version_number

in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

int main()
{
  // 处理输入并进行一些图形操作
  ...
  // 输出处理过的结果到输出变量
  out_variable_name = weird_stuff_we_processed;
}

当我们特别谈论到顶点着色器的时候,每个输入变量也叫顶点属性(Vertex Attribute)。我们能声明的顶点属性是有上限的,它一般由硬件来决定。OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限:

GLint nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

通常情况下它至少会返回16个,大部分情况下是够用了。

gradientThreshold = 0.5

前面的教程里我们简要地触及了一点着色器的皮毛,并了解了如何恰当地使用它们。现在我们会用一种更加广泛的形式详细解释着色器,特别是OpenGL着色器语言(GLSL)。

gradientThreshold = 0.0005

向量

GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):

类型    含义
vecn     包含n个float分量的默认向量
bvecn   包含n个bool分量的向量
ivecn   包含n个int分量的向量
uvecn   包含n个unsigned int分量的向量
dvecn   包含n个double分量的向量

大多数时候我们使用vecn,因为float足够满足大多数要求了。

一个向量的分量可以通过vec.x这种方式获取,这里x是指这个向量的第一个分量。你可以分别使用.x、.y、.z和.w来获取它们的第1、2、3、4个分量。GLSL也允许你对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。

向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:

vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx   anotherVec.yxzy;

你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:

vec2 vect = vec2(0.5f, 0.7f);
vec4 result = vec4(vect, 0.0f, 0.0f);
vec4 otherResult = vec4(result.xyz, 1.0f);

关于OpenGLES渲染管线,请参考博客 [OpenGL ES 02]OpenGL ES渲染管线与着色器。着色器是可编程管线中的术语,其语法类似C语言,分为顶点着色器(Vertex Shader)和片元着色器(Fragment Shader)

新葡京8455 5

着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。

为简化代码,将float rgb2gray(vec3 color)修改为float rgb2gray(vec4 color),提高pixel_operator(float dx, float dy)的调用代码可读性。同时,使用内置函数dot实现向量点乘计算。另外,dx、dy由CPU计算好再上传至GPU。

数据类型

和其他编程语言一样,GLSL有数据类型可以来指定变量的种类。GLSL中包含C等其它语言大部分的默认基础数据类型:int、float、double、uint和bool。GLSL也有两种容器类型,它们会在这个教程中使用很多,分别是向量(Vector)和矩阵(Matrix),其中矩阵我们会在之后的教程里再讨论。

float rgb2gray(vec3 color) {
    return 0.2126 * color.r   0.7152 * color.g   0.0722 * color.b;
}

感谢@琨君提出的见解。

新葡京8455 6

#pragma debug(on)
#pragma optimize(off)

致谢:

坐标的表示

dot计算RGB亮度的耗时

启用调试并关闭着色器代码优化的情况下,资源消耗比默认情况(开启着色器代码优化)略有提升,如下图所示。这里调试与非调试状态资源消耗对比不明显的可能原因是计算简单及顶点数据量小。

float sobel_filter()
{
    float dx = 1.0 / float(u_screen_width); // e.g. 1920
    float dy = 1.0 / float(u_screen_height); // e.g. 1080

    float s00 = pixel_operator(-dx, dy);
    float s10 = pixel_operator(-dx, 0.0);
    float s20 = pixel_operator(-dx, -dy);
    float s01 = pixel_operator(0.0, dy);
    float s21 = pixel_operator(0.0, -dy);
    float s02 = pixel_operator(dx, dy);
    float s12 = pixel_operator(dx, 0.0);
    float s22 = pixel_operator(dx, -dy);

    float sx = s00   2.0 * s10   s20 - (s02   2.0 * s12   s22);
    float sy = s00   2.0 * s01   s02 - (s20   2.0 * s21   s22);
    float dist = sx * sx   sy * sy;

    return dist;
}

3、Sobel Operator计算规则问题

本文档描述了有关GPU实现索贝尔算子(Sobel Operator)中Fragment Shader部分代码的简单分析。同时,包括了通过Xcode在线调试GPU对比OpenGL ES着色器语言内置函数与普通乘法计算的性能差异、启用着色器调试模式及其资源消耗等内容。

启用调试并关闭着色器代码优化的资源消耗

8、关闭Shader代码优化及开启调试功能

4、RGB亮度转换至梯度值

sx = 1*(s00 - s01 s01 - s02) 2*(s10 - s11 s11 - s12) 1*(s20 - s21 s21 - s22),sy是纵向的,相当于是加权的差分。

新葡京8455 7

新葡京8455 8

原始实现的Fragment耗时

// predefine var
float gradientThreshold = 0.005;
// void main()
if (graylevel > gradientThreshold) {
    o_Color = vec4(0.0, 0.0, 0.0, 1.0);
} else {
    o_Color = vec4(1.0);
}

新葡京8455 9

gradientThreshold = 0.05

// in vec2 v_TexCoord;
float pixel_operator(float dx, float dy) {
    vec4 rgba_color = texture( u_sampler, v_TexCoord   vec2(dx,dy) );
    return rgb2gray(rgba_color.rgb);
}

前面的sobel_filter()只是得到待计算的像素点周围的像素的坐标,实现了算法骨架,然而,对于待计算的像素点本身的坐标却没处理。在此,只需提供待计算的像素点的坐标,即可完成指定像素点的处理,这就是pixel_operator()的功能。

实现了RGB(三维颜色空间)向梯度(一维颜色空间)的转换,从而以黑、白及其过渡颜色表达了RGB图像的亮度。

gradientThreshold = 0.005

baseline为直接相乘

替换成dot计算的资源利用情况。

sx对应横向的情况,矩阵为[1, 0, -1; 2, 0, -2; 1, 0, -1]。
sy对应纵向求差分,矩阵为[1, 2, 1; 0, 0, 0, -1, -2, -1]。
可看成是一个梯度向量(sx, sy),sx平方 sy平方就是求模。

5、代码优化

文档iOS OpenGL ES 3.0 数据可视化 4:纹理映射实现2维图像与视频渲染简介使用索贝尔算子(Sobel Operator)实现了边缘检测(Edge Detection),相关代码在该文档没作分析。

@琨君对sobel的解释如下:

2、采样操作pixel_operator

直接相乘与dot运算的比较。

新葡京8455 10

6、dot运算与直接相乘的性能比较

新葡京8455 11

dot计算的资源消耗

在分析rgb2gray()前,对Sobel Operator作简单说明。虽然,Sobel Operator定义为3x3矩阵,在计算sx、sy的平方里却没按矩阵乘法进行。在此,将其理解为Sobel Operator特定的计算规则即可。

// predefine var brightness
vec3 brightness = vec3(0.2126, 0.7152, 0.0722);
float rgb2gray(vec4 color) {
    return dot(color.rgb, brightness);
}
float pixel_operator(float dx, float dy) {
    return rgb2gray( texture( u_sampler, v_TexCoord   vec2(dx,dy)) );
}

dx = 1.0 / float(u_screen_width);dy = 1.0 / float(u_screen_height);分别取出指定宽高(即,屏幕的宽高)上的最小单位,在此是一个像素。同时,确保坐标值在纹理坐标[0, 1]区间内。

7、不同阈值下的边缘检测效果

在不使用VBO的情况下,验证替换成dot计算RGB亮度是否提升性能。原始实现的运行情况如下图所示。

新葡京8455 12

依次修改gradientThreshold观察运行结果。

新葡京8455 13

新葡京8455 14

原始实现的资源消耗

在Fragment Shader中加上阈值判断,代码如下所示。

新葡京8455 15

本文由新葡京8455发布,转载请注明来源

关键词: