DAY 30
1
Modern Web

## 帆船與海

### 動態的海面法向量 `oceanNormal`

``````vec3 normal = texture2D(u_normalMap, v_texcoord * 256.0).xyz * 2.0 - 1.0;
``````

``````+vec3 oceanNormal(vec2 pos);
+
void main() {
vec2 reflectionTexcoord = (v_reflectionTexcoord.xy / v_reflectionTexcoord.w) * 0.5 + 0.5;
-  vec3 normal = texture2D(u_normalMap, v_texcoord * 256.0).xyz * 2.0 - 1.0;
+  vec3 normal = oceanNormal(v_worldSurface.xz);

reflectionTexcoord += normal.xy * 0.1;
// ...
}

+vec3 oceanNormal(vec2 position) {
+}
``````

``````#define OCEAN_SAMPLE_DISTANCE 0.01
vec3 oceanNormal(vec2 position) {
vec3 p1 = oceanSurfacePosition(position);
vec3 p2 = oceanSurfacePosition(position + vec2(OCEAN_SAMPLE_DISTANCE, 0));
vec3 p3 = oceanSurfacePosition(position + vec2(0, OCEAN_SAMPLE_DISTANCE));

return normalize(cross(
normalize(p2 - p1), normalize(p3 - p1)
));
}
``````

### 波浪函數

``````vec3 oceanSurfacePosition(vec2 position) {
float height = 0.0;
return vec3(position, height);
}
``````

``````float wave = exp(sin(x) - 1.0);
``````

`sin()` 的波形本身就是於 +1 ~ -1 之間不停來回，減 1 套 `exp()` 指數函數 得到最大值為 1 的波浪函數，實做給 `height` 試試看

``````vec3 oceanSurfacePosition(vec2 position) {
float height = exp(sin(position.x) - 1.0);
return vec3(position, height);
}
``````

``````vec3 oceanSurfacePosition(vec2 position) {

float waveX = dot(position, direction) * 2.5 + u_time * 5.0;
float height = exp(sin(waveX) - 1.0);

return vec3(position, height);
}
``````

`-2.355``3.14``-0.75` 倍，也就是反向旋轉 135 度；將 `dot(position, direction)` 乘上 2.5 可以縮小波長、時間乘上 5.0 加快波浪速度

``````float hash(vec2 p) {
p = fract(p * vec2(234.83, 194.51));
p += dot(p, p + 24.9);
return fract(p.x * p.y);
}
``````

`fract()` 用來取小數，這整個 function 其實沒有什麼道理，其中的 `234.83`, `194.51` 等數字也可以隨便改，某種程度算是 seed 吧，總之輸入一個二維向量，得到介於 0~1 的數字

`````` vec3 oceanSurfacePosition(vec2 position) {
+  vec2 id = floor(position);
+
+  float directionRad = (hash(id) - 0.5) * 0.785 - 2.355;

float waveX = dot(position, direction) * 2.5 + u_time * 5.0;
float height = exp(sin(waveX) - 1.0);

return vec3(position, height);
}
``````

``````float localWaveHeight(vec2 id, vec2 position) {
float directionRad = (hash(id) - 0.5) * 0.785 - 2.355;

float distance = length(id + 0.5 - position);
float strength = smoothstep(1.5, 0.0, distance);

float waveX = dot(position, direction) * 2.5 + u_time * 5.0;
return exp(sin(waveX) - 1.0) * strength;
}
``````
• `directionRad`, `direction`, `waveX` 以及 `exp(sin(waveX) - 1.0)` 是從 `oceanSurfacePosition()` 搬過來的
• `distance` 透過 `length()` 計算 `position``id` 格子中央的距離
• `strength` 表示海浪的強度，如果距離為 0 則強度最強為 1，而且我們只打算取到鄰近 1 格，距離到達 1.5 時表示已經到達影響力的邊緣，這時強度為 0
• `smoothstep(edge0, edge1, x)` 可以把輸入值 `x` 介於 edge0 < x < edge1 轉換成 0 ~ 1 之間的值，`x` 超出 edge0 時回傳 0、超出 edge1 時回傳 1，而且此函數是一個曲線，會平滑地到達邊緣，更詳細的資料可以參考其維基百科

``````vec3 oceanSurfacePosition(vec2 position) {
vec2 id = floor(position);

float height = 0.0;

for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
height += localWaveHeight(id + vec2(i, j), position);
}
}

return vec3(position, height);
}
``````

`````` vec3 oceanSurfacePosition(vec2 position) {
+  position *= 6.2;
vec2 id = floor(position);

float height = 0.0;

for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
height += localWaveHeight(id + vec2(i, j), position);
}
}

+  height *= 0.15;

return vec3(position, height);
}
``````

`position` 乘以 6.2 可以使格子更小，`height` 則是很直覺地乘以 0.15 降低高度，海面就平靜許多了，筆者也轉一下視角觀察反射光的反應：

### 打磨 -- 帆船的晃動

`````` function renderBoat(app, viewMatrix, programInfo) {
-  const { gl, textures, objects } = app;
+  const { gl, textures, objects, time } = app;

const worldMatrix = matrix4.multiply(
-    matrix4.translate(0, 0, 0),
-    matrix4.scale(1, 1, 1),
+    matrix4.xRotate(Math.sin(time * 0.0011) * 0.03 + 0.03),
+    matrix4.translate(0, Math.sin(time * 0.0017) * 0.05, 0),
);

// ...
}
``````

### 感謝各位讀者的閱讀

WebGL 作為底層的技術，懂得活用其功能，尤其是 shader （以及數學）的話，能製作出的效果肯定是不勝枚舉的，本系列文中有許多概念是沒有提到，像是 raycast 得到滑鼠、觸控位置的 3D 物件迷霧效果等，甚至透過 WebGL 把 GPU 當成無情的運算機器，舉 Conway's Game of Life 為例，WebGL 的實做性能顯然遠超過先前筆者用 rust + webassembly 的版本部落格文章），現在也可以想像的到使用 WebGL 實做的方向：使用 framebuffer，在 fragment shader 讀取上回合地圖 texture 相關的 cell 來繪製該回合的地圖

### 1 則留言

0

iT邦新手 4 級 ‧ 2021-09-30 19:51:09

PastLeo iT邦新手 5 級 ‧ 2021-10-01 23:17:34 檢舉