Three.js场景在Safari 11.0.2中无法渲染。

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

/**
  * 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 [示例] 的所有示例在 Safari 11.0.2 上都无法显示或以非常淡的方式出现,几乎全部是白色。
即使我打开了包括WebGL 2.0在内的所有实验性网络功能,“Safari技术预览”也是如此。
我想找出如何使场景渲染,但更有兴趣了解其他人如何尝试调试这种问题。是否有工具或资源可以帮助人们找到这种问题的原因(例如专门针对WebGL的开发人员工具)?
1个回答

5
看起来像是Safari中的合成错误。希望苹果公司会修复它。
有几种解决方法。最简单的方法似乎是将body或canvas的背景颜色设置为黑色。

/**
  * 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测试不能包括对其进行测试的测试。这是每个浏览器供应商必须实现自己的测试,而且通常他们的测试系统在非发布模式下运行浏览器,或者可能使测试变得可能的API实际上并没有通过绘制到屏幕的代码

对于WebGL,您应该在各种浏览器中获得相同的结果,并且合成问题是最常见的错误发生地点。特别是当不使用默认设置时。因此,首先我检查上下文是否被设置为非默认值,例如 alpha:falsepremultipliedAlpha:false 等等。为此,我只需打开Chrome的开发工具并选择片段上下文。

enter image description here

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

enter image description here

我看到了alpha: false,这不是默认值,所以这是第一个线索。如果有多个画布,我将不得不使用“querySelectorAll”并尝试每个画布,直到找到WebGL画布。
然后我还看到你的CSS与我的做法不同。我会使用:
body { margin: 0; }
canvas { width: 100vw; height: 100vw; display: block; }

不需要使用“overflow: hidden”,并且清楚地表明了我的意图。我坚信大多数three.js应用程序调整画布大小的方式是反模式。
我看到您将CSS设置为使画布高度为100%,但您没有设置body的高度,因此如果没有其他操作,您的画布将具有零高度。因此,我设置了画布的背景颜色,以便我可以看到它有多大。我假设它实际上是零。那时,(a)我看到它正在渲染,并且设置背景颜色使其出现,(b)您的画布出现,因为three.js基于window.innerHeight和修改您的CSS来操纵画布大小。

感谢@gman,也感谢您从以前开始就提供的关于WebGL性能技巧的精彩视频——它教会了我很多!我通常在SO帖子中发布代码,但这里讨论的是影响shadertoy.com上所有片段的更广泛的浏览器问题,因此认为不需要特定的代码示例。无论如何,您是立即开始搜索Safari浏览器错误,还是在确定这可能是浏览器错误之前使用其他诊断工具?我正在尝试了解有关WebGL故障排除过程的更多信息。 - duhaime
由于这里无法容纳,我已经在答案中添加了一些细节。 - gman
我使用了这个修复方法,一切都很完美!但是现在在Safari 11.0.3和OSX 10.13.3下,屏幕全是黑色的!还有什么问题吗?如果你运行这个解决方案中包含的代码片段,你也会看到黑屏。在之前的版本中,一切都很好。我刚刚发现,如果我移动Safari窗口(不是全屏模式),当我放开按钮时,我能看到画面不到一秒钟。 - Martin Pineault
我已经厌倦了让threejs在Safari上完美运行,所以现在我展示我的传统print.css给Safari浏览器,并且就此结束了。 - thdoan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接