[ - OpenGLES3.0 - ] 第三集 主線 - shader着色器與圖片特效

問:學OpenGL能幹嗎? 答: 隨心所欲。java

提及OpenGLES,你們可能都敬而遠之,其實它並無想象中的那麼可怕,固然也並無那麼容易
都0202年了,本系列使用OpenGLES3.0,這是一次有預謀的計劃:算法

本篇主要介紹着色器的代碼的使用,並據此完成特效圖片的自定義組件
到如今你應該能夠貼個圖在GLSerfaceView中了,若是還不會,出門左轉第二集編程


1.紋理貼圖着色器代碼

先從這張貼圖開始提及吧bash


1.1 頂點着色器:texture.vsh

#version 300 es 聲明版本爲OpenGL ES 3.00規範
in表示輸入量,java --> glsl
vec3表示三維向量,vec2表示二維向量,
out 表示輸出量,此處 vsh--> fsh
gl_Position表示頂點的位置,微信

---->[texture.vsh]----
#version 300 es
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec2 aTexCoord;

out vec2 vTexCoord;

void main(){
    gl_Position = vec4(aPosition.x, aPosition.y, aPosition.z, 1.0);
    vTexCoord = aTexCoord;
}
複製代碼

layout (location = 0) 表示aPosition句柄爲0,
以下java代碼入參時根據0爲aPosition賦值, aTexCoord同理
注意目前模擬器對layout支持不太好,需從0開始。建議真機測試,隨意指定,對應便可編輯器

---->[GLTextureDrawer.java]----
    private int aPosition = 0;//位置的句柄
    private int aTexCoord = 1;//貼圖座標句柄
複製代碼

什麼的頂點着色代碼是說,須要傳入兩個變量,aPosition和aTexCoord
其中gl_Position是一個四維向量,肯定渲染時頂點位置,其x,y,z使用aPosition份量
vTexCoord做爲輸出量傳遞給片斷着色器,其值爲aTexCoordide


1.2 片斷着色器:texture.fsh

precision 表示精度 lowp低、mediump中、highp高
很容易想到,精度越↑,效果越↑,但着色器速度↓
in vec2 vTexCoord; 表示接受頂點的輸入的vTexCoord變量
uniform 統一變量,在着色器執行期間它的值是不變的
sampler2D 類型:2D紋理函數

#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

void main(){
    outColor = texture(sTexture, vTexCoord);
}
複製代碼

片斷的out標識的變量就是輸出的色值,sTexture承載紋理,vTexCoord承載座標
經過texture函數獲取值像素的色值,做爲輸出量。就至關於圖片拓印到了"紙"上post


2. 着色器顏色效果處理

着色器提供了一個絕佳的可能性,讓咱們可以操做像素,
經過rgba,理論上咱們能夠創造一切視覺體驗,更不用說so easy的圖片特效
下面就由簡入難,分析幾個常見的圖片效果。測試


2.1 灰度圖片特效

texture函數返回一個四維向量,是一個顏色的rgba四個份量值
咱們只要輕輕的將outColor的rgb都改成g值便可

---->[gray.fsh]----
#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

void main(){
    vec4 color= texture(sTexture, vTexCoord);
    outColor = vec4(color.g, color.g, color.g, 1.0);
}
複製代碼

2.2 純黑白圖片特效

還有一種灰度計算方式35911(左一):g = r * 0.3 + g * 0.59 + b * 0.11;
變灰以後,根據灰值能夠獲取純黑白色的圖片,即像素值大於閾值爲黑,小於爲白
經過閾值的更改能夠控制顏色的通量,閾值↓,通量↓。閾值=0,不通,白色
經過閾值的控制,顏色不太複雜的圖就能夠變成線稿(左三)。

#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

const float threshold = 0.3;//閾值

void main(){
    vec4 color= texture(sTexture, vTexCoord);
    float r = color.r;
    float g = color.g;
    float b = color.b;

    g = r * 0.3 + g * 0.59 + b * 0.11;
    g= g <= threshold ? 0.0 : 1.0;

    outColor = vec4(g, g, g, 1.0);
}
複製代碼

2.3 向着色器中傳參控制

threshold若是隻能寫死在着色器代碼裏,未免有些雞肋。
如何將它放入程序中進行動態控制呢? 好比下面的效果:

---->[black_white.fsh]----
#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

uniform float uThreshold;//uniform入參

void main(){
    vec4 color= texture(sTexture, vTexCoord);
    float r = color.r;
    float g = color.g;
    float b = color.b;

    g = r * 0.3 + g * 0.59 + b * 0.11;
    g= g <= uThreshold ? 0.0 : 1.0;

    outColor = vec4(g, g, g, 1.0);
}
複製代碼

其實和前面的頂點入參差很少,在繪製的代碼裏找到uThreshold句柄再賦值便可
以後就是setThreshold方法設置值,外部經過一個Slider進行控制

//尋找句柄
uThreshold= GLES30.glGetUniformLocation(program, "uThreshold");

public void draw() {
    //...
    GLES30.glUseProgram(program);
    GLES30.glUniform1f(uThreshold, threshold); //賦值
複製代碼

這樣你是否是對着色器有了那麼一丟丟的感受,不過別急着驚訝,這僅僅是個開始。


2.4 負片、懷舊、冷調

負片是將rgb色值用1去減,獲取相對的顏色
懷舊是將圖片變成偏黃的暖調,
冷調中只是將r和b的色值進行對調,就能達到相反的效果

---->[負片]----
r = 1.0 - color.r;
g = 1.0 - color.g;
b = 1.0 - color.b;

---->[懷舊]----
r = 0.393* r + 0.769 * g + 0.189* b;
g = 0.349 * r + 0.686 * g + 0.168 * b;
b = 0.272 * r + 0.534 * g + 0.131 * b;

---->[冷調]----
b = 0.393* r + 0.769 * g + 0.189* b;
g = 0.349 * r + 0.686 * g + 0.168 * b;
r = 0.272 * r + 0.534 * g + 0.131 * b;
複製代碼

其餘的代碼都是相似的,核心是色值的算法。
固然你也能夠經過變量來控制這些係數,就能夠成爲簡單的圖片編輯器。


4.5. 流年效果

主要是對b值進行處理,arg越大,愈加藍

#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

void main(){
    float arg = 3.0;
    
    vec4 color= texture(sTexture, vTexCoord);

    float r = color.r;
    float g = color.g;
    float b = color.b;

    b = sqrt(b)*arg;
    if (b>1.0) b = 1.0;

    outColor = vec4(r, g, b, 1.0);
}
複製代碼

3. 着色器座標效果處理

除了色值,還有一個很是重要的可用數據就是貼圖座標
能夠經過座標值進行一些位置上的處理,好比對稱,旋轉,縮放,分屏等

3.1 圖片x,y反向

如今不要把它對稱一張圖片,而是一個個像素拼組成的對象
如今vTexCoord記錄着這些像素的位置,改動vTexCoord就能夠改變像素的位置
寬爲1.0,若是僅是拿1.0- pos.x,就至關於右邊的像素跑到左邊了(下圖2),其餘同理

---->[x反]---
#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){

    vec2 pos = vTexCoord.xy;
    pos.x= 1.0- pos.x;
    outColor = texture(sTexture, pos);
}

---->[y反]---
#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    vec2 pos = vTexCoord.xy;
    pos.y= 1.0- pos.y;
    outColor = texture(sTexture, pos);
}

---->[x,y反]---
#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    vec2 pos = vTexCoord.xy;
    pos.x= 1.0- pos.x;
    pos.y= 1.0- pos.y;
    outColor = texture(sTexture, pos);
}
複製代碼

3.2 分屏

分屏也就是根據像素位置判斷,去修改讀取紋理的位置座標,從而影響渲染
好比二分,當y大於0.5是,讀取的位置是pos.y - 0.5,至關於仍是渲染上面部分
你也能夠調整0.5這個參數,達到不等分分屏。

  • 二分
#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;


void main(){

    vec2 pos = vTexCoord.xy;
    if (pos.y<= 0.5) {
        pos.y = pos.y ;
    }else{
        pos.y = pos.y - 0.5;
    }
    outColor = texture(sTexture, pos);

}
複製代碼

也能夠左右分鏡:

#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

void main(){
    vec2 pos = vTexCoord;
    if (pos.x > 0.5) {
        pos.x = 1.0 - pos.x;
    }
    outColor = texture(sTexture, pos);
}
複製代碼

  • 三分
#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    vec2 pos = vTexCoord.xy;
    if (pos.y<= 1.0/3.0) {
        pos.y = pos.y * 1.0;
    }else if(pos.y<= 2.0/3.0){
        pos.y = (pos.y - 1.0/3.0) * 1.0;
    }else{
        pos.y = (pos.y - 2.0/3.0) * 1.0;
    }
    outColor = texture(sTexture, pos);
}
複製代碼

  • 四分
#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    vec2 pos = vTexCoord.xy;
    if(pos.x <= 0.5){
        pos.x = pos.x ;
    }else{
        pos.x = pos.x - 0.5;
    }

    if (pos.y<= 0.5) {
        pos.y = pos.y ;
    }else{
        pos.y = pos.y - 0.5;
    }
    outColor = texture(sTexture, pos);
}
複製代碼

也許你以爲四分屏時填滿多好啊(圖中),
若是每一個分鏡能夠處理不一樣效果那就更棒了(圖右)

#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    vec2 pos = vTexCoord.xy;
    if(pos.x <= 0.5){
        pos.x = pos.x * 2.0;
    }else{
        pos.x = (pos.x - 0.5) * 2.0;
    }

    if (pos.y<= 0.5) {
        pos.y = pos.y * 2.0;
    }else{
        pos.y = (pos.y - 0.5) * 2.0;
    }
    outColor = texture(sTexture, pos);
}
複製代碼

四分特效其實也很簡單,可就是分紅四塊去判斷,分別處理罷了
核心的算法都是上面介紹過的。是否是感受本身不知不覺就會寫些複雜的對象了?

#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    vec2 pos = vTexCoord.xy;
    vec4 result;
    if(pos.x <= 0.5 && pos.y<= 0.5){ //左上
        pos.x = pos.x * 2.0;
        pos.y = pos.y * 2.0;
        vec4 color = texture(sTexture, pos);
        result = vec4(color.g, color.g, color.g, 1.0);
    }else if (pos.x >= 0.5 && pos.y<= 0.5){//右上
        pos.x = (pos.x - 0.5) * 2.0;
        pos.y = (pos.y - 0.5) * 2.0;
        vec4 color= texture(sTexture, pos);
        float r = color.r;
        float g = color.g;
        float b = color.b;
        g = r * 0.3 + g * 0.59 + b * 0.11;
        g= g <= 0.4 ? 0.0 : 1.0;
        result = vec4(g, g, g, 1.0);
    }else if (pos.y> 0.5 && pos.x < 0.5) {//左下
        pos.y = pos.y * 2.0;
        pos.x = pos.x * 2.0;
        vec4 color= texture(sTexture, pos);
        float r = color.r;
        float g = color.g;
        float b = color.b;
        r = 0.393* r + 0.769 * g + 0.189* b;
        g = 0.349 * r + 0.686 * g + 0.168 * b;
        b = 0.272 * r + 0.534 * g + 0.131 * b;
        result = vec4(r, g, b, 1.0);
    }else  if (pos.y> 0.5 && pos.x > 0.5){//右下
        pos.y = (pos.y - 0.5) * 2.0;
        pos.x = (pos.x - 0.5) * 2.0;
        vec4 color= texture(sTexture, pos);
        float r = color.r;
        float g = color.g;
        float b = color.b;
        b = 0.393* r + 0.769 * g + 0.189* b;
        g = 0.349 * r + 0.686 * g + 0.168 * b;
        r = 0.272 * r + 0.534 * g + 0.131 * b;
        result = vec4(r, g, b, 1.0);
    }
    outColor = result;
}
複製代碼

3.3. 局部效果

能夠控制位置量後,咱們就能夠作些有意思的事,好比,局部特效
能夠經過區域的判斷,來指定部分區域進行操做,好比(橢)圓

原來很簡單,經過到指定點的距離判斷,就能夠截獲區域

#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){

    float centerX =0.4;
    float centerY =0.8;
    float raduius =0.3;

    vec2 pos = vTexCoord.xy;

    vec4 color= texture(sTexture, vTexCoord);
    float r = color.r;
    float g = color.g;
    float b = color.b;
    
    if((pos.x-centerX)*(pos.x-centerX)+(pos.y-centerY)*(pos.y-centerY)<raduius*raduius){//表示在圓的區域內
        outColor = vec4(g, g, g, 1.0);
    }else{
        outColor = vec4(r, g, b, 1.0);
    }
}
複製代碼

注意,因爲寬高不一樣,而最大值都是1.0,因此量綱是不一樣的
區域是橢圓也不用驚訝,解決方法很簡單,傳入一個寬高比校訂便可
下面的rate能夠提成變量,由java代碼傳入,java的Bitmap能夠獲取寬高
若是有閒情逸致,其餘三個量也能提出來,就是一個灰圖的探照燈
若是還有閒情逸致,能夠定義多個特效效果,經過變量控制一下
就能變成特效探照燈,照到哪裏,哪裏就特效。

#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    float rate= 1000.0/1369.0;
    float centerX =0.5;
    float centerY =0.5/rate;
    float raduius =0.25;

    vec2 pos;
    pos.x = vTexCoord.x;
    pos.y= vTexCoord.y/rate;
    vec4 color= texture(sTexture, vTexCoord);
    float r = color.r;
    float g = color.g;
    float b = color.b;

    if ((pos.x-centerX)*(pos.x-centerX)+(pos.y-centerY)*(pos.y-centerY)<raduius*raduius){ //表示在圓的區域內
        outColor = vec4(g, g, g, 1.0);
    } else {
        outColor = vec4(r, g, b, 1.0);
    }
}
複製代碼

3.4. 光照效果

根據上面的效果,實現一下局部的光照效果

#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    float rate= 1000.0/1369.0;
    float centerX =0.5;
    float centerY =0.5/rate;
    float radius =0.6;
    float strength = 150.0/255.0;//#設置光照強度

    vec2 pos;
    pos.x = vTexCoord.x;
    pos.y= vTexCoord.y/rate;
    vec4 color= texture(sTexture, vTexCoord);
    float r = color.r;
    float g = color.g;
    float b = color.b;

    float distance = sqrt((pos.x-centerX)*(pos.x-centerX)+(pos.y-centerY)*(pos.y-centerY));

    if (distance<radius){ //表示在圓的區域內
        //按照距離大小計算加強的光照值
       float result = strength*( 1.0 - distance / radius );
        r =  r+result;
        g =  g+result;
        b =  b+result;
        outColor = vec4(r, g, b, 1.0);
    } else {
        outColor = vec4(r, g, b, 1.0);
    }
}
複製代碼

3.5 矩形馬賽克

馬賽克須要指定一行的個數,以及多少個佔一塊
好比100塊,5*5,也就是一行20個小塊。這些參數均可以提出去玩

#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

void main(){
    float rate= 1000.0/1369.0;
    float cellX= 5.0;
    float cellY= 5.0;
    float rowCount=100.0;

    vec2 pos = vTexCoord;
    pos.x = pos.x*rowCount;
    pos.y = pos.y*rowCount/rate;
    
    pos = vec2(floor(pos.x/cellX)*cellX/rowCount, floor(pos.y/cellY)*cellY/(rowCount/rate))+ 0.5/rowCount*vec2(cellX, cellY);
    outColor = texture(sTexture, pos);
}
複製代碼

局部特效以及會了,馬賽克也ok了,局部馬賽克還遠嗎?


局部馬賽克

#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    float rate= 1000.0/1369.0;
    float centerX =0.4;
    float centerY =0.3/rate;
    float radius =0.15;

    float cellX= 1.0;
    float cellY=1.0;
    float rowCount=100.0;

    vec2 pos;
    pos.x = vTexCoord.x;
    pos.y= vTexCoord.y/rate;

    float distance = sqrt((pos.x-centerX)*(pos.x-centerX)+(pos.y-centerY)*(pos.y-centerY));

    if (distance<radius){ //表示在圓的區域內
        vec2 pos = vTexCoord;
        pos.x = pos.x*rowCount;
        pos.y = pos.y*rowCount/rate;

        pos = vec2(floor(pos.x/cellX)*cellX/rowCount, floor(pos.y/cellY)*cellY/(rowCount/rate))+ 0.5/rowCount*vec2(cellX, cellY);
        outColor = texture(sTexture, pos);
    } else {
        outColor = texture(sTexture, vTexCoord);
    }
}
複製代碼

3.6 圖片點陣

和矩形馬賽克相似,將圖片分紅若干個塊,當在cell半徑以內, 繪製UVMosaic點的像素值,不然,繪製白色

#version 300 es
precision mediump float;
out vec4 outColor;
in vec2 vTexCoord;
uniform sampler2D sTexture;

void main(){
    float rate= 1000.0/1369.0;
    float cellX= 3.0;
    float cellY=3.0;
    float rowCount=100.0;

    vec2 sizeFmt=vec2(rowCount, rowCount/rate);
    vec2 sizeMsk=vec2(cellX, cellY/rate);
    vec2 posFmt = vec2(vTexCoord.x*sizeFmt.x, vTexCoord.y*sizeFmt.y);
    vec2 posMsk = vec2(floor(posFmt.x/sizeMsk.x)*sizeMsk.x, floor(posFmt.y/sizeMsk.y)*sizeMsk.y)+ 0.5*sizeMsk;
    
    float del = length(posMsk - posFmt);

    vec2 UVMosaic = vec2(posMsk.x/sizeFmt.x, posMsk.y/sizeFmt.y);

    vec4 result;
    if (del< cellX/2.0)
    result = texture(sTexture, UVMosaic);
    else
    result = vec4(1.0,1.0,1.0,0.0);
    outColor = result;
}
複製代碼

基於此很容易實現圓形的馬賽克

#version 300 es
//英雄所見...

void main(){
    //英雄所見...
    else
    result = texture(sTexture, vTexCoord);
    outColor = result;
}
複製代碼

3.7 圖片加點

這個特效讓我賺了一頓午餐錢,需求是將圖片變灰加點,原本毫無頭緒
聰明的我靈機一動,這不就是小版的馬賽克,載把馬賽克區域塗黑嗎?

#version 300 es
precision mediump float;
out vec4 outColor;

in vec2 vTexCoord;

uniform sampler2D sTexture;

void main(){
    float rate= 1000.0/1369.0;
    float cellX= 1.0;
    float cellY=1.0;
    float rowCount=100.0;
    float space =4.0;

    vec2 sizeFmt=vec2(rowCount, rowCount/rate);
    vec2 sizeMsk=vec2(cellX, cellY/rate);
    vec2 posFmt = vec2(vTexCoord.x*sizeFmt.x, vTexCoord.y*sizeFmt.y);
    vec2 posMsk = vec2(floor(posFmt.x/sizeMsk.x)*sizeMsk.x, floor(posFmt.y/sizeMsk.y)*sizeMsk.y)+ 0.5*sizeMsk;

    float del = length(posMsk - posFmt);
    vec2 UVMosaic = vec2(posMsk.x/sizeFmt.x, posMsk.y/sizeFmt.y);

    vec4 result;
    if (del< cellX/space)
    result=vec4(0.2,0.2,0.2,0.1);
    else
    result = texture(sTexture, vTexCoord);
    outColor = vec4(result.g,result.g,result.g,1.0);
}
複製代碼

3.8 靈魂出竅

核心是週期性的獲取一個逐漸放大,逐漸透明的圖片和原圖進行疊合

#version 300 es
precision highp float;
in vec2 vTexCoord;
out vec4 outColor;
uniform sampler2D sTexture;
//傳遞進來的時間
uniform float uProgress;

void main () {
  float t = 0.7; //週期
  float maxAlpha = 0.4;//第二圖最大透明度
  float maxScale = 1.8;//第二圖放大最大比率
  //進度
  float progress = mod(uProgress, t) / t; // 0~1
  //當前的透明度
  float alpha = maxAlpha * (1.0 - progress);
  //當前的放大比例
  float scale = 1.0 + (maxScale - 1.0) * progress;
  //根據放大比例獲取新的圖層紋理座標
  vec2 weakPos = vec2(0.5 + (vTexCoord.x - 0.5) / scale, 0.5 + (vTexCoord.y - 0.5) / scale);

  //新圖層紋理座標對應的紋理像素值
  vec4 weakMask = texture(sTexture, weakPos);
  vec4 mask = texture(sTexture, vTexCoord);

  //紋理像素值的混合公式,得到混合後的實際顏色
  outColor = mask * (1.0 - alpha) + weakMask * alpha;
}
複製代碼

這裏我將uProgress提出變量,方便顏色動態效果


本篇到這就差很少了,你應該對shader着色器多了那麼一丟丟的理解
其次,這些特效均可以用在相機和視頻播放之中,這也是OpenGL的強大之處。
本篇已經很長了,還有一些特效,留在相機和視頻支線篇再說,敬請期待。


@張風捷特烈 2020.01.15 未允禁轉
個人公衆號:編程之王
聯繫我--郵箱:1981462002@qq.com --微信:zdl1994328
~ END ~

相關文章
相關標籤/搜索