首页 > 解决方案 > 在 Threejs 中使用 ShaderMaterial 的平面闪烁

问题描述

我正在尝试通过使用 RepeatWrapping 在一个非常大的平面(1000x1000,也缩放 10 倍)上应用纹理。当我使用 MeshBasicMaterial 时它看起来不错,但当我使用 ShaderMaterial 时它会闪烁。以下是我的代码。

    <!DOCTYPE html>
<html>
<head>
  <title>MeshShaderMaterialExample</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">

  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
  <script src="https://unpkg.com/three@0.85.0/examples/js/controls/TrackballControls.js"></script>
<script src="js/TWEEN.js"></script>
  <style type="text/css">
    body {
      width: 100%;
      height: 100%;
      background-color: #000;
      color: #fff;
      margin: 0px;
      padding: 0;
      overflow: hidden;
    }
  </style>
</head>
<body>
<script>
  var camera, scene, renderer;
var container, mesh, geometry;
var controls, effect;
  var tweenUpdate="false";
  var tweenOver="true";
  var textureData=
{
  "texture_0":
  {
    "img":"gman.png"
  },
  "texture_1":
  {
    "img":"gman.png"
  }}
var magicPosition = { magicTrans:0 };
var magicTarget = { magicTrans:1 };
var magicTween = new TWEEN.Tween(magicPosition).to(magicTarget, 1000);
magicTween.easing(TWEEN.Easing.Linear.None);
var currentTexture=0;
var nextTexture=0;
var uniforms = {
  textures: {
    value: []
  },
  repeat: {
      type: 'f',
      value: 100
  },
  transition: { 
    value: 0
  },
  currentUniform: { 
    value: 0
  },
  nextUniform: { 
    value: 0
  }
};

var textureLoader = new THREE.TextureLoader();

var pics=[];
for (var i = 0; i < Object.keys(textureData).length; i++) {
  var ass="texture_"+i;
  pics[i]= textureData[ass].img;
  console.log(pics[i]);
}
pics.forEach((p, idx)=>{
  textureLoader.load(p, function(tex){
    tex.needsUpdate = true;
    uniforms.textures.value[idx] = tex;
uniforms.textures.value[idx].needsUpdate = true;
// console.log(tex);
        uniforms.textures.value[idx].minFilter = THREE.LinearFilter;

  })
});
var vertShader = `
    varying vec2 vUv;
    uniform float repeat;
    void main()
    {

      vUv = repeat * uv;

      vec4 mvPosition = modelViewMatrix * vec4(position, 1 );
      gl_Position = projectionMatrix * mvPosition;

    }
  `;
var fragShader = `
    uniform sampler2D textures[` + pics.length + `];
    uniform float transition;
    uniform float currentUniform;
    uniform float nextUniform;
    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()
    {

        float chunk = 1. / ` + 1 + `.; // amount of transitions = 1
        float t = floor(transition / chunk);
        int idx0 = int(currentUniform);
        int idx1 = int(nextUniform); 
        gl_FragColor = mix(
          getTexture(idx0),
          getTexture(idx1),
          (transition - (float(t) * chunk)) * ` + 1 + `.
        );


    }
  `;

  window.onload=function()
{
  init();
  animate();
}

function init(){


  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild(renderer.domElement);

  camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );

  controls = new THREE.TrackballControls( camera,renderer.domElement  );
camera.position.z = 500;
  console.log(camera.fov);

  scene = new THREE.Scene();


  scene.add(camera);
  var magicPlaneMaterial = new THREE.ShaderMaterial({
  uniforms: uniforms,
  vertexShader: vertShader,
  fragmentShader: fragShader,
    side: THREE.DoubleSide
  });

  for (var i = 0; i < Object.keys(textureData).length; i++) {
    uniforms.textures.value[i].wrapS = uniforms.textures.value[i].wrapT = THREE.RepeatWrapping;
    uniforms.textures.value[i].needsUpdate = true;

  }
  // for (var i = 0; i < Object.keys(textureData).length; i++) {
  //   uniforms.textures.value[i].wrapS = uniforms.textures.value[i].wrapT = THREE.RepeatWrapping;
  //   uniforms.textures.value[i].needsUpdate = true;

  // }


  var magicPlaneGeometry = new THREE.PlaneBufferGeometry(1000, 1000, 16, 16);

  var magicPlaneMesh = new THREE.Mesh(magicPlaneGeometry, magicPlaneMaterial);
  magicPlaneMesh.position.y = -500;
  magicPlaneMesh.rotation.x = Math.PI / 2;
  magicPlaneMesh.scale.x=10;
  magicPlaneMesh.scale.y=10;
  scene.add(magicPlaneMesh);
  changeMagicPlane(currentTexture);
  document.addEventListener( 'wheel', onDocumentMouseWheel, false );

  window.addEventListener( 'resize', onWindowResize, false );
}


function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}

function onDocumentMouseWheel( event ) {
  var fov = camera.fov + event.deltaY * 0.05;

  camera.fov = THREE.Math.clamp( fov, 10, 75 );
  console.log(camera.fov);

  camera.updateProjectionMatrix();
}

function animate() {
    if(tweenUpdate=="true")
  {
    TWEEN.update();
  }
    renderer.render( scene, camera );
  controls.update();
  requestAnimationFrame( animate );
}
function changeMagicPlane(asset){
  var assNum= parseInt(asset);    
  nextTexture = assNum;
  uniforms.nextUniform.value = nextTexture;
  console.log("Cuurent: "+currentTexture);
  console.log("Next: "+nextTexture);    
  magicTween.start(); 
  tweenUpdate="true";
  tweenOver="false";
}
magicTween.onUpdate(function(){
uniforms.transition.value = magicPosition.magicTrans;
});
magicTween.onComplete(function(){
  tweenUpdate="false";
  tweenOver="true";
    clicked="false";
    //console.log("Am i complete?");
    magicPosition.magicTrans=0;
    currentTexture=nextTexture;
    uniforms.currentUniform.value = currentTexture;
    console.log("Current: "+currentTexture);
});
</script>
</body>
</html>

我正在尝试使用 ShaderMaterial 来实现淡入淡出效果。我的纹理图像是 256*256 像素。

工作片段。从这里使用 Tween.js ( http://learningthreejs.com/blog/2011/08/17/tweenjs-for-smooth-animation/ )。gman.png 来自这里(https://i.imgur.com/ZKMnXce.png

标签: three.jswebgl

解决方案


通过将纹理缩小函数 ( ) 设置为值,您已禁用三线性纹理过滤 ( mipmaps ) :.minFilterTHREE.LinearFilter

uniforms.textures.value[idx].minFilter = THREE.LinearFilter;

这会导致莫尔效应

通过以下方式激活三线纹理过滤THREE.LinearMipMapLinearFilter(这是默认设置):

uniforms.textures.value[idx].minFilter = THREE.LinearMipMapLinearFilter;

无论如何,您的(片段)着色器代码具有未定义的行为,并且 mip-mapping 将不起作用:

vec4 getTexture(int index){
     for(int i = 0; i < ` + pics.length + `; i++){
        if (i == index){ return texture2D(textures[i],vUv); }
     }
}

void main()
{
    // ....

    gl_FragColor = mix(
         getTexture(idx0),
         getTexture(idx1),
         (transition - (float(t) * chunk)) * ` + 1 + `.
    );

请参阅OpenGL ES 着色语言 1.00 规范 - 13 致谢;第 107 页:

5 数组、向量和矩阵的索引

[...]

采样器

GLSL ES 1.00 支持采样器数组和包含采样器的结构数组。在这两种情况下,对于 ES 2.0,都要求支持使用常量索引表达式进行索引,但不要求支持使用其他值进行索引。

[...]

6 纹理访问

在非统一条件块的主体内访问 mip 映射纹理会给出未定义的值。非统一条件块是在编译时无法确定其执行的块。

在 main 的块范围内进行纹理查找,并使用constant-index-expression作为纹理采样器数组的索引:

例如

float a = transition - float(t) * chunk;
gl_FragColor = mix(texture2D(textures[0], vUv), texture2D(textures[1], vUv), a);

推荐阅读