特效中的著色器代碼詳解

前言

圖像和視頻渲染離不開OpenGLES,在可編程的OpenGLES渲染管線中,頂點著色器和片段著色器使得OpenGLES更加靈活。

通過以下幾種常見的特效,來認(rèn)識和了解如何編寫一個片段著色器。

分屏效果

片段著色器代碼來源于這篇文章。代碼如下:

// 分六屏特效
precision highp float;
uniform sampler2D inputTexture;
varying highp vec2 textureCoordinate;

void main() {
    highp vec2 uv = textureCoordinate;
    // 左右分三屏
    if (uv.x <= 1.0 / 3.0) {
        uv.x = uv.x + 1.0 / 3.0;
    } else if (uv.x >= 2.0 / 3.0) {
        uv.x = uv.x - 1.0 / 3.0;
    }
    // 上下分兩屏,保留 0.25 ~ 0.75部分
    if (uv.y <= 0.5) {
        uv.y = uv.y + 0.25;
    } else {
        uv.y = uv.y - 0.25;
    }
    gl_FragColor = texture2D(inputTexture, uv);
}

這是一個典型的片段著色器代碼。
第一行代碼用精度修飾符聲明了精度類型。在OpenGLES中,有三種精度類型,高(highp)、中(mediump)、低(lowp),默認(rèn)是高精度也就是highp的。
第二行代碼聲明了一個采樣器,用于訪問著色器中的紋理圖像。
第三行代碼聲明了一個高精度的2D紋理坐標(biāo)變量textureCoordinate 。
其中precision 代表精度修飾符;uniform是變量類型限定符,代表統(tǒng)一變量,統(tǒng)一變量存儲應(yīng)用程序通過OpenGLESAPI傳入著色器的只讀值,對于保存著色器所需的所有數(shù)據(jù)類型(如變換矩陣、照明參數(shù)和顏色)都很有用。統(tǒng)一變量的命名空間在頂點和片段著色器中是共享的,也就是說,如果頂點和片段著色器一起連接到一個程序?qū)ο螅麄兙蜁蚕硗唤M統(tǒng)一變量;varying變量是頂點著色器中傳遞給片段著色器的變量值,它修飾了一個vec2類型的變量,是一個(x, y)標(biāo)識的點。

main函數(shù)是程序?qū)ο箝_始調(diào)用片段著色器的入口,在該函數(shù)中,聲明一個高精度的臨時坐標(biāo)變量uv,用于接收頂點著色器傳入的紋理點的坐標(biāo)。
接下來是具體分屏的邏輯代碼,根據(jù)需要填充的原圖像的區(qū)域,來修改當(dāng)前的填充區(qū)域。
根據(jù)當(dāng)前紋理坐標(biāo)點的x坐標(biāo),確定該點是否在整個紋理的三分之一以內(nèi)以及是否超過了紋理坐標(biāo)的三分之二。它分別代表了將紋理的x坐標(biāo)[0, 1]的紋理區(qū)間分為了3個部分。紋理讀取的結(jié)果和從頂點著色器傳遞的輸入值textureCoordinate用來確定的填充紋理的區(qū)域uv,生成新的紋理。這個裁剪坐標(biāo)是可以自定義取原圖像的部分區(qū)域。

上面是一個需要裁剪的示例,下面是一個不需要裁剪的示例
// 四分屏
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;

void main() {
    vec2 uv = TextureCoordsVarying.xy;
    if(uv.x <= 0.5){
        uv.x = uv.x * 2.0;
    }else{
        uv.x = (uv.x - 0.5) * 2.0;
    }
    
    if (uv.y<= 0.5) {
        uv.y = uv.y * 2.0;
    }else{
        uv.y = (uv.y - 0.5) * 2.0;
    }
    
    gl_FragColor = texture2D(Texture, uv);
}

如果原圖像是正方形,那么,4分屏既2x2在不需要對原圖像進(jìn)行裁剪的情況下,將填充范圍修改為當(dāng)前值得二倍。

圖像灰度

這是圖像灰度的片段著色器代碼

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

void main(){
    // 獲取對應(yīng)紋理坐標(biāo)系下色顏色值
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    // 將顏色mask與變換因子相乘得到灰度值
    float luminance = dot(mask.rgb, W);
    // 將灰度值轉(zhuǎn)換為(luminance,luminance,luminance,mask.a)并填充到像素中
    gl_FragColor = vec4(vec3(luminance), 1.0);
}

在不同平臺有多重方法可以實現(xiàn)灰度效果,如GPUImage庫,以及iOS的CoreImage。但在著色器上可以選擇權(quán)值法、平均值法、取綠色值法中,權(quán)值方法是公認(rèn)的效果最好的,如上,參考這里。

浮點算法:Gray = R*0.3 + G*0.59 + B*0.11 (RGB的權(quán)重總和為1)
整數(shù)方法:Gray = (R*30 + G*59 + B*11)/100(RGB的權(quán)重總和為100)
移位方法:Gray = (R*76 + G*151 + B*28)>>8

第一行代碼聲明了高精度的浮點類型。
第二行代碼聲明了一個采樣器,用于讀取紋理。
第三行代碼是頂點著色器中傳入的紋理的坐標(biāo)變量。
第四行代碼是一個高精度的向量的常量值,該值和目標(biāo)點的RGBA相乘會得到一個灰度值。
main函數(shù)中,第一行代碼使用texture2D函數(shù)從采樣器中讀取給定的坐標(biāo)點的像素的顏色值RGBA(是vec4類型);第二行代碼是將讀取到的紋理的顏色值與上述常量進(jìn)行點乘,調(diào)用dot函數(shù)即可。
最后一行代碼就是將計算好的新的像素值返回給需要填充的像素,gl_FragColor也是片段著色器的內(nèi)置函數(shù),主要用來設(shè)置片元像素的顏色。

漩渦效果

image.png

片段著色器代碼如下

precision mediump float;
// 計算圓周需要用到的π
const float PI = 3.14159265;
// 采樣器
uniform sampler2D image;
// 旋轉(zhuǎn)的角度,Radius是旋轉(zhuǎn)的半徑
const float uD = 80.0; 
// 設(shè)置為0.5,其實就是為了取旋渦半徑用到的
const float uR = 0.5;
// 頂點著色器傳入的紋理坐標(biāo)
varying vec2 vTexcoord;
// 主函數(shù)
void main() {
    // 聲明一個整形二維向量,x = 512, y = 512,其實就是一個512寬高的正方形
    ivec2 ires = ivec2(512, 512);
    // 取出當(dāng)前正方形的邊長也就是被旋轉(zhuǎn)區(qū)域的圓的直徑
    float res = float(ires.s);
    // 當(dāng)前紋理坐標(biāo)
    vec2 st = vTexcoord;
    // 旋轉(zhuǎn)半徑由正方形的邊長和Ur相乘得到
    float radius = Res * uR;
    // 通過直徑獲取紋理坐標(biāo)對應(yīng)的物體坐標(biāo),st是當(dāng)前的紋理坐標(biāo)。
    vec2 xy = Res * st;
    // 取出紋理坐標(biāo)減去半徑之后的具體物體坐標(biāo),向量相減
    vec2 dxy = xy - vec2(res/2., res/2.);  
    // 當(dāng)前半徑
    float r = length(dxy);
    // atan函數(shù)獲取當(dāng)前紋理坐標(biāo)的正切值,與需要旋轉(zhuǎn)的值相加得到新的旋轉(zhuǎn)角度
    float beta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * (-(r/radius)*(r/radius) + 1.0);//(1.0 - r/radius);
    vec2 xy1 = xy;
    // 當(dāng)前半徑小于旋轉(zhuǎn)半徑時,將該點坐標(biāo)旋轉(zhuǎn)成新的目標(biāo)角度
    if(r <= radius)  {
        xy1 = res/2.0 + r*vec2(cos(beta), sin(beta));
    }
    // 計算新的紋理坐標(biāo)
    st = xy1/res;
    // 通過采樣器設(shè)置新的紋理坐標(biāo),并返回給內(nèi)置函數(shù)glFragColor,設(shè)置新的片元。
    vec3 irgb = texture2D(image, st).rgb;
    gl_FragColor = vec4( irgb, 1.0 );
}

前幾行代碼和之前唯一不同的是多了一個常量π的聲明,實現(xiàn)圖像的漩渦效果的原理是在某個半徑范圍里,把當(dāng)前采樣點旋轉(zhuǎn)一定角度,旋轉(zhuǎn)以后當(dāng)前點的顏色就被旋轉(zhuǎn)后的點的顏色代替,因此整個半徑范圍里會有旋轉(zhuǎn)的效果。如果旋轉(zhuǎn)的時候旋轉(zhuǎn)角度隨著當(dāng)前點離半徑的距離遞減,整個圖像就會出現(xiàn)漩渦效果。這里使用的了拋物線遞減因子:(1.0-(r/Radius)*(r/Radius) ),參考這里這里。
main函數(shù)中,不同變量的意義如下

PI:我們的計算中的π,取值3.14159265
uR:設(shè)置為0.5,其實就是為了取旋渦半徑用到的
ivec2:整形的二維向量,這里的ires其實就是一個512寬高的正方形
Res:取出當(dāng)前正方形的邊長
uD:旋轉(zhuǎn)的角度,Radius是我們旋轉(zhuǎn)的半徑
xy:通過直徑獲得紋理坐標(biāo)對應(yīng)的物體坐標(biāo)
dxy:取出紋理坐標(biāo)減去半徑之后的具體物體坐標(biāo)
r:當(dāng)前半徑

atan(dxy.y, dxy.x):獲取的當(dāng)前的夾角,如果不設(shè)置其他的值,那么當(dāng)前圖片沒有任何旋轉(zhuǎn)效果。
radians(uD) * 2.0:在原來夾角的基礎(chǔ)上加上我們設(shè)置的旋轉(zhuǎn)角度80x2 = 160
(-(r/Radius)(r/Radius) + 1.0):拋物線衰減因子,通過距離圓心的距離計算我們旋轉(zhuǎn)衰減的增益值

縮放效果

縮放效果圖及源碼在這里。
縮放效果也是常見的視頻特效的效果,圖片有一個放大的過程,然后再回彈。它可以通過修改頂點坐標(biāo)和紋理坐標(biāo)的對應(yīng)關(guān)系來實現(xiàn)。
修改頂點坐標(biāo)和紋理坐標(biāo),既可以在頂點著色器實現(xiàn),也可以在片元著色器上實現(xiàn),下面是一個頂點著色器的示例:

// 聲明頂點坐標(biāo)屬性
attribute vec4 Position; 
// 聲明紋理坐標(biāo)屬性(attribute修飾符只在頂點著色器中使用)
attribute vec2 TextureCoords; 
// 聲明紋理坐標(biāo)將修改后的紋理坐標(biāo)傳遞給片段著色器
varying vec2 TextureCoordsVarying; 
// 統(tǒng)一變量時間戳
uniform float Time;
// PI 
const float PI = 3.1415926; 
// 頂點著色器調(diào)用入口
void main (void) {
    // ?次縮放效果時? = 0.6ms 
    float duration = 0.6; 
    // 最?縮放幅度 
    float maxAmplitude = 0.3; 
    // 表示傳?的時間周期.即time的范圍被控制在[0.0~0.6];  mod(a,b),求模運算. 即a%b
    float time = mod(Time, duration); 
    // amplitude 表示振幅,引? PI 的?的是為了使? sin 函數(shù),將 amplitude 的范圍控制在 1.0 ~ 1.3 之間,并隨著時間變化 
    float amplitude = 1.0 + maxAmplitude * abs(sin(time * (PI / duration))); 
    // 將頂點坐標(biāo)的 x 和 y 分別乘上?個放?系數(shù),在紋理坐標(biāo)不變的情況下,就達(dá)到了拉伸的 效果。x,y 放?; z和w保存不變 
    gl_Position = vec4(Position.x * amplitude, Position.y * amplitude, Position.zw);
    // 紋理坐標(biāo)傳遞給TextureCoordsVarying,該坐標(biāo)可以在片段著色器中進(jìn)行使用
    TextureCoordsVarying = TextureCoords;
}

靈魂出竅效果

示例源地址
效果圖

20200816130455528.gif

由圖中可以發(fā)現(xiàn)此效果有多個圖層,最下面的圖層不動,上面的圖層隨著時間的變化變大,并且透明度變低直至透明。此效果是由多個圖層構(gòu)成,那么就需要顏色混合,所以此效果需要在片元著色器中實現(xiàn),頂點著色器不變。
片段著色器代碼如下

// 聲明為高精度
recision highp float;
// 聲明全局變量 采樣器
uniform sampler2D Texture;
// 頂點著色器傳入的紋理坐標(biāo)變量
varying vec2 TextureCoordsVarying;
// 統(tǒng)一變量時間
uniform float Time;

void main (void) {
    // 動畫效果時長
    float duration = 0.7;
    // 最大透明度
    float maxAlpha = 0.4;
    // 放大最大的倍數(shù)
    float maxScale = 1.8;
    
    // 當(dāng)前時間的進(jìn)度 0-1,mod函數(shù)求模,當(dāng)前時間%動畫時長
    float progress = mod(Time, duration) / duration; 
    // 當(dāng)前透明度的進(jìn)度 0.4 - 0
    float alpha = maxAlpha * (1.0 - progress);
    // 放大倍數(shù)的進(jìn)度 1 - 1.8
    float scale = 1.0 + (maxScale - 1.0) * progress;
    
    // 放大后的x值 0.5是中心點,中心點是不變的
    float weakX = 0.5 + (TextureCoordsVarying.x - 0.5) / scale;
    // 放大后的x值 0.5是中心點,中心點不變
    float weakY = 0.5 + (TextureCoordsVarying.y - 0.5) / scale;
    // 放大后的紋理坐標(biāo)
    vec2 weakTextureCoords = vec2(weakX, weakY);
    // 放大后的紋素圖層
    vec4 weakMask = texture2D(Texture, weakTextureCoords);
    // 正常的紋素圖層
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    // 通過矩陣和透明度的乘積再相加實現(xiàn)顏色混合模式
    gl_FragColor = mask * (1.0 - alpha) + weakMask * alpha;
}
顏色混合模式

在上述靈魂出竅示例中,用到了顏色混合模式。什么是混合模式?這里是一篇不錯的參考?;旌夏J绞菆D像處理技術(shù)中的一個技術(shù)名詞,主要功效是可以用不同的方法將對象顏色與底層對象的顏色混合。將一種混合模式應(yīng)用于某一對象時,在此對象的圖層或組下方的任何對象上都可看到混合模式的效果。通過上面索引的文章,我們已經(jīng)能夠了解到,顏色混合其實是對RGBA矩陣執(zhí)行加、減、乘,以及其他mix操作。

正片疊底是一種常見的混合方法,它的片段著色器代碼如下:

// 紋理坐標(biāo)
varying vec2 V_Texcoord;
 // 聲明統(tǒng)一變量基礎(chǔ)紋理
uniform sampler2D U_BaseTexture;
// 聲明統(tǒng)一變量混合紋理
uniform sampler2D U_BlendTexture;

void main() {
    // 從混合紋理采樣器中根據(jù)當(dāng)前紋理坐標(biāo)獲取RGBA
    vec4 blendColor=texture2D(U_BlendTexture,V_Texcoord);
    // 從混合紋理采樣器中根據(jù)當(dāng)前紋理坐標(biāo)獲取RGBA
    vec4 baseColor=texture2D(U_BaseTexture,V_Texcoord);
    // 重新賦予新的正片疊底后的紋理RGBA,正片疊底是顏色矩陣的相乘。
    gl_FragColor=blendColor*baseColor;
}

抖動效果

抖動效果是抖音的經(jīng)典圖標(biāo)和效果,其效果如下(示例代碼源同靈魂出竅)。


20200816130843536.gif

過程:圖層變大,并且顏色發(fā)生了偏移,然后所以的再變回原來的效果。著色器代碼如下:

// 聲明片段著色器中為高精度浮點型
precision highp float;
// 聲明統(tǒng)一變量紋理采樣器
uniform sampler2D Texture;
// 頂點著色器傳入的紋理坐標(biāo)
varying vec2 TextureCoordsVarying;
// 統(tǒng)一變量時間
uniform float Time;

void main (void) {
    // 抖動時長
    float duration = 0.7;
    // 放大上限
    float maxScale = 1.1;
    // 顏色偏移步長
    float offset = 0.02;
    // 當(dāng)前時間的進(jìn)度 0-1, mod是求模函數(shù),即當(dāng)前時間%抖動時長
    float progress = mod(Time, duration) / duration; // 0~1
    // 顏色偏移的進(jìn)度
    vec2 offsetCoords = vec2(offset, offset) * progress;
    // 縮放的進(jìn)度
    float scale = 1.0 + (maxScale - 1.0) * progress;
    
    // 放大后的紋理坐標(biāo),中心點的紋理坐標(biāo)+當(dāng)前坐標(biāo)減去中心點的坐標(biāo)的差除以縮放進(jìn)度,得到放大后的紋理坐標(biāo)
    vec2 ScaleTextureCoords = vec2(0.5, 0.5) + (TextureCoordsVarying - vec2(0.5, 0.5)) / scale;
    // R偏移的紋素,涉及到紋素的變化都需要從采樣器中讀取
    vec4 maskR = texture2D(Texture, ScaleTextureCoords + offsetCoords);
     // B偏移的紋素
    vec4 maskB = texture2D(Texture, ScaleTextureCoords - offsetCoords);
     // 放大后的紋素
    vec4 mask = texture2D(Texture, ScaleTextureCoords);
    
    gl_FragColor = vec4(maskR.r, mask.g, maskB.b, mask.a);
}

反相(也就是反色效果)

image.png
// 聲明片段著色器中為高精度浮點型
precision highp float;
// 聲明統(tǒng)一變量紋理采樣器
uniform sampler2D Texture;
// 頂點著色器傳入的紋理坐標(biāo)
varying vec2 TextureCoordsVarying;

void main (void) {
    // 根據(jù)紋理坐標(biāo)點獲取當(dāng)前紋理的紋素
    vec4 textureColor = texture2D(Texture,TextureCoordsVarying);
    // 將當(dāng)前紋素的RGB值取反,即可得到圖像的反相。
    gl_FragColor = vec4(1.0 - textureColor.r,1.0 -textureColor.g,1.0 -textureColor.b,1)
}

高斯模糊

效果如下:

image.png

參考鏈接
模糊過濾的基本原理是對附近像素進(jìn)行加權(quán)和來混合當(dāng)前像素顏色。通常使用的權(quán)重隨距離減小(二維屏幕空間距離),距離當(dāng)前像素較遠(yuǎn)的像素貢獻(xiàn)較小。

頂點著色器

// 聲明一個統(tǒng)一變量的 4x4矩陣
uniform mat4 uMVPMatrix;
// 紋理坐標(biāo) 給
attribute vec4 aPosition;
// 紋理坐標(biāo)
attribute vec4 aTextureCoord;

// 高斯算子大小(3 x 3)
const int GAUSSIAN_SAMPLES = 9;
// 統(tǒng)一變量 橫向偏移
uniform float texelWidthOffset;
// 統(tǒng)一變量 縱向偏移
uniform float texelHeightOffset;
// 計算后傳給片段著色器的紋理坐標(biāo)
varying vec2 textureCoordinate;
// 傳給片段著色器的模糊坐標(biāo)向量集
varying vec2 blurCoordinates[GAUSSIAN_SAMPLES];

void main() {
    // 通過矩陣變換 獲取新的位置
    gl_Position = uMVPMatrix * aPosition;
    // 紋理坐標(biāo)(x, y)
    textureCoordinate = aTextureCoord.xy;
    // 用于計算模糊步長
    int multiplier = 0;
    // 模糊步長
    vec2 blurStep;
    // 單個紋理的xy步長偏移量
    vec2 singleStepOffset = vec2(texelHeightOffset, texelWidthOffset);
    // 計算3x3矩陣中的模糊步長與當(dāng)前紋理坐標(biāo)的和,并保存到坐標(biāo)集合中。
    for (int i = 0; i < GAUSSIAN_SAMPLES; i++) {
        multiplier = (i - ((GAUSSIAN_SAMPLES - 1) / 2));
        blurStep = float(multiplier) * singleStepOffset;
        blurCoordinates[i] = aTextureCoord.xy + blurStep;
    }
}

片段著色器

// 聲明中等精度
precision mediump float;
// 
varying highp vec2 textureCoordinate;
uniform sampler2D inputTexture;
const lowp int GAUSSIAN_SAMPLES = 9;
varying highp vec2 blurCoordinates[GAUSSIAN_SAMPLES];

void main()
{
    lowp vec3 sum = vec3(0.0);
   lowp vec4 fragColor=texture2D(inputTexture,textureCoordinate);

    sum += texture2D(inputTexture, blurCoordinates[0]).rgb * 0.05;
    sum += texture2D(inputTexture, blurCoordinates[1]).rgb * 0.09;
    sum += texture2D(inputTexture, blurCoordinates[2]).rgb * 0.12;
    sum += texture2D(inputTexture, blurCoordinates[3]).rgb * 0.15;
    sum += texture2D(inputTexture, blurCoordinates[4]).rgb * 0.18;
    sum += texture2D(inputTexture, blurCoordinates[5]).rgb * 0.15;
    sum += texture2D(inputTexture, blurCoordinates[6]).rgb * 0.12;
    sum += texture2D(inputTexture, blurCoordinates[7]).rgb * 0.09;
    sum += texture2D(inputTexture, blurCoordinates[8]).rgb * 0.05;

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

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

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