在WebGL着色器中使用mix()在两个以上的目标之间进行变形

时间:2017-06-26 21:18:08

标签: three.js glsl webgl opengl-es-2.0

我正在尝试使用three.js构建一个图像滑块,并且在绕着glsl着色器传递适当的状态时遇到困难,因此我可以在幻灯片之间进行切换。我可以很容易地在两个目标之间做它(无论是纹理还是models),只需简单地在0和1之间缓和并将其作为attrib float传递,如下所示:

string

但我无法理解如何用超过2个目标来接近它。我应该传递一个数字并做一堆if语句吗?

我设置了缓冲平面几何体和我的着色器材质,其中包含我的3个纹理,如下所示:

attribute float mix;
vec4 color = mix(tex1, tex2, mix);

这并没有给我带来理想的效果(纹理突然在彼此之间切换,不会过渡到位,也有点难看)。我是三个人的新手,我甚至无能为力地解决这个问题。如何做到这一点?

3 个答案:

答案 0 :(得分:1)

我会在GLSL中进行混合,并在着色器之外休息,管理所绘制的内容。你可以有一个带有2个或更多纹理的着色器,在它们之间进行转换,但是一旦它们达到0或1,就用另一个纹理切换出纹理。如果你只需要三个......这太过分了。

有些事情如下:

const myTransitionMaterial = new THREE.ShaderMaterial({
    uniforms:{
        uLerp: {value: 0},
        uTexA: {value: null},
        uTexB: {value: null},
    },
    vertexShader: vs,
    fragmentShader: fs,

})

//lets say you have a list of a bunch of textures, and you add them
myTransitionMaterial.textures = [tex1,tex2,tex3]

//and you want to lerp through them linearly using 0-1 regardless of how many there are
myTransitionMaterial.lerp = (normalizedFactor)=>{

    const length = myTransitionMaterial.textures.length

    const index = normalizedFactor * length // 0-3

    //at 0.00 we want 0-1 indecis and 0.00 f
    //at 0.99 we want 0-1 indecis and 0.99 f 
    //at 1.00 we want 1-2 indecis and 0.00 f
    //at 1.99 we want 1-2 indecis and 0.99 f
    //at 2.00 we want 2-3 indecis and 0.00 f
    //at 2.99 we want 2-3 indecis and 0.99 f
    //at 3.00 we want 3-4 indecis and 0.00 f

    const f = index - Math.floor(index)

    const i0 = Math.floor(index)
    const i1 = i0 <= length ? i0 + 1 : null //catch edge 

    this.uniforms.uLerp.value = f
    this.uniforms.uTexA.value = this.textures[i0]
    this.uniforms.uTexB.value = this.textures[i1]

}.bind(myTransitionMaterial)

VS

varying vec2 vUv;

void main(){
    vUv = uv;
    gl_Position = vec4(position.xy,0.,1.);
}

FS

uniform float uLerp;
uniform sampler2D uTexA;
uniform sampler2D uTexB;

varying vec2 vUv;

void main(){

    gl_FragColor = vec4( mix( texture2D(uTexA, vUv).xyz, texture2D(uTexB, vUv).xyz, uLerp ), 1. );

}

这里要指出的一个重要概念是,如果你做这样的事情,并且第一次尝试lerp,你的帧速率会随着第一次显示纹理而变得不稳定。发生这种情况是因为渲染器会在第一次遇到它们时自动将它们上传到gpu。例如,如果你使用每个纹理渲染一次帧,在进行这种转换之前,它就会像黄油一样平滑。

答案 1 :(得分:1)

我带了我的5个kopeikas:)

例如,我们想要转换几个图片。所以我们可以在制服中使用数组。

我们走了

var uniforms = {
  textures: {
    value: []
  },
  transition: {
    value: 0
  }
};
var textureLoader = new THREE.TextureLoader();
textureLoader.setCrossOrigin("");
var pics = [
  "https://threejs.org/examples/textures/UV_Grid_Sm.jpg",
  "https://threejs.org/examples/textures/colors.png",
  "https://threejs.org/examples/textures/planets/moon_1024.jpg",
  "https://threejs.org/examples/textures/decal/decal-normal.jpg"
];
pics.forEach((p, idx)=>{
    textureLoader.load(p, function(tex){
    uniforms.textures.value[idx] = tex;
    tex.needsUpdate = true;
  })
});

我们的几何和顶点着色器通常是:

var planeGeom = new THREE.PlaneBufferGeometry(10, 10);
var vertShader = `
    varying vec2 vUv;
    void main()
  {
    vUv = uv;
    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
  }
`;

Magic来自我们的片段着色器,它基于我们的数组长度动态构建,并带有图片链接:

var fragShader = `
  uniform sampler2D textures[` + pics.length + `];
  uniform float transition;
  varying vec2 vUv;

  vec4 getTexture(int index){
    for(int i = 0; i < ` + pics.length + `; i++){
         if (i == index){   return texture2D(textures[i],vUv); }
    }
  }
  void main()
  {
    if (transition == 1.){
        gl_FragColor = texture2D(textures[` + (pics.length - 1) + `], vUv); // show last
    }
    else {
      float chunk = 1. / ` + (pics.length - 1) + `.; // amount of transitions = amount of pics - 1
        float t = floor(transition / chunk);
      int idx0 = int(t);
      int idx1 = int(t) + 1; 
      gl_FragColor = mix(
        getTexture(idx0),
        getTexture(idx1),
        (transition - (float(t) * chunk)) * ` + (pics.length - 1) + `.
      );
    }
  }
`;

解决方案非常灵活,因此您可以根据需要进行多次转换。

jsfiddle示例r86

答案 2 :(得分:0)

如果已经设置了纹理的数量(并且它应该像制服一样)我会做的有点不同:

我会定义一个浮动制服,它是你的混音器,然后使用0-1值在两者之间转换。通过这种方式,您可以根据需要设置混合器变量的动画,并且GLSL保持非常简单:

uniform sampler2d t1;
uniform sampler2d t2;
uniform sampler2d t3;
uniform float mixer;
void main(){
    vec4 c1 = texture2D(t1,vUv);
    vec4 c4 = c1; //create a duplicate so you can loop back
    vec4 c2 = texture2D(t2,vUv);
    vec4 c3 = texture2D(t3,vUv);
    float mp1 = .33333; //define the locations of t2 
    float mp2 = .66666; //and t3
    float w= .33333; //define the width
    c1 *= 1-mix(0.0,w,abs(mixer)); //this is at 1 when mixer is 0 &  0 when .333
    c2 *= 1-mix(0.0,w, abs(mixer-mp1)); //this is 1 when .333 & 0 when 0<mixer>.666
    c3 *= 1-mix(0.0,w,abs(mixer-mp2)); //this is 1 when .666 & 0 when .333<mixer>1.0
    c4 *= 1-mix(0.0,w,abs(mixer-1.0)); //this is 1 when 1 & 0 when .666<mixer
    gl_FragColor=c1+c2+c3+c4; //now it will only ever be a mixture of 2 textures
}

然后你在混音器上做一些边界功能,以便

if(mixer > 1)mixer --;
if(mixer < 0)mixer ++;

然后你可以通过从0-0.3333补间从T1到T2。您可以通过从.333到.666补间从T2到T3,并从.6666到1.0补间从T3到T1。依次类推。

然后你只需要进行一些管理,以便你的补间循环 - 即,如果从当前位置到目标位置的距离大于1/3,你可以从0跳到1或从1跳到0