Three.js video texture mapping low quality/resolution

2.5k Views Asked by At

I'm about to create a panoramic-360 video player with device orientation control for Android and IOS with cocoon.js and three.js.

I have successfully built the demo on the examples at threejs.org but struggling with a problem: My original test video file is 4000x1618 resolution and only 30sec long. Loading this makes audible voice but no picture. However if I try with a file converted down to 720x292, then it works perfectly fine on both android and ios! Unfortunately this lower res. video file is too poor quality, but if I try to load one any bigger, it will make only sound and no picture again.

I found these error logs in adb logcat when starting the movie:

E/OMXNodeInstance(  124): setParameter(4b:Nvidia.h264.decode, ParamPortDefinition(0x2000001)
W/ACodec  (  124): [OMX.Nvidia.h264.decode] setting nBufferCountActual to 13 failed: -1010

My code:

<!DOCTYPE html>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script src='cordova.js'></script>
<script src='js/three.js'></script>
<script src='js/OrbitControls.js'></script>
<script src='js/PointerLockControls.js'></script>
<script src='js/DeviceOrientationControls.js'></script>
<script src='js/stats.min.js'></script>
<body style='margin: 0px;; overflow: hidden; text-align:center;'>
<div id="btn" style='background: red; width: 200px; height: 200px; position: absolute; z-index: 1000;' onclick="start_video()">START VIDEÓ</div>
<script>

var video = document.createElement('video');
video.loop = true;
video.src = 'heroes-new-720p.mp4';

function start_video() {
 document.getElementById('btn').style.visibility = 'hidden';
 video.play();
}

window.addEventListener('load', function() {
 var renderer = new THREE.WebGLRenderer({
  antialias : true,
 });
 renderer.setClearColor(new THREE.Color('lightgrey'), 1)
 renderer.setSize( window.innerWidth, window.innerHeight );
 document.body.appendChild( renderer.domElement );
 var onRenderFcts= [];
 var scene = new THREE.Scene();
 var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
 camera.position.z = 3;

 var texture = new THREE.VideoTexture( video );
 texture.minFilter = THREE.LinearFilter;
 texture.format = THREE.RGBFormat;
 texture.generateMipmaps = false;

 var controls = new THREE.OrbitControls(camera)
 controls.target.copy(scene.position)
 function onDeviceOrientation(event){
  if( !event.alpha ) return;
  controls.enabled = false
  controls = new THREE.DeviceOrientationControls(camera);
  controls.connect();
  window.removeEventListener('deviceorientation', onDeviceOrientation, false);
  renderer.domElement.addEventListener('click', function(){
   var domElement = renderer.domElement
   if(domElement.requestFullscreen)  domElement.requestFullscreen();
   else if(domElement.msRequestFullscreen)  domElement.msRequestFullscreen();
   else if(domElement.mozRequestFullScreen) domElement.mozRequestFullScreen();
   else if(domElement.webkitRequestFullscreen) domElement.webkitRequestFullscreen();
  }, false);
 }
 window.addEventListener('deviceorientation', onDeviceOrientation, false);
 onRenderFcts.push(function(){
  controls.update()
 })

;(function(){
 var geometry = new THREE.SphereGeometry(10, 32, 16);
 var material = new THREE.MeshBasicMaterial({
                // opacity         : 0.5,
                // transparent     : true,
                // side            : THREE.DoubleSide,
    map: texture
        }); 
 var mesh = new THREE.Mesh( geometry, material );
 mesh.scale.x = -1
 scene.add( mesh );
})()


 onRenderFcts.push(function(){
  onWindowResize();
  renderable();
 })
 
 function renderable() {
  if ( video.readyState === video.HAVE_ENOUGH_DATA )  {
   renderer.render( scene, camera );
  }    
 }
 
 function onWindowResize(){
  renderer.setSize( window.innerWidth, window.innerHeight )
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()  
 }
 window.addEventListener('resize', onWindowResize, false)
 
 // run the rendering loop
 var lastTimeMsec= null
 requestAnimationFrame(function animate(nowMsec){
  // keep looping
  requestAnimationFrame( animate );
  // measure time
  lastTimeMsec = lastTimeMsec || nowMsec-1000/60
  var deltaMsec = Math.min(200, nowMsec - lastTimeMsec)
  lastTimeMsec = nowMsec
  // call each update function
  onRenderFcts.forEach(function(onRenderFct){
   onRenderFct(deltaMsec/1000, nowMsec/1000)
  })
 })
})
</script>
</body>

UPDATE: Since then I figured out that the problem is not the video resolution itself, but the dimensions. I created an 1920x1080 version of the original video that runs fine, so the only thing that bothers me is the quality. Even a 16000 kbps full HD video seems pretty pixelated on my Nexus 7 and ipad 4, I'm sure it should be nicer...

2

There are 2 best solutions below

1
fmarton On

Well, finally I figured out that none of my tested devices (Moto G, Nexus 7, iPad4, HTC m8) can handle a video as texture in a three.js sphere that is larger then 1920x1080. I'm not sure about the reason but I found a very interesting article about resolutions for panoramic video, which enlighted the problem of the quality too.

To cut it short: in a 360 video, 2K width of a full HD video at 120 degrees become 682 pixels width, so this is the reason why I found my videos' quality unsatisfying.

Here's the article if anyone is interested:

http://www.360heros.com/2015/02/4k-vr-360-video-what-is-it-and-how-can-i-produce-it/

0
curtiss On

Its not threejs nor cocoonjs related. Resolutions higher then 1080p dont make sense on mobilescreens (except your use case) so the hardware doesnt support it (in most cases). What you could check is if the h264 profile fits the resolution of your videofile. The iphone in special is very picky in case of wrong encoding!