我一直在尝试将找到的here的THREE.js WebGL转换为THREE.js WebVR,以使其具有VR兼容性。我修改了main.js文件以使用某些功能,并添加了相关的javascript库库。
但是,在测试VR功能时,我注意到了两个主要问题。首先是VR跟踪控件被颠倒了。换句话说,如果我抬头,相机就往下看。如果我向左看,相机向右看,反之亦然。
另一个问题是图像看起来相距太远。当我使用VR头戴式耳机时,会看到两个单独的图像,而不是一个整体的图像。
我想知道是否有人知道导致这些问题的原因以及如何解决这些问题。我怀疑问题与原始代码中存在的一些复杂旋转有关,这些旋转未正确转换为VR。一般来说,这是我第一次使用VR或THREE.js,因此不胜感激。
我认为这是代码的相关部分:
"use strict"
/*global THREE, SHADER_LOADER, Mustache, Stats, Detector, $, dat:false */
/*global document, window, setTimeout, requestAnimationFrame:false */
/*global ProceduralTextures:false */
if (!Detector.webgl) Detector.addGetWebGLMessage();
function Observer() {
this.position = new THREE.Vector3(10, 0, 0);
this.velocity = new THREE.Vector3(0, 1, 0);
this.orientation = new THREE.Matrix3();
this.time = 0.0;
}
Observer.prototype.orbitalFrame = function() {
//var orbital_y = observer.velocity.clone().normalize();
var orbital_y = (new THREE.Vector3())
.subVectors(observer.velocity.clone().normalize().multiplyScalar(4.0),
observer.position).normalize();
var orbital_z = (new THREE.Vector3())
.crossVectors(observer.position, orbital_y).normalize();
var orbital_x = (new THREE.Vector3()).crossVectors(orbital_y, orbital_z);
return (new THREE.Matrix4()).makeBasis(
orbital_x,
orbital_y,
orbital_z
).linearPart();
};
Observer.prototype.move = function(dt) {
dt *= shader.parameters.time_scale;
var r;
var v = 0;
// motion on a pre-defined cirular orbit
if (shader.parameters.observer.motion) {
r = shader.parameters.observer.distance;
v = 1.0 / Math.sqrt(2.0 * (r - 1.0));
var ang_vel = v / r;
var angle = this.time * ang_vel;
var s = Math.sin(angle),
c = Math.cos(angle);
this.position.set(c * r, s * r, 0);
this.velocity.set(-s * v, c * v, 0);
var alpha = degToRad(shader.parameters.observer.orbital_inclination);
var orbit_coords = (new THREE.Matrix4()).makeRotationY(alpha);
this.position.applyMatrix4(orbit_coords);
this.velocity.applyMatrix4(orbit_coords);
} else {
r = this.position.length();
}
if (shader.parameters.gravitational_time_dilation) {
dt = Math.sqrt((dt * dt * (1.0 - v * v)) / (1 - 1.0 / r));
}
this.time += dt;
};
var container, stats;
var camera, scene, renderer, cameraControls, shader, dollycam;
var observer = new Observer();
...
var updateUniforms;
function init(textures) {
container = document.createElement('div');
document.body.appendChild(container);
scene = new THREE.Scene();
var geometry = new THREE.PlaneBufferGeometry(2, 2);
var uniforms = {
time: {
type: "f",
value: 0
},
resolution: {
type: "v2",
value: new THREE.Vector2()
},
cam_pos: {
type: "v3",
value: new THREE.Vector3()
},
cam_x: {
type: "v3",
value: new THREE.Vector3()
},
cam_y: {
type: "v3",
value: new THREE.Vector3()
},
cam_z: {
type: "v3",
value: new THREE.Vector3()
},
cam_vel: {
type: "v3",
value: new THREE.Vector3()
},
planet_distance: {
type: "f"
},
planet_radius: {
type: "f"
},
star_texture: {
type: "t",
value: textures.stars
},
accretion_disk_texture: {
type: "t",
value: textures.accretion_disk
},
galaxy_texture: {
type: "t",
value: textures.galaxy
},
planet_texture: {
type: "t",
value: textures.moon
},
spectrum_texture: {
type: "t",
value: textures.spectra
}
};
updateUniforms = function() {
uniforms.planet_distance.value = shader.parameters.planet.distance;
uniforms.planet_radius.value = shader.parameters.planet.radius;
uniforms.resolution.value.x = renderer.domElement.width;
uniforms.resolution.value.y = renderer.domElement.height;
uniforms.time.value = observer.time;
uniforms.cam_pos.value = observer.position;
var e = observer.orientation.elements;
uniforms.cam_x.value.set(e[0], e[1], e[2]);
uniforms.cam_y.value.set(e[3], e[4], e[5]);
uniforms.cam_z.value.set(e[6], e[7], e[8]);
function setVec(target, value) {
uniforms[target].value.set(value.x, value.y, value.z);
}
setVec('cam_pos', observer.position);
setVec('cam_vel', observer.velocity);
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: $('#vertex-shader').text(),
});
scene.updateShader = function() {
material.fragmentShader = shader.compile();
material.needsUpdate = true;
shader.needsUpdate = true;
};
scene.updateShader();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.vr.enabled = true; // added vr
document.body.appendChild(renderer.domElement);
document.body.appendChild(WEBVR.createButton(renderer));
// renderer.autoClear = false;
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild(stats.domElement);
$(stats.domElement).addClass('hidden-phone');
// Orbit camera from three.js
camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 1, 80000);
// initializeCamera(camera);
//initiallize camera
var pitchAngle = 3.0,
yawAngle = 0.0;
// there are nicely named methods such as "lookAt" in the camera object
// but there do not do a thing to the projection matrix due to an internal
// representation of the camera coordinates using a quaternion (nice)
camera.matrixWorldInverse.makeRotationX(degToRad(-pitchAngle));
camera.matrixWorldInverse.multiply(new THREE.Matrix4().makeRotationY(degToRad(-yawAngle)));
var m = camera.matrixWorldInverse.elements;
dollycam = new THREE.PerspectiveCamera();
dollycam.position.set(m[2], m[6], m[10]);
// camera.position.set(m[2],m[6],m[10]);
dollycam.add(camera);
scene.add(dollycam);
cameraControls = new THREE.OrbitControls(dollycam, renderer.domElement);
// cameraControls.target.set( 0, 0, 0 );
cameraControls.addEventListener('change', updateCamera);
cameraControls.update()
updateCamera();
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
setupGUI();
}
...
function onWindowResize(event) {
renderer.setSize(window.innerWidth, window.innerHeight);
updateUniforms();
}
function updateCamera(event) {
var zoom_dist = camera.position.length();
var m = camera.matrixWorldInverse.elements;
var camera_matrix;
if (shader.parameters.observer.motion) {
camera_matrix = new THREE.Matrix3();
} else {
camera_matrix = observer.orientation;
}
camera_matrix.set(
// row-major, not the same as .elements (nice)
// y and z swapped for a nicer coordinate system
m[0], m[1], m[2],
m[8], m[9], m[10],
m[4], m[5], m[6]
);
observer.orientation = observer.orbitalFrame().multiply(camera_matrix);
}
..
function animate() {
// requestAnimationFrame( animate );
// renderer.setAnimationLoop( renderer.render( scene, camera ) );
renderer.setAnimationLoop(render);
}
var lastCameraMat = new THREE.Matrix4().identity();
var getFrameDuration = (function() {
var lastTimestamp = new Date().getTime();
return function() {
var timestamp = new Date().getTime();
var diff = (timestamp - lastTimestamp) / 1000.0;
lastTimestamp = timestamp;
return diff;
};
})();
function render() {
// renderer.render( scene, camera );
camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse(camera.matrixWorld);
// dollycam.updateMatrixWorld();
// dollycam.matrixWorldInverse.getInverse( dollycam.matrixWorld );
updateCamera();
if (shader.needsUpdate || shader.hasMovingParts() ||
frobeniusDistance(camera.matrixWorldInverse, lastCameraMat) > 1e-10) {
shader.needsUpdate = false;
observer.move(getFrameDuration());
cameraControls.update();
updateCamera();
updateUniforms();
// render();
renderer.render(scene, camera);
lastCameraMat = camera.matrixWorldInverse.clone();
stats.update();
}
这是完整的代码:
"use strict"
/*global THREE, SHADER_LOADER, Mustache, Stats, Detector, $, dat:false */
/*global document, window, setTimeout, requestAnimationFrame:false */
/*global ProceduralTextures:false */
if (!Detector.webgl) Detector.addGetWebGLMessage();
function Observer() {
this.position = new THREE.Vector3(10, 0, 0);
this.velocity = new THREE.Vector3(0, 1, 0);
this.orientation = new THREE.Matrix3();
this.time = 0.0;
}
Observer.prototype.orbitalFrame = function() {
//var orbital_y = observer.velocity.clone().normalize();
var orbital_y = (new THREE.Vector3())
.subVectors(observer.velocity.clone().normalize().multiplyScalar(4.0),
observer.position).normalize();
var orbital_z = (new THREE.Vector3())
.crossVectors(observer.position, orbital_y).normalize();
var orbital_x = (new THREE.Vector3()).crossVectors(orbital_y, orbital_z);
return (new THREE.Matrix4()).makeBasis(
orbital_x,
orbital_y,
orbital_z
).linearPart();
};
Observer.prototype.move = function(dt) {
dt *= shader.parameters.time_scale;
var r;
var v = 0;
// motion on a pre-defined cirular orbit
if (shader.parameters.observer.motion) {
r = shader.parameters.observer.distance;
v = 1.0 / Math.sqrt(2.0 * (r - 1.0));
var ang_vel = v / r;
var angle = this.time * ang_vel;
var s = Math.sin(angle),
c = Math.cos(angle);
this.position.set(c * r, s * r, 0);
this.velocity.set(-s * v, c * v, 0);
var alpha = degToRad(shader.parameters.observer.orbital_inclination);
var orbit_coords = (new THREE.Matrix4()).makeRotationY(alpha);
this.position.applyMatrix4(orbit_coords);
this.velocity.applyMatrix4(orbit_coords);
} else {
r = this.position.length();
}
if (shader.parameters.gravitational_time_dilation) {
dt = Math.sqrt((dt * dt * (1.0 - v * v)) / (1 - 1.0 / r));
}
this.time += dt;
};
var container, stats;
var camera, scene, renderer, cameraControls, shader, dollycam;
var observer = new Observer();
function Shader(mustacheTemplate) {
// Compile-time shader parameters
this.parameters = {
n_steps: 100,
quality: 'medium',
accretion_disk: true,
planet: {
enabled: true,
distance: 4.0,
radius: 0.5
},
lorentz_contraction: true,
gravitational_time_dilation: true,
aberration: true,
beaming: true,
doppler_shift: true,
light_travel_time: true,
time_scale: 1.0,
observer: {
motion: true,
distance: 7.0,
orbital_inclination: -10
},
planetEnabled: function() {
return this.planet.enabled && this.quality !== 'fast';
},
observerMotion: function() {
return this.observer.motion;
}
};
var that = this;
this.needsUpdate = false;
this.hasMovingParts = function() {
return this.parameters.planet.enabled || this.parameters.observer.motion;
};
this.compile = function() {
return Mustache.render(mustacheTemplate, that.parameters);
};
}
function degToRad(a) {
return Math.PI * a / 180.0;
}
(function() {
var textures = {};
function whenLoaded() {
init(textures);
$('#loader').hide();
$('.initially-hidden').removeClass('initially-hidden');
animate();
}
function checkLoaded() {
if (shader === null) return;
for (var key in textures)
if (textures[key] === null) return;
whenLoaded();
}
SHADER_LOADER.load(function(shaders) {
shader = new Shader(shaders.raytracer.fragment);
checkLoaded();
});
var texLoader = new THREE.TextureLoader();
function loadTexture(symbol, filename, interpolation) {
textures[symbol] = null;
texLoader.load(filename, function(tex) {
tex.magFilter = interpolation;
tex.minFilter = interpolation;
textures[symbol] = tex;
checkLoaded();
});
}
loadTexture('galaxy', 'img/milkyway.jpg', THREE.NearestFilter);
loadTexture('spectra', 'img/spectra.png', THREE.LinearFilter);
loadTexture('moon', 'img/beach-ball.png', THREE.LinearFilter);
loadTexture('stars', 'img/stars.png', THREE.LinearFilter);
loadTexture('accretion_disk', 'img/accretion-disk.png', THREE.LinearFilter);
})();
var updateUniforms;
function init(textures) {
container = document.createElement('div');
document.body.appendChild(container);
scene = new THREE.Scene();
var geometry = new THREE.PlaneBufferGeometry(2, 2);
var uniforms = {
time: {
type: "f",
value: 0
},
resolution: {
type: "v2",
value: new THREE.Vector2()
},
cam_pos: {
type: "v3",
value: new THREE.Vector3()
},
cam_x: {
type: "v3",
value: new THREE.Vector3()
},
cam_y: {
type: "v3",
value: new THREE.Vector3()
},
cam_z: {
type: "v3",
value: new THREE.Vector3()
},
cam_vel: {
type: "v3",
value: new THREE.Vector3()
},
planet_distance: {
type: "f"
},
planet_radius: {
type: "f"
},
star_texture: {
type: "t",
value: textures.stars
},
accretion_disk_texture: {
type: "t",
value: textures.accretion_disk
},
galaxy_texture: {
type: "t",
value: textures.galaxy
},
planet_texture: {
type: "t",
value: textures.moon
},
spectrum_texture: {
type: "t",
value: textures.spectra
}
};
updateUniforms = function() {
uniforms.planet_distance.value = shader.parameters.planet.distance;
uniforms.planet_radius.value = shader.parameters.planet.radius;
uniforms.resolution.value.x = renderer.domElement.width;
uniforms.resolution.value.y = renderer.domElement.height;
uniforms.time.value = observer.time;
uniforms.cam_pos.value = observer.position;
var e = observer.orientation.elements;
uniforms.cam_x.value.set(e[0], e[1], e[2]);
uniforms.cam_y.value.set(e[3], e[4], e[5]);
uniforms.cam_z.value.set(e[6], e[7], e[8]);
function setVec(target, value) {
uniforms[target].value.set(value.x, value.y, value.z);
}
setVec('cam_pos', observer.position);
setVec('cam_vel', observer.velocity);
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: $('#vertex-shader').text(),
});
scene.updateShader = function() {
material.fragmentShader = shader.compile();
material.needsUpdate = true;
shader.needsUpdate = true;
};
scene.updateShader();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.vr.enabled = true; // added vr
document.body.appendChild(renderer.domElement);
document.body.appendChild(WEBVR.createButton(renderer));
// renderer.autoClear = false;
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild(stats.domElement);
$(stats.domElement).addClass('hidden-phone');
// Orbit camera from three.js
camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 1, 80000);
// initializeCamera(camera);
//initiallize camera
var pitchAngle = 3.0,
yawAngle = 0.0;
// there are nicely named methods such as "lookAt" in the camera object
// but there do not do a thing to the projection matrix due to an internal
// representation of the camera coordinates using a quaternion (nice)
camera.matrixWorldInverse.makeRotationX(degToRad(-pitchAngle));
camera.matrixWorldInverse.multiply(new THREE.Matrix4().makeRotationY(degToRad(-yawAngle)));
var m = camera.matrixWorldInverse.elements;
dollycam = new THREE.PerspectiveCamera();
dollycam.position.set(m[2], m[6], m[10]);
// camera.position.set(m[2],m[6],m[10]);
dollycam.add(camera);
scene.add(dollycam);
cameraControls = new THREE.OrbitControls(dollycam, renderer.domElement);
// cameraControls.target.set( 0, 0, 0 );
cameraControls.addEventListener('change', updateCamera);
cameraControls.update()
updateCamera();
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
setupGUI();
}
function setupGUI() {
var hint = $('#hint-text');
var p = shader.parameters;
function updateShader() {
hint.hide();
scene.updateShader();
}
var gui = new dat.GUI();
gui.add(p, 'quality', ['fast', 'medium', 'high']).onChange(function(value) {
$('.planet-controls').show();
switch (value) {
case 'fast':
p.n_steps = 40;
$('.planet-controls').hide();
break;
case 'medium':
p.n_steps = 100;
break;
case 'high':
p.n_steps = 200;
break;
}
updateShader();
});
gui.add(p, 'accretion_disk').onChange(updateShader);
var folder = gui.addFolder('Observer');
folder.add(p.observer, 'motion').onChange(function(motion) {
updateCamera();
updateShader();
if (motion) {
hint.text('Moving observer; drag to rotate camera');
} else {
hint.text('Stationary observer; drag to orbit around');
}
hint.fadeIn();
});
folder.add(p.observer, 'distance').min(1.5).max(30).onChange(updateCamera);
folder.open();
folder = gui.addFolder('Planet');
folder.add(p.planet, 'enabled').onChange(function(enabled) {
updateShader();
var controls = $('.indirect-planet-controls').show();
if (enabled) controls.show();
else controls.hide();
});
folder.add(p.planet, 'distance').min(1.5).onChange(updateUniforms);
folder.add(p.planet, 'radius').min(0.01).max(2.0).onChange(updateUniforms);
$(folder.domElement).addClass('planet-controls');
//folder.open();
function setGuiRowClass(guiEl, klass) {
$(guiEl.domElement).parent().parent().addClass(klass);
}
folder = gui.addFolder('Relativistic effects');
folder.add(p, 'aberration').onChange(updateShader);
folder.add(p, 'beaming').onChange(updateShader);
folder.add(p, 'doppler_shift').onChange(updateShader);
setGuiRowClass(
folder.add(p, 'gravitational_time_dilation').onChange(updateShader),
'planet-controls indirect-planet-controls');
setGuiRowClass(
folder.add(p, 'lorentz_contraction').onChange(updateShader),
'planet-controls indirect-planet-controls');
folder.open();
folder = gui.addFolder('Time');
folder.add(p, 'light_travel_time').onChange(updateShader);
folder.add(p, 'time_scale').min(0);
//folder.open();
}
function onWindowResize(event) {
renderer.setSize(window.innerWidth, window.innerHeight);
updateUniforms();
}
function updateCamera(event) {
var zoom_dist = camera.position.length();
var m = camera.matrixWorldInverse.elements;
var camera_matrix;
if (shader.parameters.observer.motion) {
camera_matrix = new THREE.Matrix3();
} else {
camera_matrix = observer.orientation;
}
camera_matrix.set(
// row-major, not the same as .elements (nice)
// y and z swapped for a nicer coordinate system
m[0], m[1], m[2],
m[8], m[9], m[10],
m[4], m[5], m[6]
);
// if (shader.parameters.observer.motion) {
observer.orientation = observer.orbitalFrame().multiply(camera_matrix);
// } else {
// var p = new THREE.Vector3(
// camera_matrix.elements[6],
// camera_matrix.elements[7],
// camera_matrix.elements[8]);
// var dist = shader.parameters.observer.distance;
// observer.position.set(-p.x*dist, -p.y*dist, -p.z*dist);
// observer.velocity.set(0,0,0);
// }
}
function frobeniusDistance(matrix1, matrix2) {
var sum = 0.0;
for (var i in matrix1.elements) {
var diff = matrix1.elements[i] - matrix2.elements[i];
sum += diff * diff;
}
return Math.sqrt(sum);
}
function animate() {
// requestAnimationFrame( animate );
// renderer.setAnimationLoop( renderer.render( scene, camera ) );
renderer.setAnimationLoop(render);
}
var lastCameraMat = new THREE.Matrix4().identity();
var getFrameDuration = (function() {
var lastTimestamp = new Date().getTime();
return function() {
var timestamp = new Date().getTime();
var diff = (timestamp - lastTimestamp) / 1000.0;
lastTimestamp = timestamp;
return diff;
};
})();
function render() {
// renderer.render( scene, camera );
camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse(camera.matrixWorld);
// dollycam.updateMatrixWorld();
// dollycam.matrixWorldInverse.getInverse( dollycam.matrixWorld );
updateCamera();
if (shader.needsUpdate || shader.hasMovingParts() ||
frobeniusDistance(camera.matrixWorldInverse, lastCameraMat) > 1e-10) {
shader.needsUpdate = false;
observer.move(getFrameDuration());
cameraControls.update();
updateCamera();
updateUniforms();
// render();
renderer.render(scene, camera);
lastCameraMat = camera.matrixWorldInverse.clone();
}
stats.update();
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Black Hole</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta property="og:url" content="https://oseiskar.github.io/black-hole" />
<meta property="og:title" content="THREE.js Black Hole" />
<meta property="og:description" content="A real time ray-traced simulation of a Schwarzschild black hole. Needs a decent GPU and a recent variant of Chrome or Firefox to run smoothly." />
<meta property="og:image" content="http://oseiskar.github.io/img/black-hole.jpg" />
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<!DOCTYPE html>
<html lang="en">
<head>
<div id="hint-text" class="hidden-phone initially-hidden">
<!-- Drag to rotate or toggle variables on the right ⟶ -->
</div>
<div class="info ">
<div id="loader">
Loading...
<div class="disclaimer">
</div>
</div>
</div>
<!-- <div class="link-bar">
<a href="README" target="_blank"><i class="fa fa-question-circle fa-lg"></i> What is this?</a>
|
<a href="https://github.com/oseiskar/black-hole" target="_blank"><i class="fa fa-github fa-lg"></i> Source</a>
|
<a href="docs/physics.html" target="_blank"><i class="fa fa-lg">Σ</i> Physics</a>
|
</div> -->
<script src="js-libs/jquery-2.1.4.min.js"></script>
<!-- <script src="js-libs/three.min.js"></script> -->
<script src="js-libs/three.js"></script>
<script src="js-libs/WebVR.js"></script>
<!-- <script src="js-libs/VRControls.js"></script> -->
<!-- <script src="js-libs/VREffect.js"></script> -->
<script src="js-libs/OrbitControls.js"></script>
<script src="js-libs/Detector.js"></script>
<!-- <script src="js-libs/DeviceOrientationControls.js"></script> -->
<script src="js-libs/stats.min.js"></script>
<script src="js-libs/ShaderLoader.min.js"></script>
<script src="js-libs/mustache.min.js"></script>
<script src="js-libs/dat.gui.min.js"></script>
<script src="three-js-monkey-patch.js"></script>
<!-- <script src="js-libs/webvr-polyfill.js"></script> -->
<!-- <script src="js-libs/webvr-manager.js"></script> -->
<script id="vertex-shader" type="x-shader/x-vertex">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script data-src="raytracer.glsl" data-name="raytracer" type="x-shader/x-fragment"></script>
<script src="main.js"></script>
</body>
</html>
编辑1:我解决了更换倒置摄像头的问题
function updateCamera(event) {
var zoom_dist = camera.position.length();
var m = camera.matrixWorldInverse.elements;
var camera_matrix;
与
function updateCamera(event) {
var zoom_dist = camera.position.length();
var m = camera.matrixWorld.elements;
var camera_matrix;
我的显示器仍然有问题。看起来,VR模式下的显示呈现为单个连续图像,而不是两个稍微偏移的立体图像。