Shader著色器
Shader出現(xiàn)在OpenGL ES 2.0中,允許創(chuàng)建自己的Shader。必須同時創(chuàng)建兩個Shader,分別是Vertex shader和Fragment shader.
Shader工具
Shader會有很多坑,不過一些工具能夠幫助你跳過這些坑
- GPUImage:https://github.com/BradLarson/GPUImage
- ShaderToy:https://www.shadertoy.com/
- Shaderific:http://www.shaderific.com/
- Quartz Composer:官方工具
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)
- OpenGL ES:https://www.khronos.org/opengles/sdk/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf
- OpenGL:https://www.khronos.org/files/opengl-quick-reference-card.pdf
變量賦值
三個可以賦值給我們的變量的標(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é)
- 3D Math Primer for Graphics and Game Development http://www.amazon.com/Math-Primer-Graphics-Game-Development/dp/1568817231/ref=sr_1_1?ie=UTF8&qid=1422837187&sr=8-1&keywords=3d+math+primer+for+graphics+and+game+development
- The Nature of Code http://natureofcode.com/
- The Computational Beauty of Nature http://www.amazon.com/Computational-Beauty-Nature-Explorations-Adaptation/dp/0262561271/ref=sr_1_1?s=books&ie=UTF8&qid=1422837256&sr=1-1&keywords=computational+beauty+of+nature
相關(guān)GLSL
- Graphic Shaders: Theory and Practice http://www.amazon.com/Graphics-Shaders-Theory-Practice-Second/dp/1568814348/ref=sr_1_1?s=books&ie=UTF8&qid=1422837351&sr=1-1&keywords=graphics+shaders+theory+and+practice
- The OpenGL Shading Language http://www.amazon.com/OpenGL-Shading-Language-Randi-Rost/dp/0321637631/ref=sr_1_1?s=books&ie=UTF8&qid=1422896457&sr=1-1&keywords=opengl+shading+language
- OpenGL 4 Shading Language Cookbook http://www.amazon.com/OpenGL-Shading-Language-Cookbook-Second/dp/1782167021/ref=sr_1_2?s=books&ie=UTF8&qid=1422896457&sr=1-2&keywords=opengl+shading+language
- GPU Gems http://http.developer.nvidia.com/GPUGems/gpugems_part01.html
- GPU Pro: Advanced Rendering Techniques http://www.amazon.com/GPU-Pro-Advanced-Rendering-Techniques/dp/1568814720/ref=sr_1_4?s=books&ie=UTF8&qid=1422837427&sr=1-4&keywords=gpu+pro