GPU處理圖像 Shader的入門

Shader著色器

Shader出現(xiàn)在OpenGL ES 2.0中,允許創(chuàng)建自己的Shader。必須同時創(chuàng)建兩個Shader,分別是Vertex shader和Fragment shader.

Shader工具

Shader會有很多坑,不過一些工具能夠幫助你跳過這些坑

Shader使用范例

Vertex shader

attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
     gl_position = position;
     textureCoordinate = inputTextureCoordinate.xy;
}

Fragment shader

直通濾鏡

varying highp vec2 textureCoordinate; //highp屬性負(fù)責(zé)變量精度,這個被加入可以提高效率
uniform sampler2D inputImageTexture; //接收一個圖片的引用,當(dāng)做2D的紋理,這個數(shù)據(jù)類型就是smpler2D。
void main()
{
     gl_FragColor = texture2D(inputImageTexture, textureCoordinate); //texture是GLSL(著色語言)特有的方法
}

GLSL著色語言

GLSL的官方快速入門指導(dǎo)

變量賦值

三個可以賦值給我們的變量的標(biāo)簽

  • Uniforms:在渲染循環(huán)里作為不變的輸入值
  • Attributes:隨頂點(diǎn)位置不同會變的輸入值
  • Varyings:用來在Vertex shader和Fragment shader之間傳遞信息的,比如在Vertex shader中寫入varying值,然后就可以在Fragment shader中讀取和處理

向量

有很多種向量,但是有三種會經(jīng)??吹?/p>

  • vec2:兩個浮點(diǎn)數(shù),適合在Fragment shader中保存X和Y坐標(biāo)的情況
  • vec3:三個浮點(diǎn)數(shù)
  • vec4:四個浮點(diǎn)數(shù),在圖像處理中持續(xù)追蹤每個像素的R,G,V,A這四個值。

矩陣

是浮點(diǎn)數(shù)組的數(shù)組。三個經(jīng)常處理的矩陣對象

  • mat2:相當(dāng)于保存了兩個vec2對象的值或四個浮點(diǎn)數(shù)。
  • mat3
  • mat4

向量和矩陣運(yùn)算

線性代數(shù)發(fā)揮作用的地方。想知道線性代數(shù)如何工作可以看這個資源站:http://betterexplained.com/articles/linear-algebra-guide/

線性代數(shù)可以一次在很多值上進(jìn)行并行操作,so,正好適合需求,GLSL內(nèi)建了很多函數(shù)可以處理龐大的計(jì)算轉(zhuǎn)換

GLSL特有函數(shù)

GLSL內(nèi)建的函數(shù)可以在Shaderific網(wǎng)站上找到:http://www.shaderific.com/glsl-functions。很多C語言數(shù)學(xué)庫基本數(shù)學(xué)運(yùn)算都有對應(yīng)的函數(shù)。

  • step():GPU處理?xiàng)l件邏輯不是很好。step()允許在不產(chǎn)生分支的前提下實(shí)現(xiàn)條件邏輯。傳入step()函數(shù)的值小于閾值就返回0.0,大于等于閾值就返回1.0。
  • mix():將兩個顏色值混合為一個。
  • clamp():可以確保值在一個區(qū)間內(nèi)。

復(fù)雜的Shader的例子

一個飽和度調(diào)節(jié)的Fragment shader的例子,出自《圖形著色器:理論和實(shí)踐》這本書。

varying highp vec2 textureCoordinate; //

uniform sampler2D inputImageTexture;
uniform lowp float saturation;

const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721); //光亮度里三個值相加要為1,各個值代表著顏色的百分比,中間是綠色的值,70%的比重會讓效果更好點(diǎn)。

void main()
{
     lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate); //根據(jù)坐標(biāo)取樣圖片顏色信息
     lowp float luminance = dot(textureColor.rgb, luminanceWeighting); //GLSL中的點(diǎn)乘運(yùn)算,線性代數(shù)的點(diǎn)運(yùn)算符相乘兩個數(shù)字。點(diǎn)乘計(jì)算需要將紋理顏色信息和相對應(yīng)的亮度權(quán)重相乘。然后取出所有的三個值相加到一起計(jì)算得到這個像素的中和亮度值。
     lowp vec3 greyScaleColor = vec3(luminance); //創(chuàng)建一個三個值都是亮度信息的vec3,如果只指定一個值,編譯器會將其它的都設(shè)置成這個值
     gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w); //用mix函數(shù)把計(jì)算的灰度值,初識的紋理顏色和得到的飽和度信息結(jié)合起來。
}

球形濾鏡示例

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;

uniform highp vec2 center;
uniform highp float radius;
uniform highp float aspectRatio;
uniform highp float refractiveIndex;

void main()
{
     highp vec2 textureCoordinateToUse = vec2(textureCoordinate.x, (textureCoordinate.y * aspectRatio + 0.5 - 0.5 * aspectRatio)); //歸一化坐標(biāo)空間需要考慮屏幕是一個單位寬和一個單位長。
     highp float distanceFromCenter = distance(center, textureCoordinateToUse); //計(jì)算特定像素點(diǎn)距離球形的中心有多遠(yuǎn)。使用GLSL內(nèi)建的distance()函數(shù),用勾股定律計(jì)算出中心坐標(biāo)和長寬比矯正過的紋理坐標(biāo)的距離
     lowp float checkForPresenceWithinSphere = step(distanceFromCenter, radius); //計(jì)算片段是否在球體內(nèi)。

     distanceFromCenter = distanceFromCenter / radius;  //標(biāo)準(zhǔn)化到球心的距離,重新設(shè)置distanceFromCenter

     highp float normalizedDepth = radius * sqrt(1.0 - distanceFromCenter * distanceFromCenter); //模擬一個玻璃球,需要計(jì)算球的“深度”是多少。
     highp vec3 sphereNormal = normalize(vec3(textureCoordinateToUse - center, normalizedDepth)); //歸一化

     highp vec3 refractedVector = refract(vec3(0.0, 0.0, -1.0), sphereNormal, refractiveIndex); //GLSL的refract()函數(shù)以剛才創(chuàng)建的球法線和折射率來計(jì)算當(dāng)光線通過球時從任意一個點(diǎn)看起來如何。

     gl_FragColor = texture2D(inputImageTexture, (refractedVector.xy + 1.0) * 0.5) * checkForPresenceWithinSphere; //最后湊齊所有計(jì)算需要的顏色信息。
}

調(diào)試Shader

使用gl_FragColor調(diào)試代碼。GPUImage是個開源的資源有些很酷的shader,非常好的學(xué)習(xí)shader的方式,可以拿一個你覺得很有意思的shader對著源碼一點(diǎn)點(diǎn)看下去。GPUImage還有一個shader設(shè)計(jì)器https://github.com/BradLarson/GPUImage/tree/master/examples/Mac/ShaderDesigner 的Mac應(yīng)用,可以測試shader而不用準(zhǔn)備OpenGL代碼。

性能調(diào)優(yōu)

簡單的方法達(dá)到調(diào)優(yōu)目的,可以用下載Imagination Technologies PowerVR SDKhttp://community.imgtec.com/developers/powervr/這個工具幫助分析shader

  • 消除條件邏輯:使用step()這樣的函數(shù)
  • 減少依賴紋理的讀?。喝绻M麖母浇袼厝佣皇怯?jì)算Fragment shader相鄰像素的偏差,最好在Vertex shader中計(jì)算然后把結(jié)果以varying的方式傳入Fragment shader里。
  • 計(jì)算盡量簡單:能夠得到一個近似值就盡量用,不要用類似sin(),cos(),tan()的比較消耗的操作。
  • 盡可能的將計(jì)算放到Vertex上:如果計(jì)算在圖片上會有相同的結(jié)果或線性變化最好這樣做。因?yàn)閂ertex shader對每個頂點(diǎn)運(yùn)行一次,而Fragment shader會在每個像素上運(yùn)行一次。
  • 移動設(shè)備使用合適的精度:在向量上使用低精度的值會變得更快。兩個lowp vec4相加可以在一個時鐘周期內(nèi)完成,兩個highp vec4相加則需要四個時鐘周期。

邊界探測

基于OpenCV庫,不過這些步驟在GPUImage中都有完整的實(shí)現(xiàn)

Sobel邊界探測

這種操作在濾鏡方面比機(jī)器視覺方面多。Sobel邊界探測用于探測邊界的出現(xiàn)位置,邊界是由明變暗或者由暗變明的區(qū)域。在被處理的圖片中一個像素的亮度反映了這個像素周圍邊界的強(qiáng)度。

  • 第一步,將彩色圖片弄成灰階圖,這個過程就是將每個像素的紅綠藍(lán)部分合一代表亮度的值。如是果YUV而不是RGB格式的可以省略這步,因?yàn)閅UV是將亮度信息和色度信息分開的。如果簡化到只剩亮度的話一個像素周圍的邊界強(qiáng)度就可以由周圍3*3個臨近像素計(jì)算得到。這個計(jì)算涉及Convolution Matrix(卷積矩陣),每個像素都要與這個矩陣計(jì)算出一個數(shù)值,因?yàn)闆]有順序要求所以可以采取并行運(yùn)算。
  • Sobel的水平處理矩陣

-1 0 +1
-2 0 +2
-1 0 +1

  • Sobel的垂直矩陣

-1 -2 -1
0 0 0
+1 +2 +1

  • 和Sobel類似的變體,Prewitt邊界探測。這個變體會在橫向豎向矩陣中用不同的矩陣,但是運(yùn)作過程差不多。
  • OpenGL ES代碼

precision mediump float;

//varying的都是在Vertex shader上定義了
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;

varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;

varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;

uniform sampler2D inputImageTexture;

void main()
{
     float bottomLeftIntensity = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
     float topRightIntensity = texture2D(inputImageTexture, topRightTextureCoordinate).r;
     float topLeftIntensity = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
     float bottomRightIntensity = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
     float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
     float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
     float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
     float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;

     float h = -bottomLeftIntensity - 2.0 * leftIntensity - topLeftIntensity + bottomRightIntensity + 2.0 * rightIntensity + topRightIntensity;
     float v = -topLeftIntensity - 2.0 * topIntensity - topRightIntensity + bottomLeftIntensity + 2.0 * bottomIntensity + bottomRightIntensity;
     float mag = length(vec2(h, v)); //length()函數(shù)計(jì)算出水平和垂直矩陣轉(zhuǎn)化后值的平方和的平方根的值,這個值會被拷貝進(jìn)輸出像素的紅綠藍(lán)通道中,這樣就可以來代表邊界的明顯程度了。

     gl_FragColor = vec4(vec3(mag), 1.0);
}

Canny邊界探測

Canny探測會比Sobel復(fù)雜些,這樣做會得到一條物體邊界的干凈線條。

探測過程:

  • 先用Sobel矩陣得到邊界梯度的強(qiáng)度。這個和Sobel比就是最后一個計(jì)算有些不同
vec2 gradientDirection;
gradientDirection.x = -bottomLeftIntensity - 2.0 * leftIntensity - topLeftIntensity + bottomRightIntensity + 2.0 * rightIntensity + topRightIntensity;
gradientDirection.y = -topLeftIntensity - 2.0 * topIntensity - topRightIntensity + bottomLeftIntensity + 2.0 * bottomIntensity + bottomRightIntensity;

float gradientMagnitude = length(gradientDirection);
vec2 normalizedDirection = normalize(gradientDirection);
normalizedDirection = sign(normalizedDirection) * floor(abs(normalizedDirection) + 0.617316); // Offset by 1-sin(pi/8) to set to 0 if near axis, 1 if away
normalizedDirection = (normalizedDirection + 1.0) * 0.5; // Place -1.0 - 1.0 within 0 - 1.0

gl_FragColor = vec4(gradientMagnitude, normalizedDirection.x, normalizedDirection.y, 1.0);
  • 著色器步驟
precision mediump float;

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;
uniform highp float texelWidth; //要處理的圖片中臨近像素之間的距離。
uniform highp float texelHeight; //同上
uniform mediump float upperThreshold; //預(yù)期邊界強(qiáng)度上下限
uniform mediump float lowerThreshold; 

void main()
{
     vec3 currentGradientAndDirection = texture2D(inputImageTexture, textureCoordinate).rgb;
     vec2 gradientDirection = ((currentGradientAndDirection.gb * 2.0) - 1.0) * vec2(texelWidth, texelHeight);

     float firstSampledGradientMagnitude = texture2D(inputImageTexture, textureCoordinate + gradientDirection).r;
     float secondSampledGradientMagnitude = texture2D(inputImageTexture, textureCoordinate - gradientDirection).r;

     float multiplier = step(firstSampledGradientMagnitude, currentGradientAndDirection.r);
     multiplier = multiplier * step(secondSampledGradientMagnitude, currentGradientAndDirection.r);

     float thresholdCompliance = smoothstep(lowerThreshold, upperThreshold, currentGradientAndDirection.r);
     multiplier = multiplier * thresholdCompliance;

     gl_FragColor = vec4(multiplier, multiplier, multiplier, 1.0);
}

Harris邊角探測

多步驟的方法來探測場景中的邊角。

  • 先弄得只有亮度信息,再通過Sobel矩陣,普里維特矩陣或者其它相關(guān)的矩陣計(jì)算出一個像素X和Y方向的梯度值,計(jì)算的結(jié)果會將X梯度傳入紅色部分,Y梯度傳入綠色部分,X與Y梯度的乘積傳入藍(lán)色部分。
  • 對計(jì)算的結(jié)果進(jìn)行高斯模糊。將模糊后圖中取出紅綠藍(lán)編碼的值帶到計(jì)算邊角點(diǎn)可能性公式:

R = Ix2 × Iy2 ? Ixy × Ixy ? k × (Ix2 + Iy2)2

資料

相關(guān)數(shù)學(xué)

相關(guān)GLSL

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 目錄結(jié)構(gòu): 第一步,明確要干嘛 第二步,怎么去畫(純理論) 第三步,怎么去畫(實(shí)戰(zhàn)) 第四步,練練手 第一步,明確...
    半紙淵閱讀 8,305評論 18 57
  • <轉(zhuǎn)>我也忘了轉(zhuǎn)自哪里,抱歉,感謝原作者 什么是Shader Shader(著色器)是一段能夠針對3D對象進(jìn)行操作...
    星易乾川閱讀 5,858評論 1 16
  • 著色器(Shader)是運(yùn)行在GPU上的小程序。這些小程序?yàn)閳D形渲染管線的某個特定部分而運(yùn)行。從基本意義上來說,著...
    RM_乾笙閱讀 1,307評論 0 0
  • OpenGL學(xué)習(xí)大致的理解 OpenGL為什么會涉及這么多操作順序。這是因?yàn)椋臀覀儸F(xiàn)在使用的C++、JAVA這種...
    wo不懂閱讀 5,535評論 10 8
  • “很多時候,讓我們難過的并不是摔倒、疼痛,而是在摔倒摔痛的時候,我們期望來安慰的那個人,沒有來。” 有人會在生病時...
    Xcer閱讀 776評論 0 1

友情鏈接更多精彩內(nèi)容