Newer
Older
reroad-test / 2020-ryusei / aframe-master / src / core / shader.js
@ryusei ryusei on 22 Oct 2020 5 KB パノラマ表示
var schema = require('./schema');

var processSchema = schema.process;
var shaders = module.exports.shaders = {};  // Keep track of registered shaders.
var shaderNames = module.exports.shaderNames = [];  // Keep track of the names of registered shaders.
var THREE = require('../lib/three');
var utils = require('../utils');

// A-Frame properties to three.js uniform types.
var propertyToThreeMapping = {
  array: 'v3',
  color: 'v3',
  int: 'i',
  number: 'f',
  map: 't',
  time: 'f',
  vec2: 'v2',
  vec3: 'v3',
  vec4: 'v4'
};

/**
 * Shader class definition.
 *
 * Shaders extend the material component API so you can create your own library
 * of customized materials
 *
 */
var Shader = module.exports.Shader = function () {};

Shader.prototype = {
  /**
   * Contains the type schema and defaults for the data values.
   * Data is coerced into the types of the values of the defaults.
   */
  schema: {},

  vertexShader:
    'void main() {' +
      'gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);' +
    '}',

  fragmentShader:
    'void main() {' +
      'gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);' +
    '}',

  /**
   * Init handler. Similar to attachedCallback.
   * Called during shader initialization and is only run once.
   */
  init: function (data) {
    this.attributes = this.initVariables(data, 'attribute');
    this.uniforms = this.initVariables(data, 'uniform');
    this.material = new (this.raw ? THREE.RawShaderMaterial : THREE.ShaderMaterial)({
      // attributes: this.attributes,
      uniforms: this.uniforms,
      vertexShader: this.vertexShader,
      fragmentShader: this.fragmentShader
    });
    return this.material;
  },

  initVariables: function (data, type) {
    var key;
    var schema = this.schema;
    var variables = {};
    var varType;

    for (key in schema) {
      if (schema[key].is !== type) { continue; }
      varType = propertyToThreeMapping[schema[key].type];
      variables[key] = {
        type: varType,
        value: undefined  // Let updateVariables handle setting these.
      };
    }
    return variables;
  },

  /**
   * Update handler. Similar to attributeChangedCallback.
   * Called whenever the associated material data changes.
   *
   * @param {object} data - New material data.
   */
  update: function (data) {
    this.updateVariables(data, 'attribute');
    this.updateVariables(data, 'uniform');
  },

  updateVariables: function (data, type) {
    var key;
    var materialKey;
    var schema = this.schema;
    var variables;

    variables = type === 'uniform' ? this.uniforms : this.attributes;
    for (key in data) {
      if (!schema[key] || schema[key].is !== type) { continue; }

      if (schema[key].type === 'map') {
        // If data unchanged, get out early.
        if (!variables[key] || variables[key].value === data[key]) { continue; }

        // Special handling is needed for textures.
        materialKey = '_texture_' + key;

        // We can't actually set the variable correctly until we've loaded the texture.
        this.setMapOnTextureLoad(variables, key, materialKey);

        // Kick off the texture update now that handler is added.
        utils.material.updateMapMaterialFromData(materialKey, key, this, data);
        continue;
      }
      variables[key].value = this.parseValue(schema[key].type, data[key]);
      variables[key].needsUpdate = true;
    }
  },

  parseValue: function (type, value) {
    var color;
    switch (type) {
      case 'vec2': {
        return new THREE.Vector2(value.x, value.y);
      }
      case 'vec3': {
        return new THREE.Vector3(value.x, value.y, value.z);
      }
      case 'vec4': {
        return new THREE.Vector4(value.x, value.y, value.z, value.w);
      }
      case 'color': {
        color = new THREE.Color(value);
        return new THREE.Vector3(color.r, color.g, color.b);
      }
      case 'map': {
        return THREE.ImageUtils.loadTexture(value);
      }
      default: {
        return value;
      }
    }
  },

  setMapOnTextureLoad: function (variables, key, materialKey) {
    var self = this;
    this.el.addEventListener('materialtextureloaded', function () {
      variables[key].value = self.material[materialKey];
      variables[key].needsUpdate = true;
    });
  }
};

/**
 * Registers a shader to A-Frame.
 *
 * @param {string} name - shader name.
 * @param {object} definition - shader property and methods.
 * @returns {object} Shader.
 */
module.exports.registerShader = function (name, definition) {
  var NewShader;
  var proto = {};

  // Format definition object to prototype object.
  Object.keys(definition).forEach(function (key) {
    proto[key] = {
      value: definition[key],
      writable: true
    };
  });

  if (shaders[name]) {
    throw new Error('The shader ' + name + ' has been already registered');
  }
  NewShader = function () { Shader.call(this); };
  NewShader.prototype = Object.create(Shader.prototype, proto);
  NewShader.prototype.name = name;
  NewShader.prototype.constructor = NewShader;
  shaders[name] = {
    Shader: NewShader,
    schema: processSchema(NewShader.prototype.schema)
  };
  shaderNames.push(name);
  return NewShader;
};