three.js学习笔记(十四)——Shaders着色器
什么是著色器?
實際上著色器是WebGL的主要組件之一。如果我們在沒接觸Three.js情況下開始學習WebGL,著色器將是我們首先且必須要學的知識,這也是為什么原生WebGL很難入門。
著色器是一種使用GLSL(OpenGL Shading Language)編寫并在GPU上運行的程序。它們被用于定位幾何體的每個頂點,并為該幾何體的每個可見像素著色。使用“像素Pixel”來描述其實并不準確,因為渲染的每個點不一定與屏幕上的每個像素相匹配,因此我們更傾向于使用術語“片元fragment”。
之后我們會向著色器發送大量數據,如頂點坐標、網格變換、攝像機及其視野范圍的信息、顏色、紋理、燈光、霧等參數。然后,GPU會按照著色器的指示處理所有的這些數據,接著幾何體便出現在渲染中。
有倆種類型的著色器,并且我們都會用到它們。
頂點著色器Vertex Shader
頂點著色器(Vertex Shader)的作用是定位幾何體的頂點。其思想是發送頂點位置、網格變換(如定位position、旋轉rotation和縮放scale)、攝影機信息(如定位position、旋轉rotation和視野fov)。然后,GPU將按照頂點著色器中的指示處理所有這些信息,以便將頂點投影到2D空間,該空間將成為我們的渲染render,也就是我們的畫布canvas。
頂點著色器對頂點實現了一種通用的可編程方法。
使用頂點著色器時,其代碼將應用于幾何體的每個頂點。但有些數據(如頂點的位置)會在每個頂點之間發生變化。這種類型的數據(在頂點之間變化的數據)稱為attribute屬性變量。
- attribute:使用頂點數組封裝每個頂點的數據,一般用于每個頂點都各不相同的變量,如頂點的位置等。
但有些數據不需要像網格的位置那樣在每個頂點之間進行變換,用于對同一組頂點組成的單個3D物體中所有頂點都相同的變量,如當前光源的位置,相機的位置,這種類型的數據(頂點之間不發生變化的數據)稱為uniform統一變量。
- uniform:頂點著色器使用的常量數據,不能被著色器修改,一般用于對同一組頂點組成的單個3D物體中所有頂點都相同的變量,如當前光源的位置。
頂點著色器會首先觸發,當放置完頂點后,GPU會知道幾何體的哪些像素是可見的,然后可以接著下去使用片元著色器。
片元著色器Fragment Shader
片元著色器的作用是為幾何體的每個可見片元(像素)進行著色。
我們會創建片元著色器,可以通過使用uniform將數據(像是顏色)和著色器發送至GPU,之后GPU就會按照指令對每個片元進行著色。
片段著色器中最簡單直接的指令是可以使用相同的顏色為所有片段著色。如果我們只設置了顏色屬性,我們就得到了與MeshBasicMaterial等效的材質(每個可見像素都被著色白色)。
參考 著色器語言三種變量attribute、uniform 和 varying
簡單總結
- 頂點著色器渲染定位頂點位置
- 片段著色器為該幾何體的每個可見片元(像素)進行著色
- 片段著色器在頂點著色器之后執行
- 在每個頂點之間會有變化的數據(如頂點的位置)稱為attribute,只能在頂點著色器中使用
- 頂點之間不變的數據(如網格位置或顏色)稱為uniform,可以在頂點著色器和片段著色器中使用
- 從頂點著色器發送到片元著色器中的插值計算數據被稱為varying
為什么要寫著色器?
Three.js的內置材質試圖涵蓋盡可能多的情況,但是依舊有其局限性。如果我們想要突破限制,就必須自個寫自己的著色器。
當然也可能是出于性能原因。像MeshStandardMaterial這樣的材質非常精細,涉及大量代碼和計算。如果我們編寫自己的著色器,我們可以將功能和計算保持在最小限度,這樣就可以更好控制與優化演算性能了。
編寫我們自己的著色器也是將后期處理添加到渲染中的一個很好的方法,但我們將在后面專門的課程中再看到這一點。
一旦掌握了著色器,它們將成為所有項目中不可或缺的必備工具。
使用原始著色器材質(RawShaderMaterial)創建自己的第一個著色器
要創建第一個著色器,我們需要創建特定的材質。該材質可以是著色器材質ShaderMaterial或原始著色器材質RawShaderMaterial。這兩者之間的區別在于前者會自動將一些代碼添加到著色器代碼中(內置attributes和uniforms),而后者正如其名,什么都不會添加。
準備工作
我們的初始場景有一塊MeshBasicMaterial材質的簡單平面,將之材質替換為RawShaderMaterial原始著色器材質。然后你會發現報錯了,這是因為我們還沒添加著色器。如前所述,我們需要同時提供頂點著色器和片元著色器,并在里面書寫程序:
const material = new THREE.RawShaderMaterial({vertexShader: `uniform mat4 projectionMatrix;uniform mat4 viewMatrix;uniform mat4 modelMatrix;attribute vec3 position;void main(){gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);}`,fragmentShader: `precision mediump float;void main(){gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}` })得到這樣的一個結果:
分離兩種著色器
新建一個shaders文件夾,在里面新建倆個文件vertex.glsl和fragment.glsl,將上邊代碼拷貝到對應文件中,再在vscode的插件商店安裝shader languages support for VS code,這樣我們的.glsl文件也就有了語法高亮提示。
之后導入:
然而我們會報一個webpack錯誤,因為需要告訴webpack如何處理.glsl文件。
去/bundler/webpack.common.js,修改如下:
這個規則僅告訴webpack提供文件的原始內容。然后重啟服務,會看到與前面一樣的場景。
我們在其他材質中介紹的大多數常見屬性(如wireframe、side、transparent、flatShading)仍然適用于RawShaderMaterial
const material = new THREE.RawShaderMaterial({vertexShader: testVertexShader,fragmentShader: testFragmentShader,wireframe: true })
但是像map、alphaMap、opacity、 color等屬性將不再生效,因為我們需要自己在著色器中編寫這些特性。
GLSL
OpenGL著色語言(OpenGL Shading Language)是用來在OpenGL中著色編程的語言,它是一種類C語言,下面先學習一下基礎語法。
日志
沒有控制臺,因此無法記錄值。這是因為代碼針對每個頂點和每個片段執行。記錄一個值是沒有意義的。
縮進
縮進不重要,可以隨意。
分號
任何指令的結尾都需要分號。哪怕忘記一個分號都可能導致編譯錯誤,使得整個材料都不起作用。
變量
GLSL是一種強類型語言,如C語言一般。
不能在操作中混合使用float和int,會報錯:
float a = 1.0; int b = 2; float c = a * b;但是可以通過類型轉換進行操作:
float a = 1.0; int b = 2; float c = a * float(b);Vector 2
如果我們想存儲具有x和y屬性的2個坐標這樣的值,我們可以使用vec2
vec2 foo = vec2(1.0, 2.0);空的vec2將導致錯誤:
vec2 foo = vec2();我們可以在創建vec2后更改這些屬性:
vec2 foo = vec2(0.0); foo.x = 1.0; foo.y = 2.0;執行vec2與浮點相乘等操作將同時操作x和y屬性:
vec2 foo = vec2(1.0, 2.0); foo *= 2.0;Vector 3
vec3與vec2類似,但具有第三個名為z的屬性。當需要3D坐標時,使用它非常方便:
vec3 foo = vec3(0.0); vec3 bar = vec3(1.0, 2.0, 3.0); bar.z = 4.0;雖然我們可以使用x、y和z,但我們也可以使用r、g和b。這只是語法糖,結果完全一樣。當我們使用vec3存儲顏色時,它非常有效:
vec3 purpleColor = vec3(0.0); purpleColor.r = 0.5; purpleColor.b = 1.0;vec3可以通過部分使用vec2來創建:
vec2 foo = vec2(1.0, 2.0); vec3 bar = vec3(foo, 3.0);我們也可以使用vec3的一部分來生成vec2:
vec3 foo = vec3(1.0, 2.0, 3.0); vec2 bar = foo.xy;Vector 4
最后,vec4的原理與前兩個類似,但第四個值命名為w或a(顏色alpha)
vec4 foo = vec4(1.0, 2.0, 3.0, 4.0); vec4 bar = vec4(foo.zw, vec2(5.0, 6.0));還有其他類型的變量,如mat2、mat3、mat4或sampler2D,我們將在后面看到這些變量。
在著色器內,一般命名以gl_開頭的變量是著色器的內置變量,除此之外webgl_和_webgl還是著色器保留字,自定義變量不能以webgl_或_webgl開頭。變量聲明一般包含<存儲限定符><數據類型><變量名稱>,以attribute vec4 a_Position為例,attribute表示存儲限定符,vec是數據類型,a_Position為變量名稱。
原生函數
GLSL有許多內置的經典函數,如sin、cos、max、min、pow、exp、mod、clamp,也有非常實用的函數,如cross、dot、mix、step、smoothstep、length、distance、reflect、refract、normalize。
不幸的是,沒有對初學者友好的文檔,而且大多數時候,我們在網絡上進行搜索,結果通常出現在以下網站:
- Kronos Group OpenGL reference pages:本文檔涉及OpenGL,但您將看到的大多數標準函數都與WebGL兼容。不要忘了WebGL只是一個訪問OpenGL的JavaScript API。
- Book of shaders documentation:《Book of shaders》主要關注片元著色器,與Three.js無關。但是這是一個很好的學習資源,它有自己的詞匯表。
理解頂點著色器
下面開始嘗試理解著色器里面的內容。
首先注意的是,頂點著色器的目的是將幾何體的每個頂點放置在2D渲染空間上。換句話說,頂點著色器將3D頂點坐標轉換為2D畫布坐標。
main函數
它會自動調用,并且不會返回return任何內容
void main() { }gl_Position
變量gl_Position原本就已經聲明好,是一個內置變量,我們只需重新分配它。
這個變量將會包含屏幕上的頂點的位置,main函數中的代碼便是要正確設置該變量。
這條長代碼將返回一個vec4,這意味著我們可以直接在gl_Position變量上使用其x、y、z和w屬性。
會看到平面往右上角移動,但是要注意,我們并沒有在3D空間里邊移動平面,雖然看起來像我們在Three.js里面控制位置一樣。不過,我們確實是在2D空間上移動了平面。
如何理解上面的話,想象一下你在桌上畫一幅帶透視效果的圖畫,然后你將整張圖畫往右上角移動,但是圖畫中的透視效果并未因此而改變。
可能你想要知道如果gl_Position的目標是在2D空間上定位頂點,那為什么還需要四個值?實際上這些坐標并不是精確的位于二維空間,而是位于我們說的需要四個維度的剪裁空間Clip Space。
剪裁空間Clip Space是指在-1到+1范圍內的所有3個方向(x、y和z)上的空間。這就像把所有東西都放在3D盒子里一樣,任何超出此范圍的內容都將被“剪裁”并消失。第四個值(w)負責透視。
幸運的是,所有這些都是自動完成的,作為初學者,我們不需要掌握所有東西,只用大概了解下就行。
但是我們到底向gl_Position發送了什么?
Position attributes
首先,我們使用下面代碼檢索頂點位置:
attribute vec3 position;請記住,相同的代碼適用于幾何體的每個頂點。屬性變量attribute是頂點之間唯一會發生變化的變量。相同的頂點著色器將應用于每個頂點,“position”屬性將包含該特定頂點的x、y和z坐標。
然后,如上面所示的代碼,我們將這個vec3轉換成一個vec4,因為矩陣和gl_Position都需要用到這個vec4:
Matrices uniforms
每個矩陣都將變換position,直到我們得到最終的剪裁空間坐標。
我們的代碼中有3個矩陣,因為它們的值對于幾何體的所有頂點都是相同的,所以我們使用uniform檢索它們。
每個矩陣都將做一部分變換:
- modelMatrix將應用與網格相關的所有變換。如果我們縮放、旋轉或移動網格,這些變換將包含在modelMatrix中并應用于position。
- viewMatrix將應用相對于相機的變換。如果我們向左旋轉相機,頂點應該在右邊。如果我們沿著網格的方向移動相機,頂點會變大,等等。
- projectionMatrix將我們的坐標轉換為最終的剪裁空間坐標。
如果想了解更多關于矩陣和坐標的信息,可以看這篇文章Coordinate-Systems
要應用矩陣,我們需要將其相乘。如果要將mat4應用于變量,該變量必須是vec4。
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);實際上有一個代碼更少的版本,便是將viewMatrix和modelMatrix組合成modelViewMatrix,雖然代碼更短但我們對每一步的控制權也就越弱:
uniform mat4 projectionMatrix; uniform mat4 modelViewMatrix;attribute vec3 position;void main() {gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }為了更好理解和控制position,我們將如下面這樣寫更長些:
uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix;attribute vec3 position;void main() {vec4 modelPosition = modelMatrix * vec4(position, 1.0);vec4 viewPosition = viewMatrix * modelPosition;vec4 projectedPosition = projectionMatrix * viewPosition;gl_Position = projectedPosition; }這些寫法都是等效的,但是現在能夠通過調整modelPosition的值來移動整個模型:
void main() {vec4 modelPosition = modelMatrix * vec4(position, 1.0);// 平面向上移動modelPosition.y += 1.0;// ... } void main() {vec4 modelPosition = modelMatrix * vec4(position, 1.0);// 平面變波浪形modelPosition.z += sin(modelPosition.x * 10.0) * 0.1;// ... }理解片元著色器
片元著色器的代碼將應用于幾何體的每個可見片元(像素)。這就是為什么片元著色器運行在頂點著色器之后。
Precision
在最頂部有這樣一條指令:
precision mediump float;這條指令可以讓我們決定浮點數的精度,有如下可能值
- highp:會造成性能影響,并可能在某些設備上無法工作。
- mediump:我們通常使用這個。
- lowp:會由于缺乏精確性而產生某些錯誤。
我們也可以設置頂點著色器的精度,但這不是必要的。
這一部分在使用著色器材質ShaderMaterial會被自動處理,但現在我們使用的是原始著色器材質。
gl_FragColor
gl_FragColor與gl_Position相似只不過是用于顏色。跟前者一樣是內置變量了,只需要在main函數中重新設置值。
這是一個vec4,前三個值是紅色、綠色和藍色通道(r、g、b),第四個值是alpha(a),下面這代碼應用后會看到紫色幾何體:
如果要降低alpha值使能夠看出差異,還需要回到RawShaderMaterial中將transparent屬性設置為true:
const material = new THREE.RawShaderMaterial({vertexShader: testVertexShader,fragmentShader: testFragmentShader,transparent: true })Attributes
我們在前面已經有一個名為position的attribute屬性變量,現在可以直接將這個屬性添加到BufferGeometry中。我們將為每個頂點添加一個隨機值,并根據這個值在z軸上移動該頂點。
回到js中,我們要在創建完幾何體后,再new一個32位的浮點數型數組Float32Array,然后為了知道幾何體中有多少個頂點,可以通過幾何體的attributes屬性獲取:
const count = geometry.attributes.position.count const randoms = new Float32Array(count)然后用隨機值填充該數組:
for(let i = 0; i < count; i++) {randoms[i] = Math.random() }最后,我們在BufferAttribute中使用該數組,并將其添加到幾何體的屬性中:
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1))setAttribute的第一個參數是我們設置的attribute屬性的名字,這也是下面在著色器中使用的名字。命名最好在前面加一個a前綴代表屬性。
BufferAttribute的第一個參數是數據數組,第二個參數是組成一個屬性的值的數量。如果我們要發送一個位置,我們會使用3,因為位置由3個值(x、y和z)組成。但在這里,每個頂點只有1個隨機值,所以我們使用1。
現在,我們可以在頂點著色器中檢索該屬性,并使用它移動頂點:
// ... attribute float aRandom;void main() {// ...modelPosition.z += aRandom * 0.1;// ... }最后得到一個尖銳峰巒狀平面:
Varyings
現在我們還想用aRandom屬性給片元著色。但可惜,我們沒法直接在片元著色器中使用attribute屬性變量。
但是,有一種方法可以將數據從頂點著色器發送到片元著色器,稱之為varying可變量。我們必須在倆種著色器上都這樣做。
在頂點著色器上,我們需要在main函數之前創建varying,這里將該變量命名為vRandom,用v作前綴好區分,然后在main函數中賦值:
// ...varying float vRandom;void main() {// ...vRandom = aRandom; }然后在片元著色器用一樣的方式:
precision mediump float; varying float vRandom; void main() {gl_FragColor = vec4(0.5, vRandom, 1.0, 1.0); }最后觀察到如下圖:
varying的一個有趣之處是,頂點之間的值是線性插值的,以此實現平滑漸變。如果GPU正在兩個頂點之間繪制一個片元,一個頂點的變量為1.0,另一個頂點的變量為0.0,那么片元值將為0.5。
最后重新回歸最初的普通平面。
Uniforms
統一變量uniform是將數據從JavaScript發送到著色器的一種方式。
如果我們想使用同一個著色器但是參數不一樣,以得到不同的結果,就可以使用uniform。
兩個著色器中都可以使用,其對每個頂點和片元的數據都是相同的。我們的代碼中已經有了projectionMatrix、viewMatrix和modelMatrix,但我們沒有創建它們,是Three.js創建的。
下面創建我們自己的統一變量uniform。
為將統一變量添加到我們的material中,要使用uniforms屬性,我們要做一個波浪平面,并且能夠控制波形頻率:
const material = new THREE.RawShaderMaterial({vertexShader: testVertexShader,fragmentShader: testFragmentShader,uniforms:{frequency: { value: 10 }} })在這里我們將統一變量命名為frequency,雖然不是強制性的,但最好在前面加個u前綴以將統一變量和其他數據區分開來:
const material = new THREE.RawShaderMaterial({vertexShader: testVertexShader,fragmentShader: testFragmentShader,uniforms:{uFrequency: { value: 10 }} })如果你也有在看其他的教程或示例,你可能會看到它是這樣聲明uniform的:
uFrequency: { value: 10, type: 'float' }在之前我們必須為其指定類型,但現在已經被棄用了。
現在,我們可以在著色器代碼中檢索該值,并在main函數中使用它:
uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix; uniform float uFrequency;attribute vec3 position;void main() {// ...modelPosition.z += sin(modelPosition.x * uFrequency) * 0.1;// ... }然后你會發現結果是一樣的,但現在我們可以在js中控制波形頻率了。
讓我們把頻率frequency改為vec2來控制水平和垂直方向的波,這里使用Three.js中一個簡單的vec2:
const material = new THREE.RawShaderMaterial({vertexShader: testVertexShader,fragmentShader: testFragmentShader,uniforms:{uFrequency: { value: new THREE.Vector2(10, 5) }} })然后回到著色器,將float改為vec2,在z軸上應用位移:
// ... uniform vec2 uFrequency;// ...void main() {// ...modelPosition.z += sin(modelPosition.x * uFrequency.x) * 0.1;modelPosition.z += sin(modelPosition.y * uFrequency.y) * 0.1;// ... }
這些值現在由JavaScript控制,所以我們可以將它們添加到我們的Dat.GUI中:
現在讓我們再新加一個uniform來讓平面像在風中飄動的旗幟。
我們將使用統一變量向著色器發送一個時間值,并在sin函數中使用:
記得在tick函數中更新uTime:
const tick = () => {const elapsedTime = clock.getElapsedTime()// Update materialmaterial.uniforms.uTime.value = elapsedTime// ... }更新頂點著色器代碼:
// ... uniform float uTime;// ...void main() {// ...modelPosition.z += sin(modelPosition.x * uFrequency.x + uTime) * 0.1;modelPosition.z += sin(modelPosition.y * uFrequency.y + uTime) * 0.1;// ... }
在使用uTime時要注意,如果我們使用原生JavaScript的Date.now(),你會發現不起作用,因為它返回的數值對于著色器而言太過龐大了。注意,我們不能發送太小或太大的統一變量值。
不要忘記這仍然是一個平面,我們可以像以前一樣變換網格。讓我們給飛機做個旗子形狀。
我們可以在著色器中通過乘以modelPosition.y來實現,但不要忘記,我們仍然可以直接在網格上更改位置position、縮放scale和旋轉rotation:
const mesh = new THREE.Mesh(geometry, material) mesh.scale.y = 2 / 3 scene.add(mesh)
在片元著色器中也可以使用統一變量,下面使用Three.js的Color作為新的統一變量:
之后你會看到旗幟變為橘色。
Textures
下面將我們的旗幟貼圖紋理應用于平面上(網上找一張圖片1024*1024即可):
const textureLoader = new THREE.TextureLoader() const flagTexture = textureLoader.load('/textures/flag-CCCP.jpg')然后將它傳給統一變量uTexture:
const material = new THREE.RawShaderMaterial({// ...uniforms:{// ...uTexture: { value: flagTexture }} })下面要將我們的旗幟紋理的顏色應用于所有可見片元上,為此我們必須使用texture2D()方法,該方法第一個參數就是應用的紋理,第二個參數是由在紋理上拾取的顏色的坐標組成,我們還沒有這些坐標,而這聽起來很熟悉,我們正在尋找可以幫助我們在幾何體上投射紋理的坐標,也就是UV坐標。
PlaneBufferGeometry會自動生成這些坐標,可以通過打印geometry.attributes.uv看到。
因為它是一個屬性,我們可以在頂點著色器中檢索它:
不過,我們需要在片元著色器中使用這些坐標。而要將數據從頂點著色器發送至片元著色器,我們要創建一個名為vUv的變量varying,并在main函數中對其賦值:
// ... attribute vec2 uv;varying vec2 vUv;void main() {// ...vUv = uv; }現在,我們可以在片元著色器中檢索變量vUv傳給texture2D()方法:
precision mediump float;uniform vec3 uColor; uniform sampler2D uTexture;varying vec2 vUv;void main() {vec4 textureColor = texture2D(uTexture, vUv);gl_FragColor = textureColor; }texture2D(...)的輸出值是一個vec4因為它包含rgb和a,即便這里沒用到a。
顏色變化
旗幟的顏色變化不是很明顯,如果有亮度的變化像陰影一般就好了,下面要用到的技術在物理上并不準確,但應該也能起到一點作用。
首先,在頂點著色器中,我們將把風的高度存儲在一個變量elevation中,然后通過varying將之發送至片元著色器:
用這個變量來改變textureColor的r,g,b屬性:
// ... varying float vElevation;void main() {vec4 textureColor = texture2D(uTexture, vUv);textureColor.rgb *= vElevation * 2.0 + 0.5;gl_FragColor = textureColor; }著色器材質
到目前為止,我們一直使用原始著色器材質RawShaderMaterial,而ShaderMaterial的工作原理其實是一樣的,只不過其有內置attributes和uniforms,精度也會自動設置。
你可以將材質直接替換:
然后在著色器中移除下面這些uniform,attribute和precision:
- uniform mat4 projectionMatrix;
- uniform mat4 viewMatrix;
- uniform mat4 modelMatrix;
- attribute vec3 position;
- attribute vec2 uv;
- precision mediump float;
之后著色器會跟之前一樣正常運行,因為它會自動添加上上面這些。
調試
調試著色器很困難,我們不能像JavaScript那樣記錄數據,因為是GPU對每個頂點和每個片元都執行著色器代碼。
然而,Three.js在編譯中傳遞錯誤這方面做得很好。
查錯
如果我們忘打一個分號,Three.js會打印整個著色器代碼并提示我們在第幾行出錯,像這樣:ERROR: 0:99: 'textureColor' : syntax error
閱讀著色器
當使用ShaderMaterial時,在報錯時查看控制臺的整個著色器代碼也是一種很好的方式,可以觀察到Three.js為我們自動添加了哪些變量。
測試數值
調試數值的另一個解決方案是在gl_FragColor中使用它們。雖然并不精確,但是能看到顏色變化,有時候這也足夠了。
如果這些值在頂點著色器中,我們可以使用一個變量將其傳遞給片元著色器。
假如我們想看uv長啥樣,我們可以將其發送至片元著色器,然后在gl_FragColor中使用:
最后
Glslify
GLSLify是一個node module模塊,它改進了我們對glsl文件的處理。通過glslify,我們可以像模塊一樣導入和導出glsl代碼。你可以使用glslify-loader并將之加到webpack配置中去。
網站
下面是學習著色器的一些網站和油管頻道:
- The Book of Shaders: https://thebookofshaders.com/
- ShaderToy: https://www.shadertoy.com/
- The Art of Code Youtube Channel: https://www.youtube.com/channel/UCcAlTqd9zID6aNX3TzwxJXg
- Lewis Lepton Youtube Channel: https://www.youtube.com/channel/UC8Wzk_R1GoPkPqLo-obU_kQ
筆記都是邊看視頻邊跟著敲代碼邊做筆記的,如果有哪里出錯,還望指出來,好做改進,謝謝。
總結
以上是生活随笔為你收集整理的three.js学习笔记(十四)——Shaders着色器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [转载]通过插件支持,Geronimo
- 下一篇: 03.获取网页源代码