作者:位元組流動
來源:
https://blog.csdn.net/Kennethdroid/article/details/104536532跳動的心

浏覽部落格時,偶然間發現這個"跳動的心"特效,瞬間被感動了,當得知這個特效是用純代碼實作( GLSL 實作)的,确實又被驚到了。
追溯該特效最初的來源,最終在 SahderToy 網站看到它的原始實作,另外在 SahderToy 上還發現了無數類似驚人的特效,并且這些特效的實作代碼完全公開。
ShaderToy 是一個跨浏覽器的線上社群,并且是用于通過 WebGL 建立和共享着色器的工具,用于在 Web 浏覽器中學習和教授 3D 計算機圖形學。![]()
不瞞你說,我被這個特效感動哭了(OpenGL ES 特效)跳動的心參考
在 SahderToy 網站上浏覽了一番,感覺仿佛發現了新大陸,該網站支援線上編寫并運作 GLSL 腳本,堪稱 GL 界的 Github 。
我們把網站上"跳動的心"特效的腳本轉換為 OpenGLES 對應的 GLSL 腳本在手機上運作,并對整個腳本進行一一解析,完整的代碼如下:
#version 300 es
precision highp float;
layout(location = 0) out vec4 outColor;//輸出
uniform float u_time;//時間偏移量
uniform vec2 u_screenSize;//螢幕尺寸
const float PI = 3.141592653;
void main()
{
// move to center
vec2 fragCoord = gl_FragCoord.xy;
vec2 p = (2.0*fragCoord-u_screenSize.xy)/min(u_screenSize.y,u_screenSize.x);
// background color
vec3 bcol = vec3(1.0,0.8,0.8)*(1.0-0.38*length(p));
// animate
float tt = u_time;
float ss = pow(tt,.2)*0.5 + 0.5;
ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5)*exp(-tt*4.0);
p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);
// shape
p.y -= 0.25;
float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);
float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);
// color
float s = 0.75 + 0.75*p.x;
s *= 1.0-0.4*r;
s = 0.3 + 0.7*s;
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );
vec3 hcol = vec3(1.0,0.5*r,0.3)*s;
vec3 col = mix( bcol, hcol, smoothstep( -0.06, 0.06, d-r) );
outColor = vec4(col,1.0);
}
關于内建變量
gl_FragCoord
,從
舊文中我們知道:與螢幕空間坐标相關的視區是由視口設定函數 glViewport 函數給定,并且可以通過片段着色器中内置的 gl_FragCoord 變量通路,
gl_FragCoord
的 x 和 y 表示該片段的螢幕空間坐标 ((0,0) 在左下角),其取值範圍由 glViewport 函數決定,螢幕空間坐标原點位于左下角。
下面一段代碼主要作用是調整坐标系,将原點從左下角移至螢幕坐标系中央,這樣所有片元的向量 gl_FragCoord.xy 均以螢幕中心為起點,則向量 p 就是螢幕中心與螢幕像素點坐标之間的方向向量。
// move to center
vec2 fragCoord = gl_FragCoord.xy;
vec2 p = (2.0 * fragCoord - u_screenSize.xy) / min(u_screenSize.y,u_screenSize.x);
接下來計算背景顔色,
length(p)
表示計算目前片元(像素)與螢幕中心點的距離,背景顔色以
vec3(1.0,0.8,0.8)
該顔色為基礎,距離螢幕越遠顔色越暗。
// background color
vec3 bcol = vec3(1.0,0.8,0.8)*(1.0-0.38*length(p));
這時,我們把背景顔色渲染出來看看:
接着繪制心形,主要利用反正切函數值和目前片元(像素)與螢幕中心點的距離相比較,來确定心形狀的邊界。GLES 中的反正切函數
atan(p.x,p.y)
取值範圍是[-π, π],然後除以 PI 後,取值範圍變成了 [-1, 1] 。
// shape
p.y -= 0.25;//向螢幕下方偏移 0.25 個機關
float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);//取絕對值
//float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);//這個函數主要使心的形狀更加扁平化,暫時先忽略
我們通過上圖來了解心形的繪制過程,每條直線上像素點得到的 a 值都是相同的,我們用黃點表示距離螢幕中心的遠近,然後通過 d-r 的值來确定心形的邊界。
vec3 col = mix(bcol, hcol, smoothstep( -0.06, 0.06, d-r) );
以上是繪制心形的關鍵函數,hcol 是心的顔色,bcol 是背景色。
smoothstep
是一個很常用的平滑過渡函數,當第三個參數比 -0.06 小時,傳回 0,比0.06 大時傳回 1 ,如果在 -0.06 和 0.06 之間,則傳回 0 到 1 之間的值,其作用是用于平緩 d-r 的值在正負交界處的突變。
mix
函數用于權重混合心的顔色和背景色,根據
smoothstep
函數特性,在心形内用心的顔色,在心形外用背景色,而邊界則是兩種顔色之間的模糊過渡。
再說說心形扁平化函數的作用,當我們不使用扁平化函數,而是直接用 h-r 來控制心的形狀,得到的圖像是一個又胖又肥的心形,這樣你大概可以得知這個函數的作用。
//float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);//這個函數主要使心的形狀更加扁平化,暫時先忽略
vec3 col = mix(bcol, hcol, smoothstep( -0.06, 0.06, h-r) );
然後看看心的顔色生成,由表達式
vec3(1.0,0.5*r,0.3)
可以看出心的顔色是紅色,且由螢幕中心向四周紅色逐漸減弱,然後産生一系列漸變,最後分出心形内外的區域顔色。
// color
float s = 0.75 + 0.75*p.x;//在 x 軸方向有一個漸變
s *= 1.0-0.4*r;//根據距離産生漸變
s = 0.3 + 0.7*s;//增亮了左側暗部區域
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );//借助變量 r/d 分出了心形内外的區域顔色
vec3 hcol = vec3(1.0,0.5*r,0.3)*s;
我們直接輸出心的顔色 hcol ,看看是什麼效果:
最後是跳動效果的實作,其原理就是對螢幕像素在 x、y 方向進行周期性偏移,偏移幅度由特殊的函數來控制。
// animate
float tt = u_time;//u_time 為周期性輸入的時間
float ss = pow(tt,.2)*0.5 + 0.5;
ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5)*exp(-tt*4.0);//控制幅度的函數
p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);
我們通過下面的代碼控制輸入時間周期為 2000ms 。
float time = static_cast<float>(fmod(GetSysCurrentTime(), 2000) / 2000);
glUniform1f(m_TimeLoc, time);
振幅控制函數的模拟曲線如下圖所示,大緻可以看出心跳動時幅度變化情況。
最後還有一點需要注意的是 GLSL 腳本中精度的聲明,文中代碼我們使用的是
highp
精度,但是當使用
mediump
精度時,會出現由于精度不夠導緻的毛刺現象,如下圖所示:
實作代碼路徑:
NDK_OpenGLES_3_0參考
https://www.shadertoy.com/view/XsfGRn https://blog.csdn.net/candycat1992/article/details/44040273「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。