iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 9
1
Modern Web

WebGL 與 Three.js 初探系列 第 9

[Day9] webGL 修羅道 - 特效實戰及總結

到目前為止,已經介紹了不少 webGL 的 API 以及 GLSL 的使用與撰寫。是時候來寫寫看有趣的特效了,其實在網路上隨便搜尋關鍵字都能看到不少絢爛奪目的 webGL 特效,不過現在你也有能力看看別人的 shader 是如何撰寫,並且自己做出特效來了。

今天要示範的效果是 distortion。
有興趣的讀者們可以到 codepen 觀看效果,或是直接觀看程式碼

distortion

不知道大家有沒有看過沙漠過熱、或是溫度太高而引起的現象。光因為氣溫太高造成折射,所以我們看到的畫面會有一點扭曲的感覺。

這邊我們的想法是,將紋理座標傳入後,對座標做扭曲處理,再透過 texture2D 跟 sampler 結合,計算出 fragment 的顏色。

聽起來還蠻簡單的吧!那我們馬上開始吧

撰寫 vertex shader 及 frag shader

首先,為了將紋理座標順利傳入,我們需要透過 varying 來傳遞。這邊簡單撰寫頂點著色器如下:

attribute vec2 a_textureCoords;
attribute vec4 a_position;

varying vec2 v_textureCoords;

attribute float a_pointSize;

void main() {
	gl_Position = a_position;
	gl_PointSize = 10.0;

	v_textureCoords = a_textureCoords;
}

最主要的處理函式在 fragment shader 當中,我們扭曲的方式是套用 sin 函數,當然如果大家有更逼真的扭曲方式,也歡迎自己動手試試看!

同樣的效果,我們可以使用在沙漠、火焰特效、飛機引擎等等,只要對應好位置,就可以做出不錯看的效果唷!

當然,用 canvas 2D 也能做到類似的效果,不過相對地處理起來會比較麻煩一些。因為你必須要對圖片中的每個像素做運算,這不是個輕鬆的工作。

撰寫 distortion function

float distortion(float point,float freq, float speed) {
  return sin(point * freq + ((3.1415/2.0) * u_time * speed));
}

point 代表傳入的點位置,第二和第三個參數分別代表頻率與速度。我們這邊很簡單地回傳用 sin 處理過後的數值。因為我們要實現動畫效果,所以可以看到 u_time 這個變數,我們待會會讓 Javascript 傳值進來。

完成了!我們簡單地對頂點座標做處理,並且加入 sin 函數做扭曲,大致完成一半了,接下來只要撰寫 main function 以及 Javascript 的部分,我們的程式應該就能順利操作了。

void main() {
	vec2 distortions = distortions(v_textureCoords);
	gl_FragColor = texture2D(u_sampler, vec2(distortions.x, distortions.y));
}

很簡潔吧!我們把處理過後的紋理座標和 sampler 結合在一起,並且傳值給 gl_FragColor

完整的 fragment shader 會長這樣:

precision mediump float;

varying vec2 v_textureCoords;

uniform float u_time;
uniform sampler2D u_sampler;
uniform vec2 u_resolution;


vec2 pixel() {
  return 1.0 / u_resolution;
}

// make position distortion by sin func.
float distortion(float point,float freq, float speed) {
  return sin(point * freq + ((3.1415/2.0) * u_time * speed));
}

vec2 distortions(vec2 pos) {
	vec2 intensity = vec2(1.0,1.0) * pixel();

  vec2 waves = vec2(
    distortion(pos.y,100.0,0.35),
    distortion(pos.x,100.0,0.4)
  );

  return pos + (waves * intensity * 1.0);
}

void main() {
	vec2 distortions = distortions(v_textureCoords);

	gl_FragColor = texture2D(u_sampler, vec2(distortions.x, distortions.y));
}

撰寫 Javascript

回到 Javascript 的部分,為了加速說明,我會省略連接 program 與 shader 的程式碼,詳細可以到文末的範例程式碼看看。

傳值給 Shader

const time = gl.getUniformLocation(program, 'u_time');
const resolution    = gl.getUniformLocation(program, 'u_resolution');
gl.uniform2f(resolution, canvas.width, canvas.height);
const textureCoords = gl.getAttribLocation(program, 'a_textureCoords');
const position      = gl.getAttribLocation(program, 'a_position');
const pointSize     = gl.getAttribLocation(program, 'a_pointSize');

const textures = [
	  0, 0,
	  1, 0,
	  0, 1,
	  1, 1,
	];

const vertices = [
  -0.8, -0.8,
  0.8, -0.8,
  -0.8, 0.8,
  0.8, 0.8,
];

當然妳可以依據自己的需求來對應不同的紋理座標以及頂點座標,也可以將 buffer 結合在一起方便取用。最後將紋理綁定後即可。

完成圖會長這樣:
http://ithelp.ithome.com.tw/upload/images/20161223/20103565SMo8UODVys.png

結論

這裡示範了 webGL 的簡單特效,妳可以到 shader toy 尋找關於更多有趣的著色器,看著他學習,試著自己打造一個吧!

好了,webGL 的介紹總算告了一段落,明天開始我們就要介紹 three.js 了。不過在這之前,先讓我們來了解一下基本知識吧!

未來 3 天,我會介紹光源、投影的概念、再開始我們的 three.js 之旅。


上一篇
[Day8] webGL 修羅道 - 3D 紋理貼圖
下一篇
[Day10] 寫在 Three.js 之前 - 投影基礎
系列文
WebGL 與 Three.js 初探30

尚未有邦友留言

立即登入留言