如何使用ogl和透视相机进行适当的平面反射?

问题描述 投票:0回答:1

我正在尝试使用 ogl 在平面上反射 WebGL 场景,就像镜子一样。我对 Three.js 反射器 进行了简单的实现,但我得到的只是一个扭曲的图像,与实际反射的样子不匹配。

通过从原始相机的虚拟反射角度渲染场景,调整虚拟相机的投影矩阵以仅渲染投影到平面上的内容,然后映射生成的纹理,我了解了反射器的工作原理到飞机上。但我并没有完全理解所有涉及的计算(特别是我描述的最后两个步骤)。

我所做的只是逐行复制 Three.js 反射器并更改函数名称或引用,但我不知道我错在哪里或错过了什么。我真的很感激任何帮助!

// 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.);
}

以下是 Three.js 中提供的一些数学函数,我必须自己实现:

// 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
}
javascript three.js 3d webgl ogl
1个回答
0
投票

Mr.Coder 的评论让我想起了这篇文章,让我觉得我应该发布我两年前提出的解决方案......我想它可以帮助一些人。

这是平面反射器的一个工作示例,这是我以前写的代码,所以它不是超级干净,需要一些重构以及打字稿重写,但如果您遇到困难,这是一个很好的起点关于这个问题,就像我发表这篇文章时一样:

import {
  RenderTarget,
  Camera,
  Plane,
  Program,
  Mesh,
  Vec2,
  Vec3,
  Vec4,
  Mat3,
  Mat4
} from 'ogl'

import { transformMat3, normalize } from 'ogl/src/math/functions/Vec3Func'
import { scale as scaleVec4 } from 'ogl/src/math/functions/Vec4Func'

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

    super(gl, {
      geometry: new Plane(gl, { width, height }),
      program: new Program(gl, {
        uniforms: {
          reflectionMap: {
            value: renderTarget.texture
          },
          reflectionMatrix: {
            value: textureMatrix
          }
        },
        vertex: `#version 300 es
          precision highp float;

          uniform mat4 projectionMatrix;
          uniform mat4 modelViewMatrix;
          uniform mat4 reflectionMatrix;

          in vec3 position;
          in vec2 uv;

          out vec4 vReflectionUv;

          void main() {
            vReflectionUv = reflectionMatrix * vec4(position, 1.0);
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
          }
        `,
        fragment: `#version 300 es
          precision highp float;

          uniform sampler2D reflectionMap;

          in vec4 vReflectionUv;

          out vec4 FragColor;

          void main() {
            vec4 reflection = textureProj(reflectionMap, vReflectionUv);
            FragColor = reflection + vec4(0.1, 0.1, 0.1, 0.);
          }
        `
      })
    })

    this.camera = camera
    this.clipBias = clipBias
    this.renderTarget = renderTarget
    this.textureMatrix = textureMatrix
    this.resolution = new Vec2(renderWidth, renderHeight)
    this.virtualCamera = new Camera(gl)
    this.normal = new Vec3()
    this.view = new Vec3()
    this.plane = new Vec4()
    this.q = new Vec4()
    this.referencePoint = new Vec3()
    this.lookAtPosition = new Vec3()
    this.targetPosition = new Vec3()
    this.normalMatrix = new Mat3()
    this.rotationMatrix = new Mat4()

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

  update() {
    this.camera.updateMatrixWorld()
    this.updateMatrixWorld()

    extractRotationMatrix(this.worldMatrix, this.rotationMatrix)

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

    this.view.sub(this.position, this.camera.worldPosition)

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

    const distance = this.view.len()

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

    this.view.add(this.position)

    extractRotationMatrix(this.camera.worldMatrix, this.rotationMatrix)

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

    reflect(
      this.targetPosition.sub(this.position, this.lookAtPosition),
      this.normal
    )
      .negate()
      .add(this.position)

    this.virtualCamera.position.copy(this.view)

    reflect(
      this.virtualCamera.up.set(0, 1, 0).applyMatrix4(this.rotationMatrix),
      this.normal
    )

    this.virtualCamera.lookAt(this.targetPosition)
    this.virtualCamera.near = this.camera.near
    this.virtualCamera.far = this.camera.far
    this.virtualCamera.fov = this.camera.fov

    this.virtualCamera.updateMatrixWorld(true)
    this.virtualCamera.projectionMatrix.copy(this.camera.projectionMatrix)

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

    this.textureMatrix
      .multiply(this.virtualCamera.projectionMatrix)
      .multiply(this.virtualCamera.viewMatrix)
      .multiply(this.worldMatrix)

    this.plane
      .set(
        this.normal.x,
        this.normal.y,
        this.normal.z,
        -this.position.dot(this.normal)
      )

    applyPlaneMatrix4(
      this.plane,
      this.virtualCamera.viewMatrix,
      this.normalMatrix,
      this.referencePoint
    )

    const projectionMatrix = this.virtualCamera.projectionMatrix

    this.q.x = (Math.sign(this.plane.x) + projectionMatrix[8]) / projectionMatrix[0]
    this.q.y = (Math.sign(this.plane.y) + projectionMatrix[9]) / projectionMatrix[5]
    this.q.z = -1
    this.q.w = (1 + projectionMatrix[10]) / projectionMatrix[14]

    scaleVec4(this.plane, this.plane, 2 / this.plane.dot(this.q))

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

    this.visible = false
    this.gl.renderer.render(this.renderParameters)
    this.visible = true
  }

  setSize(width, height) {
    this.resolution.set(width, height)
    this.renderTarget.setSize(width, height)
  }

  remove() {
    this.gl.deleteFramebuffer(this.renderTarget.buffer)

    for (const texture of this.renderTarget.textures) {
      this.gl.deleteTexture(texture)
    }
  }
}

function extractRotationMatrix(matrix, output) {
  const sX = 1 / Math.hypot(matrix[0], matrix[1], matrix[2])
  const sY = 1 / Math.hypot(matrix[4], matrix[5], matrix[6])
  const sZ = 1 / Math.hypot(matrix[8], matrix[9], matrix[10])

  output[0] = matrix[0] * sX
  output[1] = matrix[1] * sX
  output[2] = matrix[2] * sX
  output[3] = 0

  output[4] = matrix[4] * sY
  output[5] = matrix[5] * sY
  output[6] = matrix[6] * sY
  output[7] = 0

  output[8] = matrix[8] * sZ
  output[9] = matrix[9] * sZ
  output[10] = matrix[10] * sZ
  output[11] = 0

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

  return output
}

function reflect(vector, normal, output = vector) {
  const f = 2 * vector.dot(normal)
  output[0] -= normal[0] * f
  output[1] -= normal[1] * f
  output[2] -= normal[2] * f

  return output
}

function applyPlaneMatrix4(
  plane,
  matrix,
  normalMatrix,
  referencePoint,
  output = plane
) {
  normalMatrix.getNormalMatrix(matrix)
  referencePoint.copy(plane).scale(-plane.w).applyMatrix4(matrix)
  normalize(plane, transformMat3(plane, plane, normalMatrix))
  plane.w = -referencePoint.dot(plane)

  return output;
}

主要问题是纹理矩阵的初始化。在旧代码中,它是这样制作的:

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.set(
  0.5, 0.0, 0.0, 0.0,
  0.0, 0.5, 0.0, 0.0,
  0.0, 0.0, 0.5, 0.0,
  0.5, 0.5, 0.5, 1.0
)

我犯了这个错误,因为与 ogl 相比,Threejs 中的列和行颠倒了!

© www.soinside.com 2019 - 2024. All rights reserved.