Создайте упрощенный отражающий пользовательский шейдер в A-FRAME

Так что я неизбежно пошел по пути пользовательских шейдеров в A-FRAME. Когда я действительно исследовал это, и учитывая, что меня можно считать новичком в этой технологии, я наткнулся на всевозможные сложные и не очень понятные решения.

Например: https://rawgit.com/mrdoob/three.js/master/examples/webgl_materials_cubemap_balls_reflection.html создайте больше вопросов, чем ответов.

Однако, когда я посетил этот пример: https://stemkoski.github.io/Three.js/Reflection.html , я заметил другой подход, который казался более упрощенным.

Это побудило меня больше узнать о CubeCameras и о том, как threejs может использовать их в качестве шейдера для имитации отражения.

//Create cube camera
var cubeCamera = new THREE.CubeCamera( 1, 100000, 128 );
scene.add( cubeCamera );

//Create car
var chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeCamera.renderTarget } );
var car = new Mesh( carGeometry, chromeMaterial );
scene.add( car );

//Update the render target cube
car.setVisible( false );
cubeCamera.position.copy( car.position );
cubeCamera.updateCubeMap( renderer, scene );

//Render the scene
car.setVisible( true );
renderer.render( scene, camera );

Теперь вопрос в том, как я могу перевести это в A-FRAME? Я пробовал следующее:

AFRAME.registerComponent('chromeThing', {
      schema: {
        ???
      },

      init: function () {
        var el = this.el;  // Entity.
        var cubeCam = document.querySelector('#cubeCam'); //There is an <a-entity camera id="cubeCam">
        var mirrorCubeMaterial = new THREE.MeshBasicMaterial( { envMap: cubeCam.renderTarget } );
        mirrorCube = new THREE.Mesh( el, mirrorCubeMaterial );

        el.setObject3D('mesh', mirrorCube);
      }
    });

Как вы могли заметить, я не уверен, какова будет схема этого. Кроме того, это должен быть шейдер или компонент? (как я мог бы использовать это лучше всего)

РЕДАКТИРОВАТЬ: после ответа @ngokevin я придумал этот код, который все еще не дает мне желаемых результатов.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Chrome box</title>
    <meta name="description" content="Physically-Based Materials - A-Frame">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <script src="https://aframe.io/releases/0.3.0/aframe.min.js"></script>
    <script src="https://rawgit.com/ngokevin/kframe/master/dist/kframe.min.js"></script>
    <script src="https://rawgit.com/ngokevin/kframe/master/components/reverse-look-controls/index.js"></script>
  </head>
  <body>
    <a-scene>
      <a-assets>
        <img id="equi1" crossorigin  src="https://rawgit.com/aframevr/aframe/master/examples/boilerplate/panorama/puydesancy.jpg" preload="auto">
      </a-assets>
        <!-- MAIN CAMERA -->
        <a-entity id="mainCam" camera="userHeight: 1.6" reverse-look-controls wasd-controls></a-entity>

        <!-- CMAERA FOR TEXTURE (?) -->
        <a-entity id="cubecamera" camera near="0.1" far="5000" fov="512"></a-entity>

        <!-- SKY DOME TO REFLECT ON BOX -->
        <a-sky src="#equi1"></sky>

        <!-- MAKE THIS BOX CHROME -->
        <a-box id="theCube" camera-envmap-material="#cubecamera" color="tomato" depth="2" height="4" width="5" position="0 0 -3" materials=""></a-box> 
        <!-- THIS BOX IS MOVING AND SHOULD REFLECT ON THE CHROME BOX -->
        <a-box id="reflector" color="red" position="0 0 20">
          <a-animation attribute="rotation" dur="10000" fill="forwards" to="0 360 0" repeat="indefinite"></a-animation>
        </a-box>
    </a-scene>
  </body>

 <script>

AFRAME.registerComponent('camera-envmap-material', {
  dependencies: ['material'],

  // Selector type so we do `<a-entity camera-envmap-material="#cubecamera">`.
  schema: {
    type: 'selector'
  },

  init: function () {
    var cameraEl = this.data;
    var material = this.el.getObject3D('mesh').material;

    // Set envmap on existing material.
    // This assumes you have a CubeCamera component that does `setObject3D('cube-camera', new THREE.CubeCamera)`.

    material.envMap = cameraEl.getObject3D('camera').renderTarget;
    material.needsUpdate = true;
  }
});

  </script>
</html>

ОБНОВЛЕНИЕ №2

После рекомендаций от @ngokevin вот где я нахожусь, однако я не могу обновить компонент. Кажется, проблема заключается в передаче переменных в tick:function(), я продолжаю получать Uncaught TypeError: Cannot read property 'renderTarget' of undefined

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Chrome box</title>
    <meta name="description" content="Physically-Based Materials - A-Frame">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <script src="https://aframe.io/releases/0.3.0/aframe.min.js"></script>
    <script src="https://rawgit.com/ngokevin/kframe/master/dist/kframe.min.js"></script>
    <script src="https://rawgit.com/ngokevin/kframe/master/components/reverse-look-controls/index.js"></script>
  </head>
  <body>
<a-scene>
      <a-assets>
        <img id="equi1" crossorigin src="https://rawgit.com/aframevr/aframe/master/examples/boilerplate/panorama/puydesancy.jpg" preload="auto">
      </a-assets>
        <!-- MAIN CAMERA -->
        <a-entity id="mainCam" camera="userHeight: 1.6" reverse-look-controls wasd-controls></a-entity>

        <!-- SKY DOME TO REFLECT ON BOX -->
        <a-sky src="#equi1"></sky>

        <!-- MAKE THIS BOX CHROME -->
        <a-box id="theCube" camera-envmap-material depth="2" height="4" width="5" position="0 0 -3" metalness="1">
        </a-box>
        <!-- MAKE THIS ROTATE AND REFLECT ON CHROME --> 
        <a-box id="reflector" color="red" position="0 0 10">
        <a-animation attribute="rotation" dur="10000" fill="forwards" to="0 360 0" repeat="indefinite"></a-animation>
        </a-box>
     </a-scene>
  </body>

 <script>
AFRAME.registerComponent('camera-envmap-material', {
  dependencies: ['material'],

  init: function(data) {
    this.cameraEl = this.data;
    this.material = this.el.getObject3D('mesh').material;
    this.theElement = this.el;
    this.theScene = this.theElement.sceneEl;

    if (!this.theScene.renderStarted) {
      this.theScene.addEventListener('renderstart', this.init.bind(this));
      return;
    }
    this.cubeCamera = new THREE.CubeCamera( 0.1, 5000, 512);
    this.cubeCamera.position.set(5, 2, 4);
    this.cubeCamera.updateCubeMap( this.theScene.renderer, this.theScene.object3D );
    this.theElement.setObject3D('cube-camera', this.cubeCamera);
    this.material.envMap = this.cubeCamera.renderTarget;
    this.material.needsUpdate = true;
  },
  tick:function(){
    this.material.envMap = this.cubeCamera.renderTarget;
    this.material.needsUpdate = true;
  }
});
  </script>
</html>

person Jon Herrera    schedule 01.10.2016    source источник


Ответы (1)


Компонент мог бы устанавливать свойства существующего материала. Шейдер больше предназначен для регистрации шейдеров/материалов, но в A-Frame уже есть базовые/стандартные материалы. И вам нужно свойство selector в схеме.:

AFRAME.registerComponent('camera-envmap-material', {
  dependencies: ['material'],

  // Selector type so we do `<a-entity camera-envmap-material="#cubecamera">`.
  schema: {
    type: 'selector'
  },

  init: function () {
    var cameraEl = this.data;
    var material = this.el.getObject3D('mesh').material;

    // Set envmap on existing material.
    // This assumes you have a CubeCamera component that does `setObject3D('cube-camera', new THREE.CubeCamera)`.

    material.envMap = cameraEl.getObject3D('camera').renderTarget;
    material.needsUpdate = true;
  }
});

Обновлять

AFRAME.registerComponent('camera-envmap-material', {
  dependencies: ['material'],

  init: function () {
    var cameraEl = this.data;
    var material = this.el.getObject3D('mesh').material;

    if (!this.el.sceneEl.renderStarted) {
      this.el.sceneEl.addEventListener('renderstart', this.init.bind(this));
      return;
    }

      var cubeCamera = new THREE.CubeCamera( 1, 100000, 128 );
    cubeCamera.position.set(5, 2, 4);
    cubeCamera.updateCubeMap( this.el.sceneEl.renderer, this.el.sceneEl.object3D );
    this.el.setObject3D('cube-camera', cubeCamera);
    material.envMap = cubeCamera.renderTarget;
    material.needsUpdate = true;
  }
});
person ngokevin    schedule 01.10.2016
comment
Спасибо @ngokevin, я отредактировал свой исходный пост, чтобы показать полный код. Я не получаю никаких ошибок в консоли, но коробка по-прежнему не текстурируется хромом. Любые мысли о том, чего здесь не хватает. - person Jon Herrera; 01.10.2016
comment
похоже, здесь я ошибаюсь: предполагается, что у вас есть компонент CubeCamera, который выполняет setObject3D('cube-camera', new THREE.CubeCamera). - person Jon Herrera; 01.10.2016
comment
Да, это более или менее структура/скелет кода, вам нужно будет заполнить некоторые вещи, например, создать компонент CubeCamera (или создать его в компоненте, если сейчас это слишком много). - person ngokevin; 02.10.2016
comment
codepen.io? codepen.io/jonalex/pen/ORxjJk Я добавил var camera = new THREE.CubeCamera ( 1, 100000, 128 ); в init(), но я не уверен, как добавить его на сцену и использовать в предоставленном вами компоненте. - person Jon Herrera; 02.10.2016
comment
codepen.io/team/mozvr/pen/bwoAAz У меня вроде получилось. Вы добавляете к сцене с el.setObject3D. Также пришлось ждать рендерера. - person ngokevin; 02.10.2016
comment
Потрясающий! Я немного подправил, чтобы получить большую отражательную способность: - металличность = 1, шероховатость = 0 - камера = новый THREE.CubeCamera( 0.1, 5000, 512 ); - Я также добавил ‹a-animation attribute=rotation dur=15000 from=0 0 0 to=0 360 0 Repeat=indefinite›‹/a-animation› просто чтобы убедиться, что отражение обновляется... и тут я заметил таинственный черный квадрат отражается. Не имеет большого значения для моего конкретного использования, но это странно. Спасибо еще раз! - person Jon Herrera; 02.10.2016
comment
поэтому кажется, что инструкции по обновлению срабатывают только в том случае, если перемещается сам объект ... а не в случае движения чего-либо в окружающей среде. Какие-нибудь мысли? - person Jon Herrera; 02.10.2016
comment
Вы можете использовать событие или, если вам нужно непрерывное обновление кубической карты, использовать обработчик Component.tick или отделить камеру кубической карты от материала. - person ngokevin; 02.10.2016
comment
просто заменил init: на tick: и обновил. @ngokevin - person Jon Herrera; 03.10.2016
comment
заменил init: на tick: и заставил его работать, но, похоже, он использует ресурсы как сумасшедший. Я попытался определить переменные в init: , а затем просто выполнить/обновить по тике: , но переменные были недоступны, поэтому я отправил все на галочку, и она работает ... но это не похоже на ее правильный синтаксис или использование. @ngokevin - person Jon Herrera; 03.10.2016
comment
Ага. подтвержденный. Этот придурок несколько раз сбивал мой браузер. - person Jon Herrera; 03.10.2016
comment
Не ставьте все в галочки. Только инит. Вы можете определить вещи на this. - person ngokevin; 03.10.2016
comment
Я вернулся к этому и до сих пор не могу обновить компонент. Я поставил все ссылки на переменные на this. и переместил последние 2 строки вашего кода на tick:function(){} , однако я все еще получаю Uncaught TypeError: Cannot read property 'renderTarget' of undefined - person Jon Herrera; 08.10.2016
comment
Я даже пробовал этот подход aframe.io/docs/master/components /material.html#example-1, где this.update(data)вызывается в конце init. Нет игральных костей. - person Jon Herrera; 08.10.2016
comment
вызов подкрепления. @donmccurdy Единственное, чего не хватает в этом решении, — это реализовать обновление через обновление. Любые мысли о том, как получить значения из init() либо через тик(), либо через update()? - person Jon Herrera; 09.10.2016