mirror of
https://github.com/pissang/claygl.git
synced 2026-02-01 17:27:08 +00:00
wip(type): add type to pixel picking and vr
This commit is contained in:
parent
42a6df0852
commit
7effd48b50
@ -79,8 +79,8 @@ export {
|
||||
} from './shader/index';
|
||||
|
||||
// Picking
|
||||
|
||||
export { pick as pickByRay } from './picking/rayPicking';
|
||||
export { default as PixelPicking } from './picking/PixelPicking';
|
||||
|
||||
// GLTF Loader
|
||||
export { load as loadGLTF, parse as parseGLTF, parseBinary as parseGLB } from './loader/GLTF';
|
||||
@ -106,5 +106,10 @@ export { default as App3D } from './App3D';
|
||||
export { default as CompositorFullscreenQuadPass } from './composite/Pass';
|
||||
|
||||
// Deferred renderer
|
||||
|
||||
export { default as DeferredGBuffer } from './deferred/GBuffer';
|
||||
export { default as DeferredRenderer } from './deferred/Renderer';
|
||||
|
||||
// Particles
|
||||
export { default as ParticleEmitter } from './particle/Emitter';
|
||||
export { default as ParticleRenderable } from './particle/ParticleRenderable';
|
||||
export { default as ForceField } from './particle/ForceField';
|
||||
|
||||
@ -1,185 +1,173 @@
|
||||
// @ts-nocheck
|
||||
import Base from '../core/Base';
|
||||
import FrameBuffer from '../FrameBuffer';
|
||||
import Texture2D from '../Texture2D';
|
||||
import Shader from '../Shader';
|
||||
import Material from '../Material';
|
||||
|
||||
import colorEssl from './color.glsl.js';
|
||||
import type Renderer from '../Renderer';
|
||||
import type Camera from '../Camera';
|
||||
import type Scene from '../Scene';
|
||||
import type ClayNode from '../Node';
|
||||
import type Renderable from '../Renderable';
|
||||
Shader.import(colorEssl);
|
||||
|
||||
/**
|
||||
* Pixel picking is gpu based picking, which is fast and accurate.
|
||||
* But not like ray picking, it can't get the intersection point and triangle.
|
||||
* @constructor clay.picking.PixelPicking
|
||||
* @extends clay.core.Base
|
||||
*/
|
||||
const PixelPicking = Base.extend(
|
||||
function () {
|
||||
return /** @lends clay.picking.PixelPicking# */ {
|
||||
/**
|
||||
* Target renderer
|
||||
* @type {clay.Renderer}
|
||||
*/
|
||||
renderer: null,
|
||||
/**
|
||||
* Downsample ratio of hidden frame buffer
|
||||
* @type {number}
|
||||
*/
|
||||
downSampleRatio: 1,
|
||||
class PixelPicking {
|
||||
/**
|
||||
* Target renderer
|
||||
*/
|
||||
renderer: Renderer;
|
||||
/**
|
||||
* Downsample ratio of hidden frame buffer
|
||||
* @type {number}
|
||||
*/
|
||||
downSampleRatio = 1;
|
||||
|
||||
width: 100,
|
||||
height: 100,
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
lookupOffset: 1,
|
||||
lookupOffset = 1;
|
||||
|
||||
_frameBuffer: null,
|
||||
_texture: null,
|
||||
_shader: null,
|
||||
private _frameBuffer = new FrameBuffer();
|
||||
private _texture = new Texture2D();
|
||||
private _shader = new Shader(
|
||||
Shader.source('clay.picking.color.vertex'),
|
||||
Shader.source('clay.picking.color.fragment')
|
||||
);
|
||||
|
||||
_idMaterials: [],
|
||||
_lookupTable: [],
|
||||
_idMaterials: Material[] = [];
|
||||
_lookupTable: Renderable[] = [];
|
||||
|
||||
_meshMaterials: [],
|
||||
_meshMaterials: Material[] = [];
|
||||
|
||||
_idOffset: 0
|
||||
};
|
||||
},
|
||||
function () {
|
||||
if (this.renderer) {
|
||||
this.width = this.renderer.getWidth();
|
||||
this.height = this.renderer.getHeight();
|
||||
_idOffset = 0;
|
||||
|
||||
constructor(renderer: Renderer) {
|
||||
this.renderer = renderer;
|
||||
this.width = renderer.getWidth();
|
||||
this.height = renderer.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set picking presision
|
||||
* @param {number} ratio
|
||||
*/
|
||||
setPrecision(ratio: number) {
|
||||
this._texture.width = this.width * ratio;
|
||||
this._texture.height = this.height * ratio;
|
||||
this.downSampleRatio = ratio;
|
||||
}
|
||||
resize(width: number, height: number) {
|
||||
this._texture.width = width * this.downSampleRatio;
|
||||
this._texture.height = height * this.downSampleRatio;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this._texture.dirty();
|
||||
}
|
||||
/**
|
||||
* Update the picking framebuffer
|
||||
* @param {number} ratio
|
||||
*/
|
||||
update(scene: Scene, camera: Camera) {
|
||||
const renderer = this.renderer;
|
||||
const frameBuffer = this._frameBuffer;
|
||||
const newWidth = renderer.getWidth();
|
||||
const newHeight = renderer.getHeight();
|
||||
if (newWidth !== this.width || newHeight !== this.height) {
|
||||
this.resize(newWidth, newHeight);
|
||||
}
|
||||
this._init();
|
||||
},
|
||||
/** @lends clay.picking.PixelPicking.prototype */ {
|
||||
_init: function () {
|
||||
this._texture = new Texture2D({
|
||||
width: this.width * this.downSampleRatio,
|
||||
height: this.height * this.downSampleRatio
|
||||
});
|
||||
this._frameBuffer = new FrameBuffer();
|
||||
|
||||
this._shader = new Shader(
|
||||
Shader.source('clay.picking.color.vertex'),
|
||||
Shader.source('clay.picking.color.fragment')
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Set picking presision
|
||||
* @param {number} ratio
|
||||
*/
|
||||
setPrecision: function (ratio) {
|
||||
this._texture.width = this.width * ratio;
|
||||
this._texture.height = this.height * ratio;
|
||||
this.downSampleRatio = ratio;
|
||||
},
|
||||
resize: function (width, height) {
|
||||
this._texture.width = width * this.downSampleRatio;
|
||||
this._texture.height = height * this.downSampleRatio;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this._texture.dirty();
|
||||
},
|
||||
/**
|
||||
* Update the picking framebuffer
|
||||
* @param {number} ratio
|
||||
*/
|
||||
update: function (scene, camera) {
|
||||
const renderer = this.renderer;
|
||||
const frameBuffer = this._frameBuffer;
|
||||
if (renderer.getWidth() !== this.width || renderer.getHeight() !== this.height) {
|
||||
this.resize(renderer.width, renderer.height);
|
||||
}
|
||||
frameBuffer.attach(this._texture);
|
||||
frameBuffer.bind(renderer);
|
||||
this._idOffset = this.lookupOffset;
|
||||
this._setMaterial(scene);
|
||||
renderer.render(scene, camera);
|
||||
this._restoreMaterial();
|
||||
frameBuffer.unbind(renderer);
|
||||
}
|
||||
|
||||
frameBuffer.attach(this._texture);
|
||||
frameBuffer.bind(renderer);
|
||||
this._idOffset = this.lookupOffset;
|
||||
this._setMaterial(scene);
|
||||
renderer.render(scene, camera);
|
||||
this._restoreMaterial();
|
||||
frameBuffer.unbind(renderer);
|
||||
},
|
||||
|
||||
_setMaterial: function (root) {
|
||||
for (let i = 0; i < root._children.length; i++) {
|
||||
const child = root._children[i];
|
||||
if (child.geometry && child.material && child.material.shader) {
|
||||
const id = this._idOffset++;
|
||||
const idx = id - this.lookupOffset;
|
||||
let material = this._idMaterials[idx];
|
||||
if (!material) {
|
||||
material = new Material({
|
||||
shader: this._shader
|
||||
});
|
||||
const color = packID(id);
|
||||
color[0] /= 255;
|
||||
color[1] /= 255;
|
||||
color[2] /= 255;
|
||||
color[3] = 1.0;
|
||||
material.set('color', color);
|
||||
this._idMaterials[idx] = material;
|
||||
}
|
||||
this._meshMaterials[idx] = child.material;
|
||||
this._lookupTable[idx] = child;
|
||||
child.material = material;
|
||||
}
|
||||
if (child._children.length) {
|
||||
this._setMaterial(child);
|
||||
_setMaterial(root: ClayNode) {
|
||||
const children = root.childrenRef();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
if (child.isRenderable()) {
|
||||
const id = this._idOffset++;
|
||||
const idx = id - this.lookupOffset;
|
||||
let material = this._idMaterials[idx];
|
||||
if (!material) {
|
||||
material = new Material({
|
||||
shader: this._shader
|
||||
});
|
||||
const color = packID(id);
|
||||
color[0] /= 255;
|
||||
color[1] /= 255;
|
||||
color[2] /= 255;
|
||||
color[3] = 1.0;
|
||||
material.set('color', color);
|
||||
this._idMaterials[idx] = material;
|
||||
}
|
||||
this._meshMaterials[idx] = child.material;
|
||||
this._lookupTable[idx] = child;
|
||||
child.material = material;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Pick the object
|
||||
* @param {number} x Mouse position x
|
||||
* @param {number} y Mouse position y
|
||||
* @return {clay.Node}
|
||||
*/
|
||||
pick: function (x, y) {
|
||||
const renderer = this.renderer;
|
||||
|
||||
const ratio = this.downSampleRatio;
|
||||
x = Math.ceil(ratio * x);
|
||||
y = Math.ceil(ratio * (this.height - y));
|
||||
|
||||
this._frameBuffer.bind(renderer);
|
||||
const pixel = new Uint8Array(4);
|
||||
const _gl = renderer.gl;
|
||||
// TODO out of bounds ?
|
||||
// preserveDrawingBuffer ?
|
||||
_gl.readPixels(x, y, 1, 1, _gl.RGBA, _gl.UNSIGNED_BYTE, pixel);
|
||||
this._frameBuffer.unbind(renderer);
|
||||
// Skip interpolated pixel because of anti alias
|
||||
if (pixel[3] === 255) {
|
||||
const id = unpackID(pixel[0], pixel[1], pixel[2]);
|
||||
if (id) {
|
||||
const el = this._lookupTable[id - this.lookupOffset];
|
||||
return el;
|
||||
}
|
||||
if (child.childrenRef().length) {
|
||||
this._setMaterial(child);
|
||||
}
|
||||
},
|
||||
|
||||
_restoreMaterial: function () {
|
||||
for (let i = 0; i < this._lookupTable.length; i++) {
|
||||
this._lookupTable[i].material = this._meshMaterials[i];
|
||||
}
|
||||
},
|
||||
|
||||
dispose: function (renderer) {
|
||||
this._frameBuffer.dispose(renderer);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function packID(id) {
|
||||
/**
|
||||
* Pick the object
|
||||
* @param {number} x Mouse position x
|
||||
* @param {number} y Mouse position y
|
||||
* @return {clay.Node}
|
||||
*/
|
||||
pick(x: number, y: number) {
|
||||
const renderer = this.renderer;
|
||||
|
||||
const ratio = this.downSampleRatio;
|
||||
x = Math.ceil(ratio * x);
|
||||
y = Math.ceil(ratio * (this.height - y));
|
||||
|
||||
this._frameBuffer.bind(renderer);
|
||||
const pixel = new Uint8Array(4);
|
||||
const _gl = renderer.gl;
|
||||
// TODO out of bounds ?
|
||||
// preserveDrawingBuffer ?
|
||||
_gl.readPixels(x, y, 1, 1, _gl.RGBA, _gl.UNSIGNED_BYTE, pixel);
|
||||
this._frameBuffer.unbind(renderer);
|
||||
// Skip interpolated pixel because of anti alias
|
||||
if (pixel[3] === 255) {
|
||||
const id = unpackID(pixel[0], pixel[1], pixel[2]);
|
||||
if (id) {
|
||||
const el = this._lookupTable[id - this.lookupOffset];
|
||||
return el;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_restoreMaterial() {
|
||||
for (let i = 0; i < this._lookupTable.length; i++) {
|
||||
this._lookupTable[i].material = this._meshMaterials[i];
|
||||
}
|
||||
}
|
||||
|
||||
dispose(renderer: Renderer) {
|
||||
this._frameBuffer.dispose(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
function packID(id: number) {
|
||||
const r = id >> 16;
|
||||
const g = (id - (r << 16)) >> 8;
|
||||
const b = id - (r << 16) - (g << 8);
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
function unpackID(r, g, b) {
|
||||
function unpackID(r: number, g: number, b: number) {
|
||||
return (r << 16) + (g << 8) + b;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
// https://github.com/googlevr/webvr-polyfill/blob/master/src/cardboard-distorter.js
|
||||
|
||||
// Use webvr may have scale problem.
|
||||
@ -10,175 +9,174 @@ import Mesh from '../Mesh';
|
||||
import Material from '../Material';
|
||||
import Geometry from '../Geometry';
|
||||
import Shader from '../Shader';
|
||||
import Base from '../core/Base';
|
||||
import PerspectiveCamera from '../camera/Perspective';
|
||||
|
||||
import outputEssl from './output.glsl.js';
|
||||
import { Color } from '../core/type';
|
||||
import type Renderer from '../Renderer';
|
||||
import type Texture2D from '../Texture2D';
|
||||
|
||||
Shader.import(outputEssl);
|
||||
|
||||
function lerp(a, b, t) {
|
||||
function lerp(a: number, b: number, t: number) {
|
||||
return a * (1 - t) + b * t;
|
||||
}
|
||||
|
||||
const CardboardDistorter = Base.extend(
|
||||
function () {
|
||||
return {
|
||||
clearColor: [0, 0, 0, 1],
|
||||
class CardboardDistorter {
|
||||
clearColor: Color = [0, 0, 0, 1];
|
||||
|
||||
_mesh: new Mesh({
|
||||
geometry: new Geometry({
|
||||
dynamic: true
|
||||
}),
|
||||
culling: false,
|
||||
material: new Material({
|
||||
// FIXME Why disable depthMask will be wrong
|
||||
// depthMask: false,
|
||||
depthTest: false,
|
||||
shader: new Shader({
|
||||
vertex: Shader.source('clay.vr.disorter.output.vertex'),
|
||||
fragment: Shader.source('clay.vr.disorter.output.fragment')
|
||||
})
|
||||
})
|
||||
private _mesh: Mesh;
|
||||
private _fakeCamera = new PerspectiveCamera();
|
||||
constructor() {
|
||||
this._mesh = new Mesh({
|
||||
geometry: new Geometry({
|
||||
dynamic: true
|
||||
}),
|
||||
_fakeCamera: new PerspectiveCamera()
|
||||
};
|
||||
},
|
||||
{
|
||||
render: function (renderer, sourceTexture) {
|
||||
const clearColor = this.clearColor;
|
||||
const gl = renderer.gl;
|
||||
gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
culling: false,
|
||||
material: new Material({
|
||||
// FIXME Why disable depthMask will be wrong
|
||||
// depthMask: false,
|
||||
depthTest: false,
|
||||
shader: new Shader({
|
||||
vertex: Shader.source('clay.vr.disorter.output.vertex'),
|
||||
fragment: Shader.source('clay.vr.disorter.output.fragment')
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
gl.disable(gl.BLEND);
|
||||
render(renderer: Renderer, sourceTexture: Texture2D) {
|
||||
const clearColor = this.clearColor;
|
||||
const gl = renderer.gl;
|
||||
gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this._mesh.material.set('texture', sourceTexture);
|
||||
gl.disable(gl.BLEND);
|
||||
|
||||
// Full size?
|
||||
renderer.saveViewport();
|
||||
renderer.setViewport(0, 0, renderer.getWidth(), renderer.getHeight());
|
||||
renderer.renderPass([this._mesh], this._fakeCamera);
|
||||
renderer.restoreViewport();
|
||||
// this._mesh.material.shader.bind(renderer);
|
||||
// this._mesh.material.bind(renderer);
|
||||
// this._mesh.render(renderer.gl);
|
||||
},
|
||||
this._mesh.material.set('texture', sourceTexture);
|
||||
|
||||
updateFromVRDisplay: function (vrDisplay) {
|
||||
// FIXME
|
||||
if (vrDisplay.deviceInfo_) {
|
||||
// Hardcoded mesh size
|
||||
this._updateMesh(20, 20, vrDisplay.deviceInfo_);
|
||||
} else {
|
||||
console.warn('Cant get vrDisplay.deviceInfo_, seems code changed');
|
||||
}
|
||||
},
|
||||
// Full size?
|
||||
renderer.saveViewport();
|
||||
renderer.setViewport(0, 0, renderer.getWidth(), renderer.getHeight());
|
||||
renderer.renderPass([this._mesh], this._fakeCamera);
|
||||
renderer.restoreViewport();
|
||||
// this._mesh.material.shader.bind(renderer);
|
||||
// this._mesh.material.bind(renderer);
|
||||
// this._mesh.render(renderer.gl);
|
||||
}
|
||||
|
||||
_updateMesh: function (width, height, deviceInfo) {
|
||||
const positionAttr = this._mesh.geometry.attributes.position;
|
||||
const texcoordAttr = this._mesh.geometry.attributes.texcoord0;
|
||||
positionAttr.init(2 * width * height);
|
||||
texcoordAttr.init(2 * width * height);
|
||||
|
||||
const lensFrustum = deviceInfo.getLeftEyeVisibleTanAngles();
|
||||
const noLensFrustum = deviceInfo.getLeftEyeNoLensTanAngles();
|
||||
const viewport = deviceInfo.getLeftEyeVisibleScreenRect(noLensFrustum);
|
||||
|
||||
const pos = [];
|
||||
const uv = [];
|
||||
let vidx = 0;
|
||||
|
||||
// Vertices
|
||||
for (let e = 0; e < 2; e++) {
|
||||
for (let j = 0; j < height; j++) {
|
||||
for (let i = 0; i < width; i++, vidx++) {
|
||||
let u = i / (width - 1);
|
||||
let v = j / (height - 1);
|
||||
|
||||
// Grid points regularly spaced in StreoScreen, and barrel distorted in
|
||||
// the mesh.
|
||||
const s = u;
|
||||
const t = v;
|
||||
const x = lerp(lensFrustum[0], lensFrustum[2], u);
|
||||
const y = lerp(lensFrustum[3], lensFrustum[1], v);
|
||||
const d = Math.sqrt(x * x + y * y);
|
||||
const r = deviceInfo.distortion.distortInverse(d);
|
||||
const p = (x * r) / d;
|
||||
const q = (y * r) / d;
|
||||
u = (p - noLensFrustum[0]) / (noLensFrustum[2] - noLensFrustum[0]);
|
||||
v = (q - noLensFrustum[3]) / (noLensFrustum[1] - noLensFrustum[3]);
|
||||
|
||||
// FIXME: The original Unity plugin multiplied U by the aspect ratio
|
||||
// and didn't multiply either value by 2, but that seems to get it
|
||||
// really close to correct looking for me. I hate this kind of "Don't
|
||||
// know why it works" code though, and wold love a more logical
|
||||
// explanation of what needs to happen here.
|
||||
u = (viewport.x + u * viewport.width - 0.5) * 2.0; //* aspect;
|
||||
v = (viewport.y + v * viewport.height - 0.5) * 2.0;
|
||||
|
||||
pos[0] = u;
|
||||
pos[1] = v;
|
||||
pos[2] = 0;
|
||||
|
||||
uv[0] = s * 0.5 + e * 0.5;
|
||||
uv[1] = t;
|
||||
|
||||
positionAttr.set(vidx, pos);
|
||||
texcoordAttr.set(vidx, uv);
|
||||
}
|
||||
}
|
||||
|
||||
let w = lensFrustum[2] - lensFrustum[0];
|
||||
lensFrustum[0] = -(w + lensFrustum[0]);
|
||||
lensFrustum[2] = w - lensFrustum[2];
|
||||
w = noLensFrustum[2] - noLensFrustum[0];
|
||||
noLensFrustum[0] = -(w + noLensFrustum[0]);
|
||||
noLensFrustum[2] = w - noLensFrustum[2];
|
||||
viewport.x = 1 - (viewport.x + viewport.width);
|
||||
}
|
||||
|
||||
// Indices
|
||||
const indices = new Uint16Array(2 * (width - 1) * (height - 1) * 6);
|
||||
const halfwidth = width / 2;
|
||||
const halfheight = height / 2;
|
||||
vidx = 0;
|
||||
let iidx = 0;
|
||||
for (let e = 0; e < 2; e++) {
|
||||
for (let j = 0; j < height; j++) {
|
||||
for (let i = 0; i < width; i++, vidx++) {
|
||||
if (i === 0 || j === 0) {
|
||||
continue;
|
||||
}
|
||||
// Build a quad. Lower right and upper left quadrants have quads with
|
||||
// the triangle diagonal flipped to get the vignette to interpolate
|
||||
// correctly.
|
||||
if (i <= halfwidth == j <= halfheight) {
|
||||
// Quad diagonal lower left to upper right.
|
||||
indices[iidx++] = vidx;
|
||||
indices[iidx++] = vidx - width - 1;
|
||||
indices[iidx++] = vidx - width;
|
||||
indices[iidx++] = vidx - width - 1;
|
||||
indices[iidx++] = vidx;
|
||||
indices[iidx++] = vidx - 1;
|
||||
} else {
|
||||
// Quad diagonal upper left to lower right.
|
||||
indices[iidx++] = vidx - 1;
|
||||
indices[iidx++] = vidx - width;
|
||||
indices[iidx++] = vidx;
|
||||
indices[iidx++] = vidx - width;
|
||||
indices[iidx++] = vidx - 1;
|
||||
indices[iidx++] = vidx - width - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._mesh.geometry.indices = indices;
|
||||
|
||||
this._mesh.geometry.dirty();
|
||||
updateFromVRDisplay(vrDisplay: any) {
|
||||
// FIXME
|
||||
if (vrDisplay.deviceInfo_) {
|
||||
// Hardcoded mesh size
|
||||
this._updateMesh(20, 20, vrDisplay.deviceInfo_);
|
||||
} else {
|
||||
console.warn('Cant get vrDisplay.deviceInfo_, seems code changed');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
_updateMesh(width: number, height: number, deviceInfo: any) {
|
||||
const positionAttr = this._mesh.geometry.attributes.position;
|
||||
const texcoordAttr = this._mesh.geometry.attributes.texcoord0;
|
||||
positionAttr.init(2 * width * height);
|
||||
texcoordAttr.init(2 * width * height);
|
||||
|
||||
const lensFrustum = deviceInfo.getLeftEyeVisibleTanAngles();
|
||||
const noLensFrustum = deviceInfo.getLeftEyeNoLensTanAngles();
|
||||
const viewport = deviceInfo.getLeftEyeVisibleScreenRect(noLensFrustum);
|
||||
|
||||
const pos = [];
|
||||
const uv = [];
|
||||
let vidx = 0;
|
||||
|
||||
// Vertices
|
||||
for (let e = 0; e < 2; e++) {
|
||||
for (let j = 0; j < height; j++) {
|
||||
for (let i = 0; i < width; i++, vidx++) {
|
||||
let u = i / (width - 1);
|
||||
let v = j / (height - 1);
|
||||
|
||||
// Grid points regularly spaced in StreoScreen, and barrel distorted in
|
||||
// the mesh.
|
||||
const s = u;
|
||||
const t = v;
|
||||
const x = lerp(lensFrustum[0], lensFrustum[2], u);
|
||||
const y = lerp(lensFrustum[3], lensFrustum[1], v);
|
||||
const d = Math.sqrt(x * x + y * y);
|
||||
const r = deviceInfo.distortion.distortInverse(d);
|
||||
const p = (x * r) / d;
|
||||
const q = (y * r) / d;
|
||||
u = (p - noLensFrustum[0]) / (noLensFrustum[2] - noLensFrustum[0]);
|
||||
v = (q - noLensFrustum[3]) / (noLensFrustum[1] - noLensFrustum[3]);
|
||||
|
||||
// FIXME: The original Unity plugin multiplied U by the aspect ratio
|
||||
// and didn't multiply either value by 2, but that seems to get it
|
||||
// really close to correct looking for me. I hate this kind of "Don't
|
||||
// know why it works" code though, and wold love a more logical
|
||||
// explanation of what needs to happen here.
|
||||
u = (viewport.x + u * viewport.width - 0.5) * 2.0; //* aspect;
|
||||
v = (viewport.y + v * viewport.height - 0.5) * 2.0;
|
||||
|
||||
pos[0] = u;
|
||||
pos[1] = v;
|
||||
pos[2] = 0;
|
||||
|
||||
uv[0] = s * 0.5 + e * 0.5;
|
||||
uv[1] = t;
|
||||
|
||||
positionAttr.set(vidx, pos);
|
||||
texcoordAttr.set(vidx, uv);
|
||||
}
|
||||
}
|
||||
|
||||
let w = lensFrustum[2] - lensFrustum[0];
|
||||
lensFrustum[0] = -(w + lensFrustum[0]);
|
||||
lensFrustum[2] = w - lensFrustum[2];
|
||||
w = noLensFrustum[2] - noLensFrustum[0];
|
||||
noLensFrustum[0] = -(w + noLensFrustum[0]);
|
||||
noLensFrustum[2] = w - noLensFrustum[2];
|
||||
viewport.x = 1 - (viewport.x + viewport.width);
|
||||
}
|
||||
|
||||
// Indices
|
||||
const indices = new Uint16Array(2 * (width - 1) * (height - 1) * 6);
|
||||
const halfwidth = width / 2;
|
||||
const halfheight = height / 2;
|
||||
vidx = 0;
|
||||
let iidx = 0;
|
||||
for (let e = 0; e < 2; e++) {
|
||||
for (let j = 0; j < height; j++) {
|
||||
for (let i = 0; i < width; i++, vidx++) {
|
||||
if (i === 0 || j === 0) {
|
||||
continue;
|
||||
}
|
||||
// Build a quad. Lower right and upper left quadrants have quads with
|
||||
// the triangle diagonal flipped to get the vignette to interpolate
|
||||
// correctly.
|
||||
if (i <= halfwidth == j <= halfheight) {
|
||||
// Quad diagonal lower left to upper right.
|
||||
indices[iidx++] = vidx;
|
||||
indices[iidx++] = vidx - width - 1;
|
||||
indices[iidx++] = vidx - width;
|
||||
indices[iidx++] = vidx - width - 1;
|
||||
indices[iidx++] = vidx;
|
||||
indices[iidx++] = vidx - 1;
|
||||
} else {
|
||||
// Quad diagonal upper left to lower right.
|
||||
indices[iidx++] = vidx - 1;
|
||||
indices[iidx++] = vidx - width;
|
||||
indices[iidx++] = vidx;
|
||||
indices[iidx++] = vidx - width;
|
||||
indices[iidx++] = vidx - 1;
|
||||
indices[iidx++] = vidx - width - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._mesh.geometry.indices = indices;
|
||||
|
||||
this._mesh.geometry.dirty();
|
||||
}
|
||||
}
|
||||
export default CardboardDistorter;
|
||||
|
||||
@ -1,121 +1,109 @@
|
||||
// @ts-nocheck
|
||||
import Base from '../Node';
|
||||
import Camera from '../camera/Perspective';
|
||||
import PerspectiveCamera from '../camera/Perspective';
|
||||
import Matrix4 from '../math/Matrix4';
|
||||
import type ClayNode from '../Node';
|
||||
|
||||
const tmpProjectionMatrix = new Matrix4();
|
||||
|
||||
const StereoCamera = Base.extend(
|
||||
function () {
|
||||
return {
|
||||
aspect: 0.5,
|
||||
class StereoCamera {
|
||||
aspect = 0.5;
|
||||
|
||||
_leftCamera: new Camera(),
|
||||
_leftCamera = new PerspectiveCamera();
|
||||
|
||||
_rightCamera: new Camera(),
|
||||
_rightCamera = new PerspectiveCamera();
|
||||
|
||||
_eyeLeft: new Matrix4(),
|
||||
_eyeRight: new Matrix4(),
|
||||
_eyeLeft = new Matrix4();
|
||||
_eyeRight = new Matrix4();
|
||||
|
||||
_frameData: null
|
||||
};
|
||||
},
|
||||
{
|
||||
updateFromCamera: function (camera, focus, zoom, eyeSep) {
|
||||
if (camera.transformNeedsUpdate()) {
|
||||
console.warn('Node transform is not updated');
|
||||
}
|
||||
_frameData: any;
|
||||
|
||||
focus = focus == null ? 10 : focus;
|
||||
zoom = zoom == null ? 1 : zoom;
|
||||
eyeSep = eyeSep == null ? 0.064 : eyeSep;
|
||||
updateFromCamera(camera: PerspectiveCamera, focus: number, zoom: number, eyeSep: number) {
|
||||
focus = focus == null ? 10 : focus;
|
||||
zoom = zoom == null ? 1 : zoom;
|
||||
eyeSep = eyeSep == null ? 0.064 : eyeSep;
|
||||
|
||||
const fov = camera.fov;
|
||||
const aspect = camera.aspect * this.aspect;
|
||||
const near = camera.near;
|
||||
const fov = camera.fov;
|
||||
const aspect = camera.aspect * this.aspect;
|
||||
const near = camera.near;
|
||||
|
||||
// Off-axis stereoscopic effect based on
|
||||
// http://paulbourke.net/stereographics/stereorender/
|
||||
// Off-axis stereoscopic effect based on
|
||||
// http://paulbourke.net/stereographics/stereorender/
|
||||
|
||||
tmpProjectionMatrix.copy(camera.projectionMatrix);
|
||||
eyeSep = eyeSep / 2;
|
||||
const eyeSepOnProjection = (eyeSep * near) / focus;
|
||||
const ymax = (near * Math.tan((Math.PI / 180) * fov * 0.5)) / zoom;
|
||||
let xmin, xmax;
|
||||
tmpProjectionMatrix.copy(camera.projectionMatrix);
|
||||
eyeSep = eyeSep / 2;
|
||||
const eyeSepOnProjection = (eyeSep * near) / focus;
|
||||
const ymax = (near * Math.tan((Math.PI / 180) * fov * 0.5)) / zoom;
|
||||
let xmin, xmax;
|
||||
|
||||
// translate xOffset
|
||||
this._eyeLeft.array[12] = -eyeSep;
|
||||
this._eyeRight.array[12] = eyeSep;
|
||||
// translate xOffset
|
||||
this._eyeLeft.array[12] = -eyeSep;
|
||||
this._eyeRight.array[12] = eyeSep;
|
||||
|
||||
// for left eye
|
||||
xmin = -ymax * aspect + eyeSepOnProjection;
|
||||
xmax = ymax * aspect + eyeSepOnProjection;
|
||||
// for left eye
|
||||
xmin = -ymax * aspect + eyeSepOnProjection;
|
||||
xmax = ymax * aspect + eyeSepOnProjection;
|
||||
|
||||
tmpProjectionMatrix.array[0] = (2 * near) / (xmax - xmin);
|
||||
tmpProjectionMatrix.array[8] = (xmax + xmin) / (xmax - xmin);
|
||||
tmpProjectionMatrix.array[0] = (2 * near) / (xmax - xmin);
|
||||
tmpProjectionMatrix.array[8] = (xmax + xmin) / (xmax - xmin);
|
||||
|
||||
this._leftCamera.projectionMatrix.copy(tmpProjectionMatrix);
|
||||
this._leftCamera.projectionMatrix.copy(tmpProjectionMatrix);
|
||||
|
||||
// for right eye
|
||||
xmin = -ymax * aspect - eyeSepOnProjection;
|
||||
xmax = ymax * aspect - eyeSepOnProjection;
|
||||
// for right eye
|
||||
xmin = -ymax * aspect - eyeSepOnProjection;
|
||||
xmax = ymax * aspect - eyeSepOnProjection;
|
||||
|
||||
tmpProjectionMatrix.array[0] = (2 * near) / (xmax - xmin);
|
||||
tmpProjectionMatrix.array[8] = (xmax + xmin) / (xmax - xmin);
|
||||
tmpProjectionMatrix.array[0] = (2 * near) / (xmax - xmin);
|
||||
tmpProjectionMatrix.array[8] = (xmax + xmin) / (xmax - xmin);
|
||||
|
||||
this._rightCamera.projectionMatrix.copy(tmpProjectionMatrix);
|
||||
this._rightCamera.projectionMatrix.copy(tmpProjectionMatrix);
|
||||
|
||||
this._leftCamera.worldTransform.copy(camera.worldTransform).multiply(this._eyeLeft);
|
||||
this._leftCamera.worldTransform.copy(camera.worldTransform).multiply(this._eyeLeft);
|
||||
|
||||
this._rightCamera.worldTransform.copy(camera.worldTransform).multiply(this._eyeRight);
|
||||
this._rightCamera.worldTransform.copy(camera.worldTransform).multiply(this._eyeRight);
|
||||
|
||||
this._leftCamera.decomposeWorldTransform();
|
||||
this._leftCamera.decomposeProjectionMatrix();
|
||||
this._leftCamera.decomposeWorldTransform();
|
||||
this._leftCamera.decomposeProjectionMatrix();
|
||||
|
||||
this._rightCamera.decomposeWorldTransform();
|
||||
this._rightCamera.decomposeProjectionMatrix();
|
||||
},
|
||||
this._rightCamera.decomposeWorldTransform();
|
||||
this._rightCamera.decomposeProjectionMatrix();
|
||||
}
|
||||
|
||||
updateFromVRDisplay: function (vrDisplay, parentNode) {
|
||||
if (typeof VRFrameData === 'undefined') {
|
||||
return;
|
||||
}
|
||||
updateFromVRDisplay(vrDisplay: any, parentNode: ClayNode) {
|
||||
// @ts-ignore
|
||||
if (typeof VRFrameData === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
/* global VRFrameData */
|
||||
const frameData = this._frameData || (this._frameData = new VRFrameData());
|
||||
vrDisplay.getFrameData(frameData);
|
||||
const leftCamera = this._leftCamera;
|
||||
const rightCamera = this._rightCamera;
|
||||
/* global VRFrameData */
|
||||
// @ts-ignore
|
||||
const frameData = this._frameData || (this._frameData = new VRFrameData());
|
||||
vrDisplay.getFrameData(frameData);
|
||||
const leftCamera = this._leftCamera;
|
||||
const rightCamera = this._rightCamera;
|
||||
|
||||
leftCamera.projectionMatrix.setArray(frameData.leftProjectionMatrix);
|
||||
leftCamera.decomposeProjectionMatrix();
|
||||
leftCamera.viewMatrix.setArray(frameData.leftViewMatrix);
|
||||
leftCamera.setViewMatrix(leftCamera.viewMatrix);
|
||||
leftCamera.projectionMatrix.setArray(frameData.leftProjectionMatrix);
|
||||
leftCamera.decomposeProjectionMatrix();
|
||||
leftCamera.viewMatrix.setArray(frameData.leftViewMatrix);
|
||||
leftCamera.setViewMatrix(leftCamera.viewMatrix);
|
||||
|
||||
rightCamera.projectionMatrix.setArray(frameData.rightProjectionMatrix);
|
||||
rightCamera.decomposeProjectionMatrix();
|
||||
rightCamera.viewMatrix.setArray(frameData.rightViewMatrix);
|
||||
rightCamera.setViewMatrix(rightCamera.viewMatrix);
|
||||
rightCamera.projectionMatrix.setArray(frameData.rightProjectionMatrix);
|
||||
rightCamera.decomposeProjectionMatrix();
|
||||
rightCamera.viewMatrix.setArray(frameData.rightViewMatrix);
|
||||
rightCamera.setViewMatrix(rightCamera.viewMatrix);
|
||||
|
||||
if (parentNode && parentNode.worldTransform) {
|
||||
if (parentNode.transformNeedsUpdate()) {
|
||||
console.warn('Node transform is not updated');
|
||||
}
|
||||
leftCamera.worldTransform.multiplyLeft(parentNode.worldTransform);
|
||||
leftCamera.decomposeWorldTransform();
|
||||
rightCamera.worldTransform.multiplyLeft(parentNode.worldTransform);
|
||||
rightCamera.decomposeWorldTransform();
|
||||
}
|
||||
},
|
||||
|
||||
getLeftCamera: function () {
|
||||
return this._leftCamera;
|
||||
},
|
||||
|
||||
getRightCamera: function () {
|
||||
return this._rightCamera;
|
||||
if (parentNode && parentNode.worldTransform) {
|
||||
leftCamera.worldTransform.multiplyLeft(parentNode.worldTransform);
|
||||
leftCamera.decomposeWorldTransform();
|
||||
rightCamera.worldTransform.multiplyLeft(parentNode.worldTransform);
|
||||
rightCamera.decomposeWorldTransform();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
getLeftCamera() {
|
||||
return this._leftCamera;
|
||||
}
|
||||
|
||||
getRightCamera() {
|
||||
return this._rightCamera;
|
||||
}
|
||||
}
|
||||
export default StereoCamera;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user