threejs自定義shader

源碼:

源碼做者:jam3html

下載地址:https://github.com/Jam3/jam3-lesson-webgl-shader-threejs前端

這是一個很好的如何在threejs中使用自定義shader的入門教程。git

若github下載太慢,可可關注公衆號"實用圖形學遊玩指南及源碼",回覆「tsh1」使用國內網盤下載。github

下面的內容翻譯自readme.mdweb

WebGL 課程 — ThreeJS Shaders

在這堂課裏,咱們將學習如何在THREEJS中使用片斷和頂點着色器。請確保你對着色器和有基本的瞭解。npm

爲何要用 ThreeJS?

使用原生WebGL,那麼就要寫不少樣板文件,增長了很大工做量。而使用ThreeJS提供了便利的封裝,讓咱們更加專一於本身的事情。瀏覽器

若是你對ThreeJS和WebGL有基本的瞭解,那麼學習起來將輕鬆不少。babel

Setup

你能夠先直接下載ThreeJS,但在這裏咱們將直接使用npmbudo來更快投入開發。less

💡 npm和budo是前端的模組,在這裏你並不須要知道有關的太多信息。

第一步,你應該首先克隆這個項目到本地而且安裝相關依賴。函數

git clone https://github.com/Jam3/jam3-lesson-webgl-shader-threejs.git

# move into directory
cd jam3-lesson-webgl-shader-threejs

# install dependencies
npm install

# start dev server
npm run start

你應該看到以下畫面,這生成一個供index.html使用的bundle.js

如今來瀏覽器中打開http://localhost:9966/你就能看到一個白色的bunny,你可用鼠標拖動攝像機來從不一樣角度觀察這個3D的bunny。

這個項目經過babelify使用了Babel,也使用了brfs

代碼概覽

代碼被放在不一樣的文件裏。

  • [./lib/index.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/index.js)
    這裏是咱們demo的核心部分,建立了咱們的應用,幾何,網格和渲染循環。咱們確保THREE能正常工做,咱們經過require命令從其餘文件引入。
global.THREE = require('three');
  • [./lib/createApp.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/createApp.js)
    這個樣本程序使用orbit-controls,這樣你就能用鼠標控制攝像機了。這不是這節課的重點。
  • [./lib/createBunnyGeometry.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/createBunnyGeometry.js)
    這個文件從網格多邊形(mesh primitive)中建立了ThreeJS幾何體,也就是一個小兔子
  • [./lib/shader.frag](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.frag)
    這是你將在本節課使用的片斷着色器。
  • [./lib/shader.vert](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.vert)
    這是你在本節課使用的頂點着色器。
  • [./lib/reference/](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/reference)
    這裏你能夠看到一些寫好的着色器示例。

Mesh & Shader

在 ThreeJS,Mesh是最基本的渲染3D形狀的材料。它由GeometryMaterial組成,例以下面這段代碼在常見中添加一個正方體。

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 'red' });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

./lib/index.js, 咱們使用相似的代碼建立3D的bunny的幾何模型。

// Get a nicely prepared geometry
const geometry = createBunnyGeometry({ flat: true });

如今要爲咱們的bunny建立材質。

咱們使用RawShaderMaterial, 這是一種可使用vertexShaderfragmentShader的特殊材質。咱們使用brfs, 一種 source transform,這樣咱們就能在開發時在單獨的文件裏編寫shader,並在打包時將它們包括進去。

const path = require('path');
const fs = require('fs');

// Create our vertex/fragment shaders
const material = new THREE.RawShaderMaterial({
  vertexShader: fs.readFileSync(path.join(__dirname, 'shader.vert'), 'utf8'),
  fragmentShader: fs.readFileSync(path.join(__dirname, 'shader.frag'), 'utf8')
});
💡 也許 glslifybrfs更合你意。

就像建立box的mesh同樣,咱們建立了 bunny的mesh,

// Setup our mesh
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

步驟一:你的第一個shader

參考:shader.vert,shader.frag

這個簡單的Shader使用白色的紋理覆蓋了bunny。在你挑戰複雜的事情,先把簡單的事情作好總時沒錯的。

頂點着色器

這個代碼位於[./lib/shader.vert](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.vert)

attribute vec3 position;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

void main () {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

片斷着色器和頂點着色器須要 一個main()函數,WebGL將讓它被每個像素或每個頂點調用。

頂點着色器引進了兩個新概念:attributesuniforms.

Attributes

頂點着色器將在幾何體的每一個頂點上運行,包含一些_attributes,好比_Position (XYZ), UV coordinates (XY), Normal (XYZ)。須要在JavaScript程序中預先給attributes賦值,而後傳到頂點着色器中。

例如,一個三角形有3個頂點,每一個頂點有一個_attribute_保存了XYZ左邊。對每一個三角形來講,GPU將執行三次頂點着色器程序。

咱們的 bunny 頂點着色器只有一個 attribute 。

attribute vec3 position;

Uniforms

頂點和片斷着色器都能使用_uniforms,_這是一個全部片斷使用的常量,在JavaScript中賦值好,而後再着色器中讀取。

咱們咱們使用一個 uniform來爲咱們的mesh規定一個常量RGB顏色。

ThreeJS提供一些給着色器的內建uniforms,例如以下的四個矩陣,都是mat4的

  • projectionMatrix— 將3D世界座標系轉換爲2D屏幕座標系
  • viewMatrixPerspectiveCamera世界矩陣的逆
  • modelMatrixMesh的局部座標矩陣
  • modelViewMatrix— view 和 model 矩陣的聯合

你須要以下定義它們才能使用。

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

頂點投影

頂點着色器的目標是將咱們的3D數據轉換爲能被WebGL光柵化投影到2D屏幕上的數據。

爲了作到這點,咱們使用以下代碼。

gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
💡 咱們如今沒必要關心 1.0爲何要做爲位置裏的 w份量。但你也能夠看看 here.

咱們也能夠寫的不一樣,用vec4定義attribute,WebGL將用 w= 1.0默認擴展每一個向量,而後分別乘上三個矩陣

attribute vec4 position;

uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;

void main () {
  gl_Position = projectionMatrix * viewMatrix * modelMatrix * position;
}

片斷着色器

若是你打開了[./lib/shader.frag](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.frag), 你將看到以下代碼

precision highp float;

void main () {
  gl_FragColor = vec4(1.0);
}

第一行precision, 定義了GPU使用的浮點數的精度。當使用RawShaderMaterial, 你就得在片斷着色器中第一行這樣定義。你可使用lowp,mediump, 或highp, 但通常來講將使用highp

💡 頂點着色浮點數據默認精度是 highp, 因此以前咱們不須要定義它。

而後,咱們使用gl_FragColor, 這是一個vec4做爲顏色輸出,你應該將這個向量的四個份量都寫上。這裏咱們將它寫爲純白:(1.0, 1.0, 1.0, 1.0)。與ShaderToy中的fragColor同樣。

步驟2:與中心的距離

參見:shader2.vert,shader2.frag

這裏,咱們將更加每一個像素離中心(0, 0, 0)的距離XYZ來決定它們的顏色。

頂點着色器:

這一步,頂點着色器相似於這樣:

attribute vec4 position;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying float distFromCenter;

void main () {
  distFromCenter = length(position.xyz);
  gl_Position = projectionMatrix * modelViewMatrix * position;
}

這個着色器使用了新概念:varyings. 這個值只能在頂點着色器中寫入,並被傳遞到片斷着色器中。 這裏,咱們使用position向量的長度,即這個頂點於vec3(0.0)的距離。

💡 你也可使用內置的 distance,例如:
distFromCenter = distance(position.xyz, vec3(0.0));

片斷着色器

precision highp float;

varying float distFromCenter;
void main () {
  gl_FragColor = vec4(vec3(distFromCenter), 1.0);
}

在片斷着色器中,varyings是隻讀的,來自於頂點着色器。片斷着色器將用頂點間的插值着色。好比一個頂點的值是0.0,另外一個頂點的值是1.0,這兩個頂點中間的像素的值將是介於這兩個值之間的值。

這裏,像素的顏色將有distFromCenter來決定 ,也就是說黑色(0.0,0.0,0.0)表明靠近中心點,白色(1.0,1.0,1.0)表明遠離中心點。

步驟三:可視化法向量

參見:shader3.vert,shader3.frag

這裏,咱們將在mesh裏可視化法向量。這是一個新的attribute,已經在createBunnyGeometry.js定義好。法向量是用來定義三角形朝向的, RGB對應着XYZ,也就是說你看到的藍色地方的三角形對着Z軸,其餘同理。

頂點着色器

attribute vec4 position;
attribute vec3 normal;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying vec3 vNormal;

void main () {
  vNormal = normal;
  gl_Position = projectionMatrix * modelViewMatrix * position;
}

這個着色器裏,咱們使用了一個新的attribute,normal, 而且使用varying 類型的vNormal傳遞給片斷着色器。

片斷着色器

precision highp float;

varying vec3 vNormal;

void main () {
  gl_FragColor = vec4(vNormal, 1.0);
}

Here, we simply render the passedvNormalfor each fragment. It ends up looking nice, even though some values will be clamped because they are less than 0.0.

這裏,咱們知識簡單的用法向量當成顏色來渲染,看起來效果很不錯,雖然小於0的值會被轉換爲0。

💡 index.js中建立的幾何體使用了 { flat: true }, 參數,意味着法向量是獨立的。你也能夠改變這個參數看看聯合的法向量是什麼效果。

步驟4:爆炸三角形

參考:shader4.vert,shader4.frag

而後,咱們將沿着表面法向量推進每一個三角形來實現爆炸效果!

頂點着色器

attribute vec4 position;
attribute vec3 normal;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying vec3 vNormal;

void main () {
  vNormal = normal;

  vec4 offset = position;
  float dist = 0.25;
  offset.xyz += normal * dist;
  gl_Position = projectionMatrix * modelViewMatrix * offset;
}

這個着色器裏,咱們將使用一個向量來調節每一個頂點的位置。這裏咱們直接使用normal來做爲位移的方向,dist做爲位移的程度。

片斷着色器無需改變。

步驟5:動畫

參考:shader5.vert,shader5.frag

最後,咱們將實現爆炸動畫,咱們只須要給着色器添加一個uniform

咱們須要在js中設置好,咱們打開index.js而且定義一個uniforms

// Create our vertex/fragment shaders
const material = new THREE.RawShaderMaterial({
  vertexShader: fs.readFileSync(path.join(__dirname, 'shader.vert'), 'utf8'),
  fragmentShader: fs.readFileSync(path.join(__dirname, 'shader.frag'), 'utf8'),
  uniforms: {
    time: { type: 'f', value: 0 }
  }
});

咱們定義了timeuniform (着色器中將使用相同的名字), 數據類型是浮點型 (ThreeJS 使用'f'來表示 — 參見here), 而且提供默認值。

而後,咱們的渲染循環將在每幀增長這個值。

// Time since beginning
let time = 0;

// Start our render loop
createLoop((dt) => {
  // update time
  time += dt / 1000;

  // set value
  material.uniforms.time.value = time;

  // render
  ...
}).start();

頂點着色器

attribute vec4 position;
attribute vec3 normal;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

uniform float time;

varying vec3 vNormal;

void main () {
  vNormal = normal;

  vec4 offset = position;

  // Animate between 0 and 1
  // sin(x) returns a value in [-1...1] range
  float dist = sin(time) * 0.5 + 0.5;

  offset.xyz += normal * dist;
  gl_Position = projectionMatrix * modelViewMatrix * offset;
}

在這個着色器中,咱們將在main函數簽名定義咱們的uniform。因爲這是一個uniform,這將在渲染時對全部頂點保持同樣的值。

uniform float time;

咱們使用GLSL內建的sin函數(與JS中的Math.sin同樣),將返回-1.0到1.0的值。咱們將它歸一化到0到1,這樣咱們的mesh就只向外面爆炸。

float dist = sin(time) * 0.5 + 0.5;

Voilà! 咱們有了個爆炸效果的 bunny! 🐰

附錄: ShaderMaterial vs RawShaderMaterial

你可能很疑惑爲何ThreeJS 同時擁有 ShaderMaterial 和 RawShaderMaterial。我建議使用RawShaderMaterial,由於不容易出錯,但你得多花一些時間來調節各類參數。

你也可使用ShaderMaterial這樣就能夠跳過一些設置,直接使用 ThreeJS's的內建attributes, uniforms。

void main () {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.0);
}

下一步

在知乎專欄上持續關注圖形學內容!

若是github下載太慢,可關注公衆號,回覆「tsh1」得到源碼國內網盤下載地址,以及持續跟蹤最新消息!

相關文章
相關標籤/搜索