THREE.JS入門教程(3)著色器-下
更新時(shí)間:2013年01月24日 10:54:11 作者:
Three.js是一個(gè)偉大的開(kāi)源WebGL庫(kù),WebGL允許JavaScript操作GPU,在瀏覽器端實(shí)現(xiàn)真正意義的3D本文將介紹模擬光照/Attribut變量/更新著色器材質(zhì)/更新著色器材質(zhì)等等感興趣的朋友可以了解下啊
譯序
Three.js是一個(gè)偉大的開(kāi)源WebGL庫(kù),WebGL允許JavaScript操作GPU,在瀏覽器端實(shí)現(xiàn)真正意義的3D。但是目前這項(xiàng)技術(shù)還處在發(fā)展階段,資料極為匱乏,愛(ài)好者學(xué)習(xí)基本要通過(guò)Demo源碼和Three.js本身的源碼來(lái)學(xué)習(xí)。
.簡(jiǎn)介
這是WebGL著色器教程的后半部分,如果你沒(méi)看過(guò)前一篇,閱讀這一篇教程可能會(huì)使你感到困惑,建議你翻閱前面的教程。
上一篇結(jié)束的時(shí)候,我們?cè)谄聊恢醒氘?huà)了一個(gè)好看的粉紅色的球體?,F(xiàn)在我要開(kāi)始創(chuàng)建一些更加有意思的東西了。
在這一篇教程中,我們會(huì)先花點(diǎn)時(shí)間來(lái)加入一個(gè)動(dòng)畫(huà)循環(huán),然后是頂點(diǎn)attributes變量和一個(gè)uniform變量。我們還要加一些varying變量,這樣頂點(diǎn)著色器就可以向片元著色器傳遞信息了。最終的結(jié)果是哪個(gè)粉紅色的球體會(huì)從頂部開(kāi)始向兩側(cè)“點(diǎn)燃”,然后作有規(guī)律的運(yùn)動(dòng)。這有一點(diǎn)迷幻,但是會(huì)幫助你對(duì)著色器中的三種變量有更好的了解:他們互相聯(lián)系,實(shí)現(xiàn)了整個(gè)集合體。當(dāng)然我們會(huì)在Three.js的框架中做這些。
1.模擬光照
讓我們更新顏色吧,這樣球體看起來(lái)就不會(huì)是個(gè)扁平晦暗的圓了。如果我們想看看Three.js是怎樣處理光照的,我敢肯定你會(huì)發(fā)現(xiàn)這比我們需要的要復(fù)雜得多,所以我們先模擬光照吧。你應(yīng)該瀏覽一下Three.js中那些奇妙的著色器,還有一些來(lái)自最近的一個(gè) Chris Milk 和 Google, Rome 的WebGL項(xiàng)目。
回到著色器,我們要更新頂點(diǎn)著色器來(lái)向片元著色器傳遞頂點(diǎn)的法向量。利用一個(gè)varying變量:
// 創(chuàng)建一個(gè)varying變量vNormal,頂點(diǎn)著色器和片元著色器都包含了該變量
varying vec3 vNormal;
void main() {
// 將vNormal設(shè)置為normal,后者是Three.js創(chuàng)建并傳遞給著色器的attribute變量
vNormal = normal;
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position, 1.0);
}
在片元著色器中,我們將會(huì)創(chuàng)建一個(gè)相同變量名的變量,然后將法線向量和另一個(gè)表示來(lái)自右上方光線的向量點(diǎn)乘,并將結(jié)果作用于顏色。最后結(jié)果的效果有點(diǎn)像平行光。
// 和頂點(diǎn)著色器中一樣的變量vNormal
varying vec3 vNormal;
void main() {
// 定義光線向量
vec3 light = vec3(0.5,0.2,1.0);
// 確保其歸一化
light = normalize(light);
// 計(jì)算光線向量和法線向量的點(diǎn)積,如果點(diǎn)積小于0(即光線無(wú)法照到),就設(shè)為0
float dProd = max(0.0, dot(vNormal, light));
// 填充片元顏色
gl_FragColor = vec4(dProd, // R
dProd, // G
dProd, // B
1.0); // A
}
使用點(diǎn)積的原因是:兩個(gè)向量的點(diǎn)積表明他們有多么“相似”。如果兩個(gè)向量都是歸一化的,而且他們的方向一模一樣,點(diǎn)積的值就是1;如果兩個(gè)向量的方向恰巧完全相反,點(diǎn)積的值就是-1。我們所做的就是把點(diǎn)積的值拿來(lái)作用到光纖上,所以如果這個(gè)點(diǎn)在球體的右上方,點(diǎn)積的值就是1,也就是完全照亮了;而在另一邊的點(diǎn),獲得的點(diǎn)積值接近0,甚至到了-1。我們將獲得的任何負(fù)值都設(shè)置為0。當(dāng)你將數(shù)據(jù)傳入之后,你就會(huì)看到最基本的光照效果了。
下面是什么?我們會(huì)將頂點(diǎn)的坐標(biāo)摻和進(jìn)來(lái)。
2.Attribut變量
接下來(lái)我要通過(guò)Attribute變量為每一個(gè)頂點(diǎn)傳遞一個(gè)隨機(jī)數(shù),這個(gè)隨機(jī)數(shù)被用來(lái)將頂點(diǎn)沿著法線向量推出去一段距離。新的結(jié)果有點(diǎn)像一個(gè)怪異的不規(guī)則物體,每次刷新頁(yè)面物體都會(huì)隨機(jī)變化。現(xiàn)在,他還不會(huì)動(dòng)(后面我會(huì)讓他動(dòng)起來(lái)),但是幾次刷新就可以很好地觀察到,他的形狀是隨機(jī)的。
讓我們開(kāi)始為頂點(diǎn)著色器加入attribute變量吧:
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// 將隨機(jī)數(shù)displacement轉(zhuǎn)化為三維向量,這樣就可以和法線相乘了
vec3 newPosition = position +
normal * vec3(displacement);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition, 1.0);
}
你看到什么都沒(méi)變,因?yàn)閍ttribute變量displacement還沒(méi)有被設(shè)定你,所以著色器就使用了0作為默認(rèn)值。這時(shí)displacement還沒(méi)起作用,但我們馬上就要在著色器材質(zhì)中加上attribute變量了,然后Three.js就會(huì)自動(dòng)地把它們綁在一起運(yùn)行了。
同時(shí)也要注意這樣一個(gè)事實(shí),我將更新后的位置指定給了一個(gè)新的三維向量變量,因?yàn)樵瓉?lái)的位置變量position,就像所有的attribute變量一樣,都是只讀的。
3.更新著色器材質(zhì)
現(xiàn)在我們來(lái)更新著色器材質(zhì),傳入一些東西給attribute對(duì)象displacement。記住,attribute對(duì)象是和頂點(diǎn)一一對(duì)應(yīng)的,所以我們對(duì)球體的每一個(gè)頂點(diǎn)都有一個(gè)值,就像這樣:
var attributes = {
displacement: {
type: 'f', // 浮點(diǎn)數(shù)
value: [] // 空數(shù)組
}
};
var vShader = $('#vertexshader');
var fShader = $('#fragmentshader');
// 創(chuàng)建一個(gè)包含attribute屬性的著色器材質(zhì)
var shaderMaterial =
new THREE.MeshShaderMaterial({
attributes: attributes,
vertexShader: vShader.text(),
fragmentShader: fShader.text()
});
// 向displacement中填充隨機(jī)數(shù)
var verts = sphere.geometry.vertices;
var values = attributes.displacement.value;
for(var v = 0; v < verts.length; v++) {
values.push(Math.random() * 30);
}
這樣,就可以看到一個(gè)變形的球體了。最Cool的是:所有這些變形都是在GPU中完成的。
4.動(dòng)起來(lái)
要使這東西動(dòng)起來(lái),應(yīng)該怎么做?好吧,應(yīng)該做這兩件事情。
一個(gè)uniform變量amplitude,在每一幀控制displacement實(shí)際造成了多少位移。我們可以使用正弦或余弦函數(shù)來(lái)在每一幀中生成它,因?yàn)檫@兩個(gè)函數(shù)的取值范圍從-1到1。
一個(gè)幀循環(huán)。
我們需要將這個(gè)uniform變量加入到著色器材質(zhì)中,同時(shí)也需要加入到頂點(diǎn)著色器中。先來(lái)看頂點(diǎn)著色器:
uniform float amplitude;
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// 將displacement乘以amplitude,當(dāng)我們?cè)诿恳粠衅交淖僡mplitude時(shí),畫(huà)面就動(dòng)起來(lái)了
vec3 newPosition =
position +
normal *
vec3(displacement *
amplitude);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition, 1.0);
}
然后更新著色器材質(zhì):
var uniforms = {
amplitude: {
type: 'f', // a float
value: 0
}
};
var vShader = $('#vertexshader');
var fShader = $('#fragmentshader');
// 創(chuàng)建最終的著色器材質(zhì)
var shaderMaterial =
new THREE.MeshShaderMaterial({
uniforms: uniforms,
attributes: attributes,
vertexShader: vShader.text(),
fragmentShader: fShader.text()
});
我們的著色器也已經(jīng)就緒了。但我們好像又倒退了一步,屏幕中又只剩下光滑的球了。別擔(dān)心,這是因?yàn)閍mplitude值設(shè)置為0,因?yàn)槲覀儗mplitude乘上了displacement,所以現(xiàn)在看不到任何變化。我們還沒(méi)設(shè)置循環(huán)呢,所以amplitude只可能是0.
在我們的JavaScript中,需要將渲染過(guò)程打包成一個(gè)函數(shù),然后用requestAnimationFrame去調(diào)用該函數(shù)。在這個(gè)函數(shù)里,我們更新uniform(譯者注:即amplitude)的值。
var frame = 0;
function update() {
// amplitude來(lái)自于frame的正弦值
uniforms.amplitude.value =
Math.sin(frame);
// 更新全局變量frame
frame += 0.1;
renderer.render(scene, camera);
// 指定下一次屏幕刷新時(shí),調(diào)用update
requestAnimFrame(update);
}
requestAnimFrame(update);
5.小結(jié)
就是它了!你看到球體正在奇怪地脈動(dòng)著。關(guān)于著色器,還有太多的內(nèi)容沒(méi)有講到呢,但是我希望這篇教程能夠?qū)δ阌幸恍椭,F(xiàn)在,當(dāng)你看到一些其他的著色器時(shí),我希望你能夠理解它們,而且你應(yīng)該有信心去創(chuàng)建自己的著色器了!
和往常一樣,我將這一課的源碼打包了
Three.js是一個(gè)偉大的開(kāi)源WebGL庫(kù),WebGL允許JavaScript操作GPU,在瀏覽器端實(shí)現(xiàn)真正意義的3D。但是目前這項(xiàng)技術(shù)還處在發(fā)展階段,資料極為匱乏,愛(ài)好者學(xué)習(xí)基本要通過(guò)Demo源碼和Three.js本身的源碼來(lái)學(xué)習(xí)。
.簡(jiǎn)介
這是WebGL著色器教程的后半部分,如果你沒(méi)看過(guò)前一篇,閱讀這一篇教程可能會(huì)使你感到困惑,建議你翻閱前面的教程。
上一篇結(jié)束的時(shí)候,我們?cè)谄聊恢醒氘?huà)了一個(gè)好看的粉紅色的球體?,F(xiàn)在我要開(kāi)始創(chuàng)建一些更加有意思的東西了。
在這一篇教程中,我們會(huì)先花點(diǎn)時(shí)間來(lái)加入一個(gè)動(dòng)畫(huà)循環(huán),然后是頂點(diǎn)attributes變量和一個(gè)uniform變量。我們還要加一些varying變量,這樣頂點(diǎn)著色器就可以向片元著色器傳遞信息了。最終的結(jié)果是哪個(gè)粉紅色的球體會(huì)從頂部開(kāi)始向兩側(cè)“點(diǎn)燃”,然后作有規(guī)律的運(yùn)動(dòng)。這有一點(diǎn)迷幻,但是會(huì)幫助你對(duì)著色器中的三種變量有更好的了解:他們互相聯(lián)系,實(shí)現(xiàn)了整個(gè)集合體。當(dāng)然我們會(huì)在Three.js的框架中做這些。
1.模擬光照
讓我們更新顏色吧,這樣球體看起來(lái)就不會(huì)是個(gè)扁平晦暗的圓了。如果我們想看看Three.js是怎樣處理光照的,我敢肯定你會(huì)發(fā)現(xiàn)這比我們需要的要復(fù)雜得多,所以我們先模擬光照吧。你應(yīng)該瀏覽一下Three.js中那些奇妙的著色器,還有一些來(lái)自最近的一個(gè) Chris Milk 和 Google, Rome 的WebGL項(xiàng)目。
回到著色器,我們要更新頂點(diǎn)著色器來(lái)向片元著色器傳遞頂點(diǎn)的法向量。利用一個(gè)varying變量:
復(fù)制代碼 代碼如下:
// 創(chuàng)建一個(gè)varying變量vNormal,頂點(diǎn)著色器和片元著色器都包含了該變量
varying vec3 vNormal;
void main() {
// 將vNormal設(shè)置為normal,后者是Three.js創(chuàng)建并傳遞給著色器的attribute變量
vNormal = normal;
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position, 1.0);
}
在片元著色器中,我們將會(huì)創(chuàng)建一個(gè)相同變量名的變量,然后將法線向量和另一個(gè)表示來(lái)自右上方光線的向量點(diǎn)乘,并將結(jié)果作用于顏色。最后結(jié)果的效果有點(diǎn)像平行光。
復(fù)制代碼 代碼如下:
// 和頂點(diǎn)著色器中一樣的變量vNormal
varying vec3 vNormal;
void main() {
// 定義光線向量
vec3 light = vec3(0.5,0.2,1.0);
// 確保其歸一化
light = normalize(light);
// 計(jì)算光線向量和法線向量的點(diǎn)積,如果點(diǎn)積小于0(即光線無(wú)法照到),就設(shè)為0
float dProd = max(0.0, dot(vNormal, light));
// 填充片元顏色
gl_FragColor = vec4(dProd, // R
dProd, // G
dProd, // B
1.0); // A
}
使用點(diǎn)積的原因是:兩個(gè)向量的點(diǎn)積表明他們有多么“相似”。如果兩個(gè)向量都是歸一化的,而且他們的方向一模一樣,點(diǎn)積的值就是1;如果兩個(gè)向量的方向恰巧完全相反,點(diǎn)積的值就是-1。我們所做的就是把點(diǎn)積的值拿來(lái)作用到光纖上,所以如果這個(gè)點(diǎn)在球體的右上方,點(diǎn)積的值就是1,也就是完全照亮了;而在另一邊的點(diǎn),獲得的點(diǎn)積值接近0,甚至到了-1。我們將獲得的任何負(fù)值都設(shè)置為0。當(dāng)你將數(shù)據(jù)傳入之后,你就會(huì)看到最基本的光照效果了。
下面是什么?我們會(huì)將頂點(diǎn)的坐標(biāo)摻和進(jìn)來(lái)。
2.Attribut變量
接下來(lái)我要通過(guò)Attribute變量為每一個(gè)頂點(diǎn)傳遞一個(gè)隨機(jī)數(shù),這個(gè)隨機(jī)數(shù)被用來(lái)將頂點(diǎn)沿著法線向量推出去一段距離。新的結(jié)果有點(diǎn)像一個(gè)怪異的不規(guī)則物體,每次刷新頁(yè)面物體都會(huì)隨機(jī)變化。現(xiàn)在,他還不會(huì)動(dòng)(后面我會(huì)讓他動(dòng)起來(lái)),但是幾次刷新就可以很好地觀察到,他的形狀是隨機(jī)的。
讓我們開(kāi)始為頂點(diǎn)著色器加入attribute變量吧:
復(fù)制代碼 代碼如下:
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// 將隨機(jī)數(shù)displacement轉(zhuǎn)化為三維向量,這樣就可以和法線相乘了
vec3 newPosition = position +
normal * vec3(displacement);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition, 1.0);
}
你看到什么都沒(méi)變,因?yàn)閍ttribute變量displacement還沒(méi)有被設(shè)定你,所以著色器就使用了0作為默認(rèn)值。這時(shí)displacement還沒(méi)起作用,但我們馬上就要在著色器材質(zhì)中加上attribute變量了,然后Three.js就會(huì)自動(dòng)地把它們綁在一起運(yùn)行了。
同時(shí)也要注意這樣一個(gè)事實(shí),我將更新后的位置指定給了一個(gè)新的三維向量變量,因?yàn)樵瓉?lái)的位置變量position,就像所有的attribute變量一樣,都是只讀的。
3.更新著色器材質(zhì)
現(xiàn)在我們來(lái)更新著色器材質(zhì),傳入一些東西給attribute對(duì)象displacement。記住,attribute對(duì)象是和頂點(diǎn)一一對(duì)應(yīng)的,所以我們對(duì)球體的每一個(gè)頂點(diǎn)都有一個(gè)值,就像這樣:
復(fù)制代碼 代碼如下:
var attributes = {
displacement: {
type: 'f', // 浮點(diǎn)數(shù)
value: [] // 空數(shù)組
}
};
var vShader = $('#vertexshader');
var fShader = $('#fragmentshader');
// 創(chuàng)建一個(gè)包含attribute屬性的著色器材質(zhì)
var shaderMaterial =
new THREE.MeshShaderMaterial({
attributes: attributes,
vertexShader: vShader.text(),
fragmentShader: fShader.text()
});
// 向displacement中填充隨機(jī)數(shù)
var verts = sphere.geometry.vertices;
var values = attributes.displacement.value;
for(var v = 0; v < verts.length; v++) {
values.push(Math.random() * 30);
}
這樣,就可以看到一個(gè)變形的球體了。最Cool的是:所有這些變形都是在GPU中完成的。
4.動(dòng)起來(lái)
要使這東西動(dòng)起來(lái),應(yīng)該怎么做?好吧,應(yīng)該做這兩件事情。
一個(gè)uniform變量amplitude,在每一幀控制displacement實(shí)際造成了多少位移。我們可以使用正弦或余弦函數(shù)來(lái)在每一幀中生成它,因?yàn)檫@兩個(gè)函數(shù)的取值范圍從-1到1。
一個(gè)幀循環(huán)。
我們需要將這個(gè)uniform變量加入到著色器材質(zhì)中,同時(shí)也需要加入到頂點(diǎn)著色器中。先來(lái)看頂點(diǎn)著色器:
復(fù)制代碼 代碼如下:
uniform float amplitude;
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// 將displacement乘以amplitude,當(dāng)我們?cè)诿恳粠衅交淖僡mplitude時(shí),畫(huà)面就動(dòng)起來(lái)了
vec3 newPosition =
position +
normal *
vec3(displacement *
amplitude);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition, 1.0);
}
然后更新著色器材質(zhì):
復(fù)制代碼 代碼如下:
var uniforms = {
amplitude: {
type: 'f', // a float
value: 0
}
};
var vShader = $('#vertexshader');
var fShader = $('#fragmentshader');
// 創(chuàng)建最終的著色器材質(zhì)
var shaderMaterial =
new THREE.MeshShaderMaterial({
uniforms: uniforms,
attributes: attributes,
vertexShader: vShader.text(),
fragmentShader: fShader.text()
});
我們的著色器也已經(jīng)就緒了。但我們好像又倒退了一步,屏幕中又只剩下光滑的球了。別擔(dān)心,這是因?yàn)閍mplitude值設(shè)置為0,因?yàn)槲覀儗mplitude乘上了displacement,所以現(xiàn)在看不到任何變化。我們還沒(méi)設(shè)置循環(huán)呢,所以amplitude只可能是0.
在我們的JavaScript中,需要將渲染過(guò)程打包成一個(gè)函數(shù),然后用requestAnimationFrame去調(diào)用該函數(shù)。在這個(gè)函數(shù)里,我們更新uniform(譯者注:即amplitude)的值。
復(fù)制代碼 代碼如下:
var frame = 0;
function update() {
// amplitude來(lái)自于frame的正弦值
uniforms.amplitude.value =
Math.sin(frame);
// 更新全局變量frame
frame += 0.1;
renderer.render(scene, camera);
// 指定下一次屏幕刷新時(shí),調(diào)用update
requestAnimFrame(update);
}
requestAnimFrame(update);
5.小結(jié)
就是它了!你看到球體正在奇怪地脈動(dòng)著。關(guān)于著色器,還有太多的內(nèi)容沒(méi)有講到呢,但是我希望這篇教程能夠?qū)δ阌幸恍椭,F(xiàn)在,當(dāng)你看到一些其他的著色器時(shí),我希望你能夠理解它們,而且你應(yīng)該有信心去創(chuàng)建自己的著色器了!
和往常一樣,我將這一課的源碼打包了
相關(guān)文章
js中對(duì)象的聲明方式以及數(shù)組的一些用法示例
本文為大家介紹下js中的對(duì)象聲明方式以及數(shù)組的一些用法,下面有個(gè)不錯(cuò)的示例,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-12-12
JavaScript開(kāi)發(fā)過(guò)程中規(guī)范commit?msg意義詳解
這篇文章主要為大家介紹了JavaScript開(kāi)發(fā)過(guò)程中規(guī)范commit?msg意義的內(nèi)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11
Javascript入門學(xué)習(xí)第九篇 Javascript DOM 總結(jié)
作為一個(gè)js-DOM開(kāi)發(fā)者,你必須知道的一些DOM方法:2008-07-07
簡(jiǎn)介JavaScript中strike()方法的使用
這篇文章主要介紹了簡(jiǎn)介JavaScript中strike()方法的使用,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06
Ajax執(zhí)行順序流程及回調(diào)問(wèn)題分析
有些朋友在實(shí)現(xiàn)異步局部更新數(shù)據(jù),會(huì)遇到ajax的執(zhí)行問(wèn)題,本文將對(duì)此進(jìn)行詳細(xì)介紹,需要了解的朋友可以參考下2012-12-12

