Home Reference Source Repository

js/base/Renderer.js

'use strict'

import Vector4 from './Vector4'
import Material from './Material'
import ShaderBank from './ShaderBank'

/**
 * Renderer class
 * @access public
 */
export default class Renderer {
  /**
   * constructor
   * @access public
   * @param {WebGLRenderingContext} gl -
   * @param {Camera} camera -
   * @constructor
   */
  constructor(gl, camera) {
    this._vertexShader = null
    this._fragmentShader = null
    this._programObject = null
    this._light = null

    this._clearColor = null
    this._clearDepth = 1.0
    this._clearStencil = 0
    this._stencilMask = 0xff

    this._stencilOn = false
  
    this._gl = gl
    this._camera = camera

    // initialize vertex shader
    this._vertexShader = ShaderBank.getShaderOfContext(this._vertexShaderName, gl)

    // initialize fragment shader
    this._fragmentShader = ShaderBank.getShaderOfContext(this._fragmentShaderName, gl)

    this._programObject = this._gl.createProgram()
    this._gl.attachShader(this._programObject, this._vertexShader.getShader())
    this._gl.attachShader(this._programObject, this._fragmentShader.getShader())

    // bind variables
    this._vertexShader.bindAttribute(this._programObject)

    // link program object
    this._gl.linkProgram(this._programObject)
    if(!this._gl.getProgramParameter(this._programObject, this._gl.LINK_STATUS)){
      throw new Error(this._gl.getProgramInfoLog(this._programObject))
    }
    this._vertexShader.bindAttribute2(this._programObject)

    this._gl.useProgram(this._programObject)

    this._clearColor = new Vector4(1.0, 1.0, 1.0, 1.0)
    this._gl.clearColor(this._clearColor.x, this._clearColor.y, this._clearColor.z, this._clearColor.w)
    this._gl.clearDepth(this._clearDepth)
    this._gl.clearStencil(this._clearStencil)

    this._gl.enable(gl.DEPTH_TEST)
    this._gl.enable(gl.BLEND)
    this._gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
    this._gl.enable(gl.CULL_FACE)
    this._gl.cullFace(gl.FRONT)
    //this._gl.disable(gl.STENCIL_TEST)
    this.disableStencil()
    this._gl.stencilMask(this._stencilMask)

    this._gl.checkGLError('Renderer.initialize')
  }

  get _vertexShaderName() {
    return ''
  }

  get _fragmentShaderName() {
    return ''
  }

  setCamera(camera) {
    this._camera = camera
  }

  setLight(light) {
    this._light = light
  }

  setClearColor(r, g, b, a) {
    this._clearColor.setValue(r, g, b, a)
    this._gl.clearColor(this._clearColor.x, this._clearColor.y, this._clearColor.z, this._clearColor.w)
  }

  render(dhObject) {
    // FIXME: いろいろ
    this._gl.checkGLError('before render')

    if(this._gl._program !== this._programObject){
      this._gl.useProgram(this._programObject)
      this._gl._program = this._programObject
    }
    this._gl.checkGLError('gl.useProgram')

    // FIXME: culling setting
    this._gl.disable(this._gl.CULL_FACE)

    // camera setting
    this._gl.uniformMatrix4fv(this._gl.getUniformLocation(this._programObject, 'cameraProjectionViewMatrix'), false, this._camera.getProjectionViewMatrix())
    this._gl.uniform3fv(this._gl.getUniformLocation(this._programObject, 'cameraPosition'), this._camera.getPosition())
    this._gl.checkGLError('render: cameraPosition')

    // FIXME:Lightは毎回転送する必要ない?
    // FIXME:Locationのキャッシュ
    this._vertexShader.setLightData(this._light)

    this._gl.bindBuffer(this._gl.ARRAY_BUFFER, dhObject._model.vertexBuffer)
    this._vertexShader.bufferDynamicVertexData(dhObject)
    this._vertexShader.setAttribPointer()

    // renderGroup毎に描画
    for(let i=0; i<dhObject._model.renderGroupArray.length; i++){
      const renderGroup = dhObject._model.renderGroupArray[i]
      const material = renderGroup.material

      this._gl.bindBuffer(this._gl.ELEMENT_ARRAY_BUFFER, renderGroup.indexBuffer)

      this._gl.checkGLError('gl.bindBuffer(ELEMENT_ARRAY_BUFFER)')

      if(dhObject._reflectionMode){
        if(!renderGroup.reflectionMaterial){
          const m = new Material(material)
          const r = 0.5
          m.ambient.x *= r
          m.ambient.y *= r
          m.ambient.z *= r
          m.diffuse.x *= r
          m.diffuse.y *= r
          m.diffuse.z *= r
          m.specular.x *= r
          m.specular.y *= r
          m.specular.z *= r
          m.emission.x *= r
          m.emission.y *= r
          m.emission.z *= r
          m.alpha = r
          renderGroup.reflectionMaterial = m
        }
        this._vertexShader.setMaterialData(renderGroup.reflectionMaterial)
      }else{
        // FIXME: clipPlane無効化
        const zero = new Vector4(0, 0, 0, 1)
        this._vertexShader.setClipPlane(zero)

        this._vertexShader.setMaterialData(material)
      }

      // bone行列
      let boneArr = []
      for(let j=0; j<renderGroup.boneArray.length; j++){
        boneArr = boneArr.concat(renderGroup.boneArray[j].inflMatrix.getArray())
      }

      this._gl.uniformMatrix4fv(
        this._gl.getUniformLocation(this._programObject, 'effBones'),
        false, 
        new Float32Array(boneArr)
      )
      this._gl.checkGLError('boneArr')

      // TODO: toon、edgeの設定

      if(material.texture){
        // FIXME
        let texture = material.texture
        if(dhObject._dynamicTexture){
          texture = dhObject._dynamicTexture
        }

        this._gl.checkGLError('before texture')
        this._gl.uniform1i(this._gl.getUniformLocation(this._programObject, 'enableTexture'), 1)
        this._gl.checkGLError('gl.uniform1i, enableTexture=1')
        this._gl.activeTexture(this._gl.TEXTURE0)
        this._gl.checkGLError('gl.activeTexture')
        //this._gl.bindTexture(this._gl.TEXTURE_2D, material.texture)
        this._gl.bindTexture(this._gl.TEXTURE_2D, texture)
        this._gl.checkGLError('bindTexture')
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR)
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR)
        if(material.texture_repeat){
          this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.REPEAT)
          this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.REPEAT)
        }else{
          this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE)
          this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE)
        }
        this._gl.checkGLError('texParameteri')
      }else{
        this._gl.uniform1i(this._gl.getUniformLocation(this._programObject, 'enableTexture'), 0)
        this._gl.checkGLError('gl.uniform1i, enableTexture=0')
        // FIXME: error
        //this._gl.disable(this._gl.TEXTURE_2D)
        //this._gl.checkGLError('gl.disable')
      }
      this._gl.drawElements(this._gl.TRIANGLES, renderGroup.indices.length, this._gl.UNSIGNED_SHORT, 0)

      this._gl.checkGLError('gl.drawElements: length:' + renderGroup.indices.length)
    }
  }

  renderMirror(dhObject, reflectionObjectArray) {
    if(!dhObject._mirror)
      return

    const gl = this._gl

    // FIXME: save gl settings

    // set 1 to stencil buffer
    //gl.enable(gl.STENCIL_TEST)
    this.enableStencil()
    gl.colorMask(0, 0, 0, 0)
    /*
    gl.depthFunc(gl.LESS)
    gl.depthRange(0,1)
    gl.enable(gl.STENCIL_TEST)
    gl.stencilFunc(gl.ALWAYS, 1, 1)
    gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE)
    gl.colorMask(0,0,0,0)
    this.render(dhObject)
    */

    // reset (set 1) depth buffer
    gl.depthRange(1, 1)
    gl.depthFunc(gl.ALWAYS)
    gl.stencilFunc(gl.EQUAL, 16, this._stencilMask)
    gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
    this.render(dhObject)

    // FIXME: clip plane
    // gl.clipPlane(gl.CLIP_PLANE0, plane)
    // FIXME: set clipPlane
    const plane = new Vector4(0, 1, 0, 0)
    this._vertexShader.setClipPlane(plane)

    // draw reflection objects
    //gl.stencilFunc(gl.LESS, 1, this._stencilMask)
    gl.stencilFunc(gl.LEQUAL, 4, this._stencilMask)
    gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE)
    gl.depthFunc(gl.LESS)
    gl.depthRange(0, 1)
    gl.enable(gl.BLEND)
    //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
    gl.blendFunc(gl.ONE, gl.ZERO)
    //gl.depthFunc(gl.GREATER)
    gl.colorMask(1, 1, 1, 1)
    gl.cullFace(gl.BACK)

    // FIXME: calc mirroring matrix
    this._camera.scale(1, -1, 1)
    const myObj = this
    reflectionObjectArray.forEach( (obj) => {
      if(obj === myObj)
        return

      obj.setReflectionMode(true)
      obj.render()
      obj.setReflectionMode(false)
    })
    this._camera.scale(1, -1, 1)

    const zero = new Vector4(0, 0, 0, 1)
    this._vertexShader.setClipPlane(zero)

    // draw mirror
    gl.stencilFunc(gl.EQUAL, 4, this._stencilMask)
    gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP)
    gl.cullFace(gl.FRONT)
    gl.enable(gl.BLEND)
    gl.blendFunc(gl.ONE_MINUS_DST_ALPHA, gl.DST_ALPHA)
    //gl.blendFunc(gl.ONE, gl.ONE)
    this.render(dhObject)

    // FIXME: restore setting
    this.disableStencil()
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
  }

  getContext() {
    return this._gl
  }

  getVertexData(dhObject) {
    return this._vertexShader.getVertexData(dhObject)
  }
  getDynamicVertexData(dhObject){
    return this._vertexShader.getDynamicVertexData(dhObject)
  }

  enableStencil() {
    if(this._stencilOn)
      return

    this._gl.enable(this._gl.STENCIL_TEST)
    this._stencilOn = true
  }

  disableStencil() {
    if(!this._stencilOn)
      return

    this._gl.disable(this._gl.STENCIL_TEST)
    this._stencilOn = false
  }
}