我正在尝试使用 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
}
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 中的列和行颠倒了!