ThreeJS Shader的效果樣例網格平面和網格球體(一)

火星写程序發表於2024-07-19

本文中效果主要採用ThreeJS 中的著色器(Shader)以及結合ShaderMaterial實現的。

主要用到的內建方法有:

step:是一個階躍函式,它將一個浮點數與一個閾值進行比較,並返回一個階躍值;
   比如step(edge, x), 如果 x 小於等於 edge,則返回 0.0, 如果 x 大於 edge ,則返回 1.0。
fract:用於獲取浮點數的小數部分。它返回輸入值的小數部分,即去除整數部分後的部分。比如fract(1.5),返回0.5;

一、網格平面

const vertex = '\
    varying vec3 vPos;\
    void main() {\
      vPos = position;\
      gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ); \
    }\
  ';
  const frag = '\
    varying vec3 vPos;\
    void main() {\
      vec3 mask1 = vec3(step(0.5, fract(vPos.x * 2.0)));\
      vec3 mask2 = vec3(step(0.5, fract(vPos.y * 2.0)));\
      vec3 mask3 = vec3(step(0.5, fract(vPos.z * 2.0)));\
      vec3 color = abs(mask1 - mask2);\
      gl_FragColor = vec4(color, 1.0);\
    }\
  ';

 原理:如果設定平面的大小為2,那麼座標軸X點的範圍為-1.0 ~ 1.0,已X座標為示例,資料變化形式如下圖 

  

    如上圖可以將資料分為4個部分,X軸和Y軸同理:

      1) 0~0.25的資料經過fract和step函式處理後資料變為0;

      2) 0.25~0.5的資料經過fract和step函式處理後資料變為1;

      3) 0.5~0.75的資料經過處理變為0;

      4) 0.75~1.0的資料經過處理變為1;

     最後將生成的向量X軸-Y軸資料可以繪製成如下圖:

  

  這樣就生成了第一象限的圖形,第二、三、四象限結果同上。

二、網格狀的球體 

本列中涉及到GLSL的幾個內建函式:

dot:兩個向量的點積,可以獲得向量的夾角

asin: 反三角函式,獲得弧度值

1. 第一個圖就是要實現的最終效果,一個網格狀的球體,實現原理主要可以分為分別計算經度方向的線圈和緯度方向的線圈。

2. 與平面網格計算顏色值相同,將兩個顏色值相減即可得到一個網格球體

3. 如何實現緯度方向的線圈?

  實現邏輯:將球體沿球心縱向切一刀生成一個圓形橫截面M,將圓分成弧度相等的N個圓弧,然後再間隔開賦予不同的顏色就可以形成圖一的效果。

const vertex = '\
    varying vec3 vPos;\
    void main() {\
      vPos = position;\
      gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ); \
    }\
  ';
  // 獲取UV點對應的單位向量B
  // 獲取Z軸方向的單位向量A
  // 計算向量B和向量A的夾角
  // 透過degree將夾角的弧度轉換成角度,除以要拆分的條數latBeta,透過配合fract和step即可獲取間隔的0、1值
  // 最後生成間隔的黑白顏色值
  const frag = '\
    uniform float latBeta;\
    varying vec3 vPos;\
    void main() {\
      vec3 latEveryVec = normalize(vPos);\
      vec3 latBaseVec = normalize(vec3(vPos.x, 0, vPos.z));\
      float latAngle = asin(dot(latBaseVec, latEveryVec));\
      vec3 latColor = vec3(step(0.5, fract(degrees(latAngle) / latBeta)));\
      gl_FragColor = vec4(latColor, 1.0);\
    }\
  ';

4. 如何實現經度方向的線圈?

實現邏輯:與第三步生成緯度方向的線圈類似,將球體沿球心橫向切一刀生成一個圓形橫截面M,將圓分成弧度相等的N個圓弧,然後再間隔開賦予不同的顏色就可以形成圖一的效果。

const vertex = '\
    varying vec3 vPos;\
    void main() {\
      vPos = position;\
      gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ); \
    }\
  ';
  // 獲取UV點對應的單位向量B
  // 獲取X軸正方向的單位向量A
  // 計算向量B和向量A的夾角
  // 透過degree將夾角的弧度轉換成角度,除以要拆分的條數lonBeta,透過配合fract和step即可獲取間隔的0、1值
  // 最後生成間隔的黑白顏色值
  const frag = '\
    uniform float lonBeta;\
    varying vec3 vPos;\
    void main() {\
      vec3 lonEveryVec = normalize(vec3(vPos.x, 0.0, vPos.z));\
      vec3 lonBaseVec = vec3(0.0, 0.0, 1.0);\
      float lonAngle = asin(dot(lonBaseVec, lonEveryVec));\
      vec3 lonColor = vec3(step(0.5, fract(degrees(lonAngle) / lonBeta)));\
      gl_FragColor = vec4(lonColor, 1.0);\
    }\
  ';

完整程式碼如下:

const vertex = '\
    varying vec3 vPos;\
    void main() {\
      vPos = position;\
      gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ); \
    }\
  ';
  const frag = '\
    uniform float latBeta;\
    uniform float lonBeta;\
    varying vec3 vPos;\
    void main() {\
      vec3 latEveryVec = normalize(vPos);\
      vec3 latBaseVec = normalize(vec3(vPos.x, 0, vPos.z));\
      float latAngle = asin(dot(latBaseVec, latEveryVec));\
      vec3 latColor = vec3(step(0.5, fract(degrees(latAngle) / latBeta)));\
      \
      vec3 lonEveryVec = normalize(vec3(vPos.x, 0.0, vPos.z));\
      vec3 lonBaseVec = vec3(0.0, 0.0, 1.0);\
      float lonAngle = asin(dot(lonBaseVec, lonEveryVec));\
      vec3 lonColor = vec3(step(0.5, fract(degrees(lonAngle) / lonBeta)));\
      vec3 color = abs(latColor - lonColor);\
      gl_FragColor = vec4(color, 1.0);\
    }\
  ';

相關文章