源碼做者:jam3html
下載地址:https://github.com/Jam3/jam3-lesson-webgl-shader-threejs前端
這是一個很好的如何在threejs中使用自定義shader的入門教程。git
若github下載太慢,可可關注公衆號"實用圖形學遊玩指南及源碼",回覆「tsh1」使用國內網盤下載。github
下面的內容翻譯自readme.mdweb
在這堂課裏,咱們將學習如何在THREEJS中使用片斷和頂點着色器。請確保你對着色器和有基本的瞭解。npm
使用原生WebGL,那麼就要寫不少樣板文件,增長了很大工做量。而使用ThreeJS提供了便利的封裝,讓咱們更加專一於本身的事情。瀏覽器
若是你對ThreeJS和WebGL有基本的瞭解,那麼學習起來將輕鬆不少。babel
你能夠先直接下載ThreeJS,但在這裏咱們將直接使用npm和budo來更快投入開發。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)
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)
[./lib/createBunnyGeometry.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/createBunnyGeometry.js)
[./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)
在 ThreeJS,Mesh
是最基本的渲染3D形狀的材料。它由Geometry
和Material
組成,例以下面這段代碼在常見中添加一個正方體。
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
, 這是一種可使用vertexShader
和fragmentShader
的特殊材質。咱們使用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') });
💡 也許
glslify比
brfs
更合你意。
就像建立box的mesh同樣,咱們建立了 bunny的mesh,
// Setup our mesh const mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
這個簡單的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將讓它被每個像素或每個頂點調用。
頂點着色器引進了兩個新概念:attributes和uniforms.
頂點着色器將在幾何體的每一個頂點上運行,包含一些_attributes,好比_Position (XYZ), UV coordinates (XY), Normal (XYZ)。須要在JavaScript程序中預先給attributes賦值,而後傳到頂點着色器中。
例如,一個三角形有3個頂點,每一個頂點有一個_attribute_保存了XYZ左邊。對每一個三角形來講,GPU將執行三次頂點着色器程序。
咱們的 bunny 頂點着色器只有一個 attribute 。
attribute vec3 position;
頂點和片斷着色器都能使用_uniforms,_這是一個全部片斷使用的常量,在JavaScript中賦值好,而後再着色器中讀取。
咱們咱們使用一個 uniform來爲咱們的mesh規定一個常量RGB顏色。
ThreeJS提供一些給着色器的內建uniforms,例如以下的四個矩陣,都是mat4的
projectionMatrix
— 將3D世界座標系轉換爲2D屏幕座標系viewMatrix
—PerspectiveCamera
世界矩陣的逆modelMatrix
—Mesh
的局部座標矩陣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
同樣。
這裏,咱們將更加每一個像素離中心(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)表明遠離中心點。
這裏,咱們將在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 passedvNormal
for 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 }
, 參數,意味着法向量是獨立的。你也能夠改變這個參數看看聯合的法向量是什麼效果。
而後,咱們將沿着表面法向量推進每一個三角形來實現爆炸效果!
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
做爲位移的程度。
片斷着色器無需改變。
最後,咱們將實現爆炸動畫,咱們只須要給着色器添加一個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 } } });
咱們定義了time
uniform (着色器中將使用相同的名字), 數據類型是浮點型 (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! 🐰
你可能很疑惑爲何ThreeJS 同時擁有 ShaderMaterial 和 RawShaderMaterial。我建議使用RawShaderMaterial,由於不容易出錯,但你得多花一些時間來調節各類參數。
你也可使用ShaderMaterial
這樣就能夠跳過一些設置,直接使用 ThreeJS's的內建attributes, uniforms。
void main () { gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.0); }
若是github下載太慢,可關注公衆號,回覆「tsh1」得到源碼國內網盤下載地址,以及持續跟蹤最新消息!