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

duh*_*ime 4 browser safari standards webgl three.js

我试图确定为什么 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();
Run Code Online (Sandbox Code Playgroud)
  body { margin: 0; overflow: hidden; }
  canvas { width: 100%; height: 100%; }
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

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

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

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

gma*_*man 5

看起来像是 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();
Run Code Online (Sandbox Code Playgroud)
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; background: black; }
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

至于如何知道如何找到这个错误,在这种特殊情况下我不知道除了经验之外我是如何知道的。不幸的是,我知道浏览器在使用 WebGL 时遇到合成错误很常见,因为它很难测试。大多数浏览器在没有 GPU 的服务器上进行测试,这意味着它们对 WebGL 的测试不够。他们在 GPU 加速合成之前构建了测试系统。另一个原因是测试合成是特定于浏览器的,因此 WebGL 测试不能包含针对它的测试。每个浏览器供应商都必须实现自己的测试,并且通常他们的测试系统以非发布模式运行浏览器,或者可能使测试成为可能的 API 实际上不会执行与绘制的代码相同的代码屏幕

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

在此输入图像描述

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

在此输入图像描述

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

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

body { margin: 0; }
canvas { width: 100vw; height: 100vw; display: block; }
Run Code Online (Sandbox Code Playgroud)

不需要overflow: hidden并明确说明我想要什么。我强烈认为大多数 Three.js 应用程序调整画布大小的方式是一种反模式。

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