將ShaderToy中的着色器移至Three.js(1)

更多示例代碼:github地址git

更多示例效果:demo地址github

基本的轉換規則:

  1. 把ShaderToy特定變量做爲 uniform 變量添加到本身的着色器中,如iGlobalTime、iResolution等
  2. 把mainImage(out vec4 z, in vec2 w)重命名爲main()
  3. 把輸出變量重命名爲gl_FragColor,而輸入變量即爲頂點着色器裏的輸出
  4. 利用three.js內置的uv變量,重寫shadertoy裏對於座標的計算

遵守這種基本模式,能夠在three中轉換shadertoy中的着色器代碼。bash

我首先在shadertoy上選了一個看起來很簡單的效果,上升氣泡測試

效果圖

大概是這個樣子的。動畫

下面是它的shader代碼:ui

短小精悍,很是適合作最初的測試。spa

而後就是去嘗試對它進行改造了。3d

首先,咱們知道shadertoy中提供的只是片元着色器,因此我須要本身首先補充一個頂點着色器,代碼以下:code

precision mediump float;
varying vec2 vUv;
void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
複製代碼

很簡單,只須要作一件事,轉換目標的座標系。orm

第二步是抄代碼,把shadertoy裏的代碼直接複製下來,粘貼到咱們的片元着色器中。

第三步,改造。咱們不須要知道它是如何完成動畫和幾何體構建的,只須要按照模式,改掉不兼容的地方便可。

3.1 咱們看到代碼裏有兩個以 "i" 開頭的變量,iGlobalTime 和 iResolution ,這兩個在shader中對應的是uniform變量,須要咱們在js代碼裏傳進來。我暫時沒必要知道這兩個變量將被賦予什麼樣的值,先在最頂部手動去定義這兩個變量便可。

3.2 mainImage() => main();fragColor => gl_FragColor。

3.3 修改uv座標:shadertoy中沒有內置uv變量,因此它須要進行一次計算,而咱們在three.js裏能夠跳過這一步,直接把第6行改成,' -1.0 + 2.0 * vUv '。

到這一步,fragment shader改造結束:

precision mediump float;
uniform float iGlobalTime;
uniform vec2 iResolution;
varying vec2 vUv;

void main() {
    vec2 uv = -1.0 + 2.0 * vUv;
    uv.x *=  iResolution.x / iResolution.y;
    vec3 color = vec3(0.8 + 0.2 * uv.y);
    for( int i = 0; i < 40; i++ ){

        // bubble seeds
        float pha =      sin(float(i) * 546.13 + 1.0) * 0.5 + 0.5;
        float siz = pow( sin(float(i) * 651.74 + 5.0) * 0.5 + 0.5, 4.0 );
        float pox =      sin(float(i) * 321.55 + 4.1) * iResolution.x / iResolution.y;

        // buble size, position and color
        float rad = 0.1 + 0.5 * siz;
        vec2  pos = vec2( pox, -1.0 - rad + (2.0 + 2.0 * rad) * mod(pha + 0.1 * iGlobalTime * (0.2 + 0.8 * siz), 1.0));
        float dis = length( uv - pos );
        vec3  col = mix(vec3(0.94, 0.3, 0.0), vec3(0.1, 0.4, 0.8), 0.5 + 0.5 * sin(float(i) * 1.2 + 1.9));
        //    col += 8.0 * smoothstep( rad * 0.95, rad, dis );

        // render
        float f = length(uv-pos)/rad;
        f = sqrt(clamp(1.0 - f * f, 0.0, 1.0));
        color -= col.zyx * (1.0 - smoothstep( rad * 0.95, rad, dis )) * f;
    }
    color *= sqrt(1.5 - 0.5 * length(uv));
    gl_FragColor = vec4(color, 1.0);
}
複製代碼

這時還沒結束,咱們如今依然不知道iGlobalTime和iResolution是什麼。因此咱們仍是須要簡單的看一下它的代碼,不難發現,iGlobalTime是一個時間流,表明的是幀率,而iResolution表明的則是座標,咱們能夠理解爲是這個shader容器的長和寬。

瞭解了它們是什麼,咱們天然就可以知道如何給它們賦值:

// ...
// 初始值
var uniforms = {
    iGlobalTime: {
        type: 'f',
        value: 1.0
    },
    iResolution: {
        type: 'v2',
        value: new THREE.Vector2()
    }
}
uniforms.iResolution.value.x = 1 // window.innerWidth;
uniforms.iResolution.value.y = 1 // window.innerHeight;
 
// 加到shader材質中
var material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: document.getElementById('general').textContent,
    fragmentShader: document.getElementById('frag1').textContent
})
// ...
mesh.startTime = Date.now() // 在網格中保存一個初始時間
mesh.uniforms = uniforms    // 在網格中保存uniform變量
// ...
 
// 逐幀更新時間
function render() {
    var time = (Date.now() - mesh.startTime) / 1000
    mesh.uniforms.iGlobalTime.value = time
    // ...
}

複製代碼

效果:

完美改造。

更復雜的shader

固然,這只是一個簡單例子,實際在three.js中,不少時候咱們是須要把着色器的效果做用於場景,而不是單個的網格內部,這時上述套路很明顯就再也不適合了。

如何在場景中隨意發揮着色器的威力,把shadertoy或者本身寫的着色器融合進後處理通道中,這也是我目前正在研究和解決的問題,在解決以後會繼續更新這篇文章。

相關文章
相關標籤/搜索