2

I'm trying to reflect a WebGL scene in a plane with ogl, as a mirror would do. I made a naive implementation of the three.js reflector but all I got is a distorted image that doesn't match what an actual reflection would look like.

enter image description here

I get the general idea of how the reflector works by rendering the scene from the virtual reflected point of view of the original camera, adjusting the projection matrix of the virtual camera to render only what projects onto the plane, and later mapping the resulting texture to the plane. But I don't have a complete understanding of all the involved computations (particularly for the two last steps I described).

All I did is copy line by line the three.js reflector and change function names or references, but I can't tell where I'm wrong or what I missed. I would really appreciate any help !

// objects/Reflector.js

import { Transform, Mesh, Plane, Program, RenderTarget, Camera, Vec3, Vec4, Quat, Mat4 } from 'ogl'
import { dot } from 'ogl/src/math/functions/Vec4Func'

import vertex from '../shaders/reflector.vert'
import fragment from '../shaders/reflector.frag'

import { setFromNormalAndCoplanarPoint, transformMat4 } from '../math/PlaneFunc'
import { getRotationMatrix } from '../math/Mat4Func'
import { reflect } from '../math/Vec3Func'

export class Reflector extends Mesh {
  constructor(gl, {
    scene,
    camera,
    renderSize = 512,
    clipBias = 0
  }) {
    const renderTarget = new RenderTarget(gl, {
      width: renderSize,
      height: renderSize
    })

    super(gl, {
      geometry: new Plane(gl),
      program: new Program(gl, {
        vertex,
        fragment,
        uniforms: {
          textureMatrix: { value: new Mat4() },
          diffuseMap: { value: renderTarget.texture }
        }
      })
    })

    this.viewCamera = camera
    this.clipBias = clipBias
    this.reflectionCamera = new Camera(gl)
    this.reflectionPlane = new Vec4()
    this.reflectionWorldMatrixInverse = new Mat4()
    this.reflectionQuaternion = new Quat()
    this.worldPosition = new Vec3()
    this.normal = new Vec3()
    this.view = new Vec3()
    this.lookAtPosition = new Vec3()
    this.rotationMatrix = new Mat4()
    this.target = new Vec3()
    this.textureMatrix = this.program.uniforms.textureMatrix.value

    this.renderParameters = {
      scene,
      target: renderTarget,
      camera: this.reflectionCamera
    }
  }

  update() {
    this.worldMatrix.getTranslation(this.worldPosition)
    getRotationMatrix(this.rotationMatrix, this.worldMatrix)

    this.normal
      .set(0, 0, 1)
      .applyMatrix4(this.rotationMatrix)

    this.view.sub(this.worldPosition, this.viewCamera.worldPosition)

    if (this.view.dot(this.normal) > 0) {
      return
    }

    reflect(this.view, this.view, this.normal)
      .negate()
      .add(this.worldPosition)

    getRotationMatrix(this.rotationMatrix, this.viewCamera.worldMatrix)

    this.lookAtPosition.set(0, 0, -1)
    this.lookAtPosition.applyMatrix4(this.rotationMatrix)
    this.lookAtPosition.add(this.viewCamera.worldPosition)

    this.target.sub(this.worldPosition, this.lookAtPosition)
    reflect(this.target, this.target, this.normal)
      .negate()
      .add(this.worldPosition)

    this.reflectionCamera.position.copy(this.view)

    this.reflectionCamera.up
      .set(0, 1, 0)
      .applyMatrix4(this.rotationMatrix)

    reflect(this.reflectionCamera.up, this.reflectionCamera.up, this.normal)
    this.reflectionCamera.lookAt(this.target)

    this.reflectionCamera.perspective({
      near: this.viewCamera.near,
      far: this.viewCamera.far,
      fov: this.viewCamera.fov,
      aspect: 1
    })

    this.reflectionCamera.worldMatrixNeedsUpdate = true
    this.reflectionCamera.updateMatrixWorld()
    this.reflectionWorldMatrixInverse.inverse(this.reflectionCamera.worldMatrix)
    this.reflectionCamera.updateFrustum()

    this.textureMatrix.set(
      0.5, 0.0, 0.0, 0.5,
      0.0, 0.5, 0.0, 0.5,
      0.0, 0.0, 0.5, 0.5,
      0.0, 0.0, 0.0, 1.0
    )

    this.textureMatrix
      .multiply(this.reflectionCamera.projectionMatrix)
      .multiply(this.reflectionWorldMatrixInverse)
      .multiply(this.worldMatrix)

    setFromNormalAndCoplanarPoint(this.reflectionPlane, this.normal, this.worldPosition)
    transformMat4(this.reflectionPlane, this.reflectionPlane, this.reflectionWorldMatrixInverse)

    const projectionMatrix = this.reflectionCamera.projectionMatrix

    this.reflectionQuaternion.set(
      (Math.sign(this.reflectionPlane.x) + projectionMatrix[8]) / projectionMatrix[0],
      (Math.sign(this.reflectionPlane.y) + projectionMatrix[9]) / projectionMatrix[5],
      -1,
      (1 + projectionMatrix[10]) / projectionMatrix[14]
    )

    const f = 2 / dot(this.reflectionPlane, this.reflectionQuaternion)

    this.reflectionPlane.x *= f
    this.reflectionPlane.y *= f
    this.reflectionPlane.z *= f
    this.reflectionPlane.w *= f

    projectionMatrix[2] = this.reflectionPlane.x
    projectionMatrix[6] = this.reflectionPlane.y
    projectionMatrix[10] = this.reflectionPlane.z + 1 - this.clipBias
    projectionMatrix[14] = this.reflectionPlane.w

    this.helper.position.copy(this.reflectionCamera.position)
    this.helper.rotation.copy(this.reflectionCamera.rotation)

    this.visible = false

    this.gl.renderer.render(this.renderParameters)

    this.visible = true
  }
}
// shaders/reflector.vert

precision highp float;

attribute vec3 position;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 textureMatrix;

varying vec4 vUv;

void main() {
  vUv = textureMatrix * vec4(position, 1.);

  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
}

// shaders/reflector.frag

precision highp float;

uniform sampler2D diffuseMap;

varying vec4 vUv;

void main() {
  vec3 diffuse = texture2DProj(diffuseMap, vUv).rgb;

  gl_FragColor = vec4(diffuse, 1.);
}

Here are some math functions available in three.js that I had to implement myself:

// math/Mat4Func.js

import { length } from 'ogl/src/math/functions/Vec3Func'

export function getRotationMatrix(out, m) {
  const sX = 1 / length(m.slice(0, 3))
  const sY = 1 / length(m.slice(4, 7))
  const sZ = 1 / length(m.slice(8, 11))

  out[0] = m[0] * sX
  out[1] = m[1] * sX
  out[2] = m[2] * sX
  out[3] = 0

  out[4] = m[4] * sY
  out[5] = m[5] * sY
  out[6] = m[6] * sY
  out[7] = 0

  out[8] = m[8] * sZ
  out[9] = m[9] * sZ
  out[10] = m[10] * sZ
  out[11] = 0

  out[12] = 0
  out[13] = 0
  out[14] = 0
  out[15] = 1

  return out
}
// math/PlaneFunc.js

import { normalFromMat4 } from 'ogl/src/math/functions/Mat3Func'

import {
  dot,
  normalize,
  transformMat4 as transformMat4Vec3,
  transformMat3 as transformMat3Vec3
} from 'ogl/src/math/functions/Vec3Func'

const normal = []
const normalMatrix = []
const coplanarPoint = []

export function transformMat4(out, p, m) {
  normalFromMat4(normalMatrix, m)
  getCoplanarPoint(coplanarPoint, p)
  transformMat4Vec3(coplanarPoint, coplanarPoint, m)
  transformMat3Vec3(normal, p, normalMatrix)
  normalize(normal, normal)
  setFromNormalAndCoplanarPoint(out, normal, coplanarPoint)

  return out
}

export function getCoplanarPoint(out, p) {
  out[0] = p[0] * -p[3]
  out[1] = p[1] * -p[3]
  out[2] = p[2] * -p[3]

  return out
}

export function setFromNormalAndCoplanarPoint(out, n, c) {
  out[0] = n[0]
  out[1] = n[1]
  out[2] = n[2]
  out[3] = -dot(c, n)

  return out
}

// math/Vec3Func.js

import { dot } from 'ogl/src/math/functions/Vec3Func'

export function reflect(out, a, b) {
  const f = 2 * dot(a, b)

  out[0] = a[0] - b[0] * f
  out[1] = a[1] - b[1] * f
  out[2] = a[2] - b[2] * f

  return out
}
Julien Dargelos
  • 356
  • 1
  • 12

0 Answers0