【问题标题】:Three.js scene does not render in Safari 11.0.2Three.js 场景在 Safari 11.0.2 中不呈现
【发布时间】:2018-01-05 23:44:18
【问题描述】:

我正在尝试确定为什么 Three.js 场景无法在 Safari 11.0.2 (OSX 10.12.6) 中呈现。

/**
  * Generate a scene object with a background color
  **/

function getScene() {
  var scene = new THREE.Scene();
  scene.background = new THREE.Color(0x111111);
  return scene;
}

/**
  * Generate the camera to be used in the scene. Camera args:
  *   [0] field of view: identifies the portion of the scene
  *     visible at any time (in degrees)
  *   [1] aspect ratio: identifies the aspect ratio of the
  *     scene in width/height
  *   [2] near clipping plane: objects closer than the near
  *     clipping plane are culled from the scene
  *   [3] far clipping plane: objects farther than the far
  *     clipping plane are culled from the scene
  **/

function getCamera() {
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 10000);
  camera.position.set(0,150,400);
  camera.lookAt(scene.position);  
  return camera;
}

/**
  * Generate the light to be used in the scene. Light args:
  *   [0]: Hexadecimal color of the light
  *   [1]: Numeric value of the light's strength/intensity
  *   [2]: The distance from the light where the intensity is 0
  * @param {obj} scene: the current scene object
  **/

function getLight(scene) {
  var lights = [];
  lights[0] = new THREE.PointLight( 0xffffff, 0.6, 0 );
  lights[0].position.set( 100, 200, 100 );
  scene.add( lights[0] );

  var ambientLight = new THREE.AmbientLight(0x111111);
  scene.add(ambientLight);
  return light;
}

/**
  * Generate the renderer to be used in the scene
  **/

function getRenderer() {
  // Create the canvas with a renderer
  var renderer = new THREE.WebGLRenderer({antialias: true});
  // Add support for retina displays
  renderer.setPixelRatio(window.devicePixelRatio);
  // Specify the size of the canvas
  renderer.setSize(window.innerWidth, window.innerHeight);
  // Add the canvas to the DOM
  document.body.appendChild(renderer.domElement);
  return renderer;
}

/**
  * Generate the controls to be used in the scene
  * @param {obj} camera: the three.js camera for the scene
  * @param {obj} renderer: the three.js renderer for the scene
  **/

function getControls(camera, renderer) {
  var controls = new THREE.TrackballControls(camera, renderer.domElement);
  controls.zoomSpeed = 0.4;
  controls.panSpeed = 0.4;
  return controls;
}

/**
  * Get grass
  **/

function getPlane(scene, loader) {
  var texture = loader.load('grass.jpg');
  texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 
  texture.repeat.set( 10, 10 );
  var material = new THREE.MeshBasicMaterial({
    map: texture, side: THREE.DoubleSide
  });
  var geometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
  var plane = new THREE.Mesh(geometry, material);
  plane.position.y = -0.5;
  plane.rotation.x = Math.PI / 2;
  scene.add(plane);
  return plane;
}

/**
  * Add background
  **/

function getBackground(scene, loader) {
  var imagePrefix = '';
  var directions  = ['right', 'left', 'top', 'bottom', 'front', 'back'];
  var imageSuffix = '.bmp';
  var geometry = new THREE.BoxGeometry( 1000, 1000, 1000 ); 

  var materialArray = [];
  for (var i = 0; i < 6; i++)
    materialArray.push( new THREE.MeshBasicMaterial({
      map: loader.load(imagePrefix + directions[i] + imageSuffix),
      side: THREE.BackSide
    }));
  var sky = new THREE.Mesh( geometry, materialArray );
  scene.add(sky);
}

/**
  * Add a character
  **/

function getSphere(scene) {
  var geometry = new THREE.SphereGeometry( 30, 12, 9 );
  var material = new THREE.MeshPhongMaterial({
    color: 0xd0901d,
    emissive: 0xaf752a,
    side: THREE.DoubleSide,
    flatShading: true
  });
  var sphere = new THREE.Mesh( geometry, material );

  // create a group for translations and rotations
  var sphereGroup = new THREE.Group();
  sphereGroup.add(sphere)

  sphereGroup.position.set(0, 24, 100);
  scene.add(sphereGroup);
  return [sphere, sphereGroup];
}

/**
  * Store all currently pressed keys
  **/

function addListeners() {
  window.addEventListener('keydown', function(e) {
    pressed[e.key.toUpperCase()] = true;
  })
  window.addEventListener('keyup', function(e) {
    pressed[e.key.toUpperCase()] = false;
  })
}

/**
 * Update the sphere's position
 **/

function moveSphere() {
  var delta = clock.getDelta(); // seconds
  var moveDistance = 200 * delta; // 200 pixels per second
  var rotateAngle = Math.PI / 2 * delta; // pi/2 radians (90 deg) per sec

  // move forwards/backwards/left/right
  if ( pressed['W'] ) {
    sphere.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle)
    sphereGroup.translateZ( -moveDistance );
  }
  if ( pressed['S'] ) 
    sphereGroup.translateZ(  moveDistance );
  if ( pressed['Q'] )
    sphereGroup.translateX( -moveDistance );
  if ( pressed['E'] )
    sphereGroup.translateX(  moveDistance ); 

  // rotate left/right/up/down
  var rotation_matrix = new THREE.Matrix4().identity();
  if ( pressed['A'] )
    sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), rotateAngle);
  if ( pressed['D'] )
    sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), -rotateAngle);
  if ( pressed['R'] )
    sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), rotateAngle);
  if ( pressed['F'] )
    sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle);
}

/**
  * Follow the sphere
  **/

function moveCamera() {
  var relativeCameraOffset = new THREE.Vector3(0,50,200);
  var cameraOffset = relativeCameraOffset.applyMatrix4(sphereGroup.matrixWorld);
  camera.position.x = cameraOffset.x;
  camera.position.y = cameraOffset.y;
  camera.position.z = cameraOffset.z;
  camera.lookAt(sphereGroup.position);
}

// Render loop
function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  moveSphere();
  moveCamera();
};

// state
var pressed = {};
var clock = new THREE.Clock();

// globals
var scene = getScene();
var camera = getCamera();
var light = getLight(scene);
var renderer = getRenderer();

// add meshes
var loader = new THREE.TextureLoader();
var floor = getPlane(scene, loader);
var background = getBackground(scene, loader);
var sphereData = getSphere(scene);
var sphere = sphereData[0];
var sphereGroup = sphereData[1];

addListeners();
render();
  body { margin: 0; overflow: hidden; }
  canvas { width: 100%; height: 100%; }
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>

更一般地说,shadertoy.com [example] 上的所有示例在 Safari 11.0.2 上要么不显示,要么显示非常微弱且几乎完全为白色。

即使在我打开所有实验性网络功能(包括 WebGL 2.0)之后,“Safari 技术预览版”也是如此。

我想弄清楚如何渲染场景,但我更感兴趣的是了解其他人如何尝试调试此类问题。是否有工具或资源可以帮助您查明此类问题(例如仅用于 WebGL 的开发人员工具)?

【问题讨论】:

    标签: browser safari three.js webgl standards


    【解决方案1】:

    这个looks like a compositing bug in Safari。希望苹果能修复它。

    有几种解决方法。最简单的方法似乎是将主体或画布的背景颜色设置为黑色。

    /**
      * Generate a scene object with a background color
      **/
    
    function getScene() {
      var scene = new THREE.Scene();
      scene.background = new THREE.Color(0x111111);
      return scene;
    }
    
    /**
      * Generate the camera to be used in the scene. Camera args:
      *   [0] field of view: identifies the portion of the scene
      *     visible at any time (in degrees)
      *   [1] aspect ratio: identifies the aspect ratio of the
      *     scene in width/height
      *   [2] near clipping plane: objects closer than the near
      *     clipping plane are culled from the scene
      *   [3] far clipping plane: objects farther than the far
      *     clipping plane are culled from the scene
      **/
    
    function getCamera() {
      var aspectRatio = window.innerWidth / window.innerHeight;
      var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 10000);
      camera.position.set(0,150,400);
      camera.lookAt(scene.position);  
      return camera;
    }
    
    /**
      * Generate the light to be used in the scene. Light args:
      *   [0]: Hexadecimal color of the light
      *   [1]: Numeric value of the light's strength/intensity
      *   [2]: The distance from the light where the intensity is 0
      * @param {obj} scene: the current scene object
      **/
    
    function getLight(scene) {
      var lights = [];
      lights[0] = new THREE.PointLight( 0xffffff, 0.6, 0 );
      lights[0].position.set( 100, 200, 100 );
      scene.add( lights[0] );
    
      var ambientLight = new THREE.AmbientLight(0x111111);
      scene.add(ambientLight);
      return light;
    }
    
    /**
      * Generate the renderer to be used in the scene
      **/
    
    function getRenderer() {
      // Create the canvas with a renderer
      var renderer = new THREE.WebGLRenderer({antialias: true});
      // Add support for retina displays
      renderer.setPixelRatio(window.devicePixelRatio);
      // Specify the size of the canvas
      renderer.setSize(window.innerWidth, window.innerHeight);
      // Add the canvas to the DOM
      document.body.appendChild(renderer.domElement);
      return renderer;
    }
    
    /**
      * Generate the controls to be used in the scene
      * @param {obj} camera: the three.js camera for the scene
      * @param {obj} renderer: the three.js renderer for the scene
      **/
    
    function getControls(camera, renderer) {
      var controls = new THREE.TrackballControls(camera, renderer.domElement);
      controls.zoomSpeed = 0.4;
      controls.panSpeed = 0.4;
      return controls;
    }
    
    /**
      * Get grass
      **/
    
    function getPlane(scene, loader) {
      var texture = loader.load('grass.jpg');
      texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 
      texture.repeat.set( 10, 10 );
      var material = new THREE.MeshBasicMaterial({
        map: texture, side: THREE.DoubleSide
      });
      var geometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
      var plane = new THREE.Mesh(geometry, material);
      plane.position.y = -0.5;
      plane.rotation.x = Math.PI / 2;
      scene.add(plane);
      return plane;
    }
    
    /**
      * Add background
      **/
    
    function getBackground(scene, loader) {
      var imagePrefix = '';
      var directions  = ['right', 'left', 'top', 'bottom', 'front', 'back'];
      var imageSuffix = '.bmp';
      var geometry = new THREE.BoxGeometry( 1000, 1000, 1000 ); 
    
      var materialArray = [];
      for (var i = 0; i < 6; i++)
        materialArray.push( new THREE.MeshBasicMaterial({
          map: loader.load(imagePrefix + directions[i] + imageSuffix),
          side: THREE.BackSide
        }));
      var sky = new THREE.Mesh( geometry, materialArray );
      scene.add(sky);
    }
    
    /**
      * Add a character
      **/
    
    function getSphere(scene) {
      var geometry = new THREE.SphereGeometry( 30, 12, 9 );
      var material = new THREE.MeshPhongMaterial({
        color: 0xd0901d,
        emissive: 0xaf752a,
        side: THREE.DoubleSide,
        flatShading: true
      });
      var sphere = new THREE.Mesh( geometry, material );
    
      // create a group for translations and rotations
      var sphereGroup = new THREE.Group();
      sphereGroup.add(sphere)
    
      sphereGroup.position.set(0, 24, 100);
      scene.add(sphereGroup);
      return [sphere, sphereGroup];
    }
    
    /**
      * Store all currently pressed keys
      **/
    
    function addListeners() {
      window.addEventListener('keydown', function(e) {
        pressed[e.key.toUpperCase()] = true;
      })
      window.addEventListener('keyup', function(e) {
        pressed[e.key.toUpperCase()] = false;
      })
    }
    
    /**
     * Update the sphere's position
     **/
    
    function moveSphere() {
      var delta = clock.getDelta(); // seconds
      var moveDistance = 200 * delta; // 200 pixels per second
      var rotateAngle = Math.PI / 2 * delta; // pi/2 radians (90 deg) per sec
    
      // move forwards/backwards/left/right
      if ( pressed['W'] ) {
        sphere.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle)
        sphereGroup.translateZ( -moveDistance );
      }
      if ( pressed['S'] ) 
        sphereGroup.translateZ(  moveDistance );
      if ( pressed['Q'] )
        sphereGroup.translateX( -moveDistance );
      if ( pressed['E'] )
        sphereGroup.translateX(  moveDistance ); 
    
      // rotate left/right/up/down
      var rotation_matrix = new THREE.Matrix4().identity();
      if ( pressed['A'] )
        sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), rotateAngle);
      if ( pressed['D'] )
        sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), -rotateAngle);
      if ( pressed['R'] )
        sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), rotateAngle);
      if ( pressed['F'] )
        sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle);
    }
    
    /**
      * Follow the sphere
      **/
    
    function moveCamera() {
      var relativeCameraOffset = new THREE.Vector3(0,50,200);
      var cameraOffset = relativeCameraOffset.applyMatrix4(sphereGroup.matrixWorld);
      camera.position.x = cameraOffset.x;
      camera.position.y = cameraOffset.y;
      camera.position.z = cameraOffset.z;
      camera.lookAt(sphereGroup.position);
    }
    
    // Render loop
    function render() {
      requestAnimationFrame(render);
      renderer.render(scene, camera);
      moveSphere();
      moveCamera();
    };
    
    // state
    var pressed = {};
    var clock = new THREE.Clock();
    
    // globals
    var scene = getScene();
    var camera = getCamera();
    var light = getLight(scene);
    var renderer = getRenderer();
    
    // add meshes
    var loader = new THREE.TextureLoader();
    var floor = getPlane(scene, loader);
    var background = getBackground(scene, loader);
    var sphereData = getSphere(scene);
    var sphere = sphereData[0];
    var sphereGroup = sphereData[1];
    
    addListeners();
    render();
    body { margin: 0; overflow: hidden; }
    canvas { width: 100%; height: 100%; background: black; }
    <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
    <script src='https://threejs.org/examples/js/controls/TrackballControls.js'></script>

    至于如何知道如何找到这个错误,在这种特殊情况下,除了经验我不知道我是怎么知道的。我知道很遗憾,浏览器使用 WebGL 合成错误是很常见的,因为它很难测试。大多数浏览器在没有 GPU 的服务器上进行测试,这意味着它们对 WebGL 的测试不够。他们在 GPU 加速合成之前构建了他们的测试系统。另一个原因是测试合成是特定于浏览器的东西,因此 WebGL 测试不能包含对它的测试。这是每个浏览器供应商都必须针对 their testing systems run the browsers in non-release modes or the APIs that might make it possible to test don't actually go through the same code as the code the draws to the screen 实现自己的测试的东西。

    对于 WebGL,您通常应该在不同的浏览器中获得相同的结果,而合成问题是最常见的错误之处。特别是在不使用默认值时。因此,首先我检查了上下文是否设置为非默认,如alpha: falsepremultipliedAlpha: false 等。为此,我只需打开 Chrome 的开发工具并选择 sn-p 上下文

    一旦我有了正确的调试器上下文,我就从第一个画布中获得了 WebGL 上下文

    我看到alpha: false 不是默认值,所以这是第一个线索。如果有多个画布,我将不得不使用“querySelectorAll”并尝试每个画布,直到获得 WebGL 一个。

    然后我也看到你的 CSS 和我做的不一样。我会用的

    body { margin: 0; }
    canvas { width: 100vw; height: 100vw; display: block; }
    

    不需要overflow: hidden 并明确说明我想要什么。 I have strong opinions 大多数 three.js 应用程序大小画布的方式是一种反模式。

    我看到您将 CSS 设置为使画布高度为 100%,但您没有设置主体高度,因此如果不执行其他任何操作,您的画布高度将为零。所以,我设置了画布的背景颜色,这样我就可以看到它有多大。我假设它实际上是零。那时(a)我看到它正在渲染并设置背景颜色使其出现,并且(b)你的画布出现了,因为 three.js 正在根据window.innerHeight 修改画布大小并且还使用你的 css

    【讨论】:

    • 感谢@gman,感谢您在过去提供的有关 WebGL 性能技巧的精彩视频——它教会了我很多东西!我通常在 SO 帖子中发布代码,但这里讨论的是影响 shadertoy.com 上所有 sn-ps 的更广泛的浏览器问题,因此认为不需要特定的代码示例。无论如何,您是立即开始搜索 Safari 浏览器错误,还是在确定这可能是浏览器错误之前使用了其他诊断工具?我正在尝试更多地了解 WebGL 打嗝中的调查过程。
    • 在答案中添加了一些细节,因为它们不适合这里
    • 我使用了这个修复程序,一切都很完美!现在使用 Safari 11.0.3 和 OSX 10.13.3 屏幕全黑!还有什么?如果您运行此解决方案中包含的代码 sn-p,您也会看到黑屏。在以前的版本中一切都很好。我刚刚发现,如果我移动 Safari 窗口(不是全屏模式),当我释放按钮时,我看到的绘图不到一秒钟。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多