feat: viewer api to set texture shader globally

This commit is contained in:
Oscar Lorentzon 2022-04-13 20:21:33 -07:00
parent dd9505bbc4
commit 86f26fc05b
11 changed files with 243 additions and 61 deletions

134
examples/debug/shader.html Normal file
View File

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html>
<head>
<title>MapillaryJS Shader</title>
<link rel="icon" href="data:," />
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"
/>
<link rel="stylesheet" href="/dist/mapillary.css" />
<style>
body {
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
}
.viewer {
width: 100%;
height: calc(100% - 32px);
}
button {
margin-top: 4px;
margin-left: 6px;
height: 24px;
}
</style>
</head>
<body>
<script type="module">
import {
CameraControls,
Shader,
ShaderChunk,
Viewer,
} from "/dist/mapillary.module.js";
import { accessToken } from "/doc-src/.access-token/token.js";
(function main() {
const imageId = "303729947926829";
const container = document.createElement("div");
container.className = "viewer";
document.body.append(container);
const viewer = new Viewer({
accessToken,
cameraControls: CameraControls.Street,
component: { cover: false },
container,
});
viewer.moveTo(imageId).catch((error) => console.warn(error));
addButtons(viewer);
})();
function addButton(content, handler) {
const button = document.createElement("button");
button.textContent = content;
button.addEventListener("click", handler);
document.body.append(button);
}
function addButtons(viewer) {
addButton("Texture", () => viewer.setShader(Shader.texture));
addButton("Magnesis", () => viewer.setShader(makeMagnesis()));
addButton("Earth", () =>
viewer.setCameraControls(CameraControls.Earth)
);
addButton("Street", () =>
viewer.setCameraControls(CameraControls.Street)
);
}
function makeMagnesis() {
const vertex = /* glsl */ `
#include <uniforms_vertex>
#include <varyings_vertex>
varying vec4 cameraCoords;
void main()
{
cameraCoords = modelViewMatrix * vec4(position, 1.0);
#include <extrinsic_vertex>
#include <gl_position_vertex>
}
`;
const fragment = `
#include <precision_fragment>
#include <common>
#include <uniforms_fragment>
#include <varyings_fragment>
#include <coordinates>
#expand <parameters>
#expand <uniforms>
#expand <project_to_sfm_definition>
varying vec4 cameraCoords;
void main()
{
#include <bearing_fragment>
#expand <project_to_sfm_invocation>
#include <map_color_fragment>
float magnesis = cameraCoords.x * 3.0;
if (abs(magnesis - floor(magnesis)) < 0.07) {
mapColor.r = 1.0;
}
#include <gl_frag_color_fragment>
}
`;
const magnesis = {
fragment,
vertex,
};
return magnesis;
}
</script>
</body>
</html>

View File

@ -48,6 +48,7 @@ import { ComponentConfiguration } from "../interfaces/ComponentConfiguration";
import { Transform } from "../../geo/Transform";
import { ComponentName } from "../ComponentName";
import { State } from "../../state/State";
import { GLShader } from "../../shader/Shader";
interface ImageGLRendererOperation {
(renderer: ImageGLRenderer): ImageGLRenderer;
@ -170,10 +171,11 @@ export class ImageComponent extends Component<ComponentConfiguration> {
.subscribe(this._rendererOperation$));
subs.push(this._navigator.stateService.currentState$.pipe(
withLatestFrom(this._navigator.projectionService.shader$),
map(
(frame: AnimationFrame): ImageGLRendererOperation => {
([frame, shader]: [AnimationFrame, GLShader]): ImageGLRendererOperation => {
return (renderer: ImageGLRenderer): ImageGLRenderer => {
renderer.updateFrame(frame);
renderer.updateFrame(frame, shader);
return renderer;
};
@ -381,10 +383,11 @@ export class ImageComponent extends Component<ComponentConfiguration> {
share());
subs.push(cachedPanNodes$.pipe(
withLatestFrom(this._navigator.projectionService.shader$),
map(
([n, t]: [ImageNode, Transform]): ImageGLRendererOperation => {
([[n, t], s]: [[ImageNode, Transform], GLShader]): ImageGLRendererOperation => {
return (renderer: ImageGLRenderer): ImageGLRenderer => {
renderer.addPeripheryPlane(n, t);
renderer.addPeripheryPlane(n, t, s);
return renderer;
};

View File

@ -9,6 +9,7 @@ import { TextureProvider } from "../../tile/TextureProvider";
import { MeshFactory } from "../util/MeshFactory";
import { MeshScene } from "../util/MeshScene";
import { ProjectorShaderMaterial } from "./interfaces/ProjectorShaderMaterial";
import { GLShader } from "../../shader/Shader";
export class ImageGLRenderer {
private _factory: MeshFactory;
@ -53,8 +54,8 @@ export class ImageGLRenderer {
this._needsRender = true;
}
public addPeripheryPlane(image: Image, transform: Transform): void {
const mesh: THREE.Mesh = this._factory.createMesh(image, transform);
public addPeripheryPlane(image: Image, transform: Transform, shader: GLShader): void {
const mesh: THREE.Mesh = this._factory.createMesh(image, transform, shader);
const planes: { [key: string]: THREE.Mesh; } = {};
planes[image.id] = mesh;
this._scene.addPeripheryPlanes(planes);
@ -68,11 +69,11 @@ export class ImageGLRenderer {
this._needsRender = true;
}
public updateFrame(frame: AnimationFrame): void {
public updateFrame(frame: AnimationFrame, shader: GLShader): void {
this._updateFrameId(frame.id);
this._needsRender = this._updateAlpha(frame.state.alpha) || this._needsRender;
this._needsRender = this._updateAlphaOld(frame.state.alpha) || this._needsRender;
this._needsRender = this._updateImagePlanes(frame.state) || this._needsRender;
this._needsRender = this._updateImagePlanes(frame.state, shader) || this._needsRender;
}
public setTextureProvider(key: string, provider: TextureProvider): void {
@ -129,7 +130,7 @@ export class ImageGLRenderer {
const plane: THREE.Mesh = planes[key];
let material: ProjectorShaderMaterial = <ProjectorShaderMaterial>plane.material;
let texture: THREE.Texture = <THREE.Texture>material.uniforms.projectorTex.value;
let texture: THREE.Texture = <THREE.Texture>material.uniforms.map.value;
texture.image = imageElement;
texture.needsUpdate = true;
@ -228,7 +229,7 @@ export class ImageGLRenderer {
return true;
}
private _updateImagePlanes(state: IAnimationState): boolean {
private _updateImagePlanes(state: IAnimationState, shader: GLShader): boolean {
if (state.currentImage == null ||
state.currentImage.id === this._currentKey) {
return false;
@ -250,7 +251,10 @@ export class ImageGLRenderer {
if (previousKey != null) {
if (previousKey !== this._currentKey && previousKey !== this._previousKey) {
let previousMesh: THREE.Mesh =
this._factory.createMesh(state.previousImage, state.previousTransform);
this._factory.createMesh(
state.previousImage,
state.previousTransform,
shader);
const previousPlanes: { [key: string]: THREE.Mesh; } = {};
previousPlanes[previousKey] = previousMesh;
@ -264,7 +268,8 @@ export class ImageGLRenderer {
let currentMesh =
this._factory.createMesh(
state.currentImage,
state.currentTransform);
state.currentTransform,
shader);
const planes: { [key: string]: THREE.Mesh; } = {};
planes[currentKey] = currentMesh;
@ -288,11 +293,11 @@ export class ImageGLRenderer {
const plane: THREE.Mesh = planes[key];
let material: ProjectorShaderMaterial = <ProjectorShaderMaterial>plane.material;
let oldTexture: THREE.Texture = <THREE.Texture>material.uniforms.projectorTex.value;
material.uniforms.projectorTex.value = null;
let oldTexture: THREE.Texture = <THREE.Texture>material.uniforms.map.value;
material.uniforms.map.value = null;
oldTexture.dispose();
material.uniforms.projectorTex.value = texture;
material.uniforms.map.value = texture;
}
}

View File

@ -3,11 +3,11 @@ import * as THREE from "three";
import { Transform } from "../../geo/Transform";
import { Image } from "../../graph/Image";
import { isFisheye, isSpherical } from "../../geo/Geo";
import { IUniform, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from "three";
import { IUniform, Material, Matrix3, Matrix4, ShaderMaterial, Vector2, Vector3, Vector4 } from "three";
import { Camera } from "../../geometry/Camera";
import { resolveExpands, resolveIncludes } from "../../shader/Resolver";
import { Shader } from "../../shader/Shader";
import { GLShader } from "../../shader/Shader";
function makeCameraUniforms(camera: Camera): { [key: string]: IUniform; } {
const cameraUniforms: { [key: string]: IUniform; } = {};
@ -79,60 +79,60 @@ export class MeshFactory {
this._imageSphereRadius = imageSphereRadius != null ? imageSphereRadius : 200;
}
public createMesh(image: Image, transform: Transform): THREE.Mesh {
public createMesh(image: Image, transform: Transform, shader: GLShader): THREE.Mesh {
const texture = this._createTexture(image.image);
const materialParameters =
this._createMaterialParameters(
transform,
texture,
shader);
const material = new THREE.ShaderMaterial(materialParameters);
if (isSpherical(transform.cameraType)) {
return this._createImageSphere(image, transform);
return this._createImageSphere(image, transform, material);
} else if (isFisheye(transform.cameraType)) {
return this._createImagePlaneFisheye(image, transform);
return this._createImagePlaneFisheye(image, transform, material);
} else {
return this._createImagePlane(image, transform);
return this._createImagePlane(image, transform, material);
}
}
private _createImageSphere(image: Image, transform: Transform): THREE.Mesh {
let texture: THREE.Texture = this._createTexture(image.image);
let materialParameters: THREE.ShaderMaterialParameters = this._createDefaultMaterialParameters(transform, texture);
let material: THREE.ShaderMaterial = new THREE.ShaderMaterial(materialParameters);
let mesh: THREE.Mesh = this._useMesh(transform, image) ?
new THREE.Mesh(this._getImageSphereGeo(transform, image), material) :
new THREE.Mesh(this._getFlatImageSphereGeo(transform), material);
return mesh;
private _createImageSphere(
image: Image,
transform: Transform,
material: ShaderMaterial): THREE.Mesh {
const geometry = this._useMesh(transform, image) ?
this._getImageSphereGeo(transform, image) :
this._getFlatImageSphereGeo(transform);
return new THREE.Mesh(geometry, material);
}
private _createImagePlane(image: Image, transform: Transform): THREE.Mesh {
let texture: THREE.Texture = this._createTexture(image.image);
let materialParameters: THREE.ShaderMaterialParameters = this._createDefaultMaterialParameters(transform, texture);
let material: THREE.ShaderMaterial = new THREE.ShaderMaterial(materialParameters);
let geometry: THREE.BufferGeometry = this._useMesh(transform, image) ?
private _createImagePlane(
image: Image,
transform: Transform,
material: ShaderMaterial): THREE.Mesh {
const geometry = this._useMesh(transform, image) ?
this._getImagePlaneGeo(transform, image) :
this._getRegularFlatImagePlaneGeo(transform);
return new THREE.Mesh(geometry, material);
}
private _createImagePlaneFisheye(image: Image, transform: Transform): THREE.Mesh {
let texture: THREE.Texture = this._createTexture(image.image);
let materialParameters: THREE.ShaderMaterialParameters = this._createDefaultMaterialParameters(transform, texture);
let material: THREE.ShaderMaterial = new THREE.ShaderMaterial(materialParameters);
let geometry: THREE.BufferGeometry = this._useMesh(transform, image) ?
private _createImagePlaneFisheye(image: Image, transform: Transform, material: ShaderMaterial): THREE.Mesh {
const geometry = this._useMesh(transform, image) ?
this._getImagePlaneGeoFisheye(transform, image) :
this._getRegularFlatImagePlaneGeoFisheye(transform);
return new THREE.Mesh(geometry, material);
}
private _createDefaultMaterialParameters(
private _createMaterialParameters(
transform: Transform,
texture: THREE.Texture): THREE.ShaderMaterialParameters {
texture: THREE.Texture,
shader: GLShader): THREE.ShaderMaterialParameters {
const scaleX = Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth;
const scaleY = Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight;
return {
depthWrite: false,
fragmentShader: resolveShader(transform.camera, Shader.texture.fragment),
fragmentShader: resolveShader(transform.camera, shader.fragment),
side: THREE.DoubleSide,
transparent: true,
uniforms: {
@ -142,7 +142,7 @@ export class MeshFactory {
scale: { value: new Vector2(scaleX, scaleY) },
...makeCameraUniforms(transform.camera),
},
vertexShader: resolveShader(transform.camera, Shader.texture.vertex),
vertexShader: resolveShader(transform.camera, shader.vertex),
};
}

View File

@ -2,13 +2,14 @@ import common from "./chunk/common.glsl";
import coordinates from "./chunk/coordinates.glsl";
import bearing_fragment from "./chunk/bearing_fragment.glsl";
import color_fragment from "./chunk/color_fragment.glsl";
import map_color_fragment from "./chunk/map_color_fragment.glsl";
import gl_frag_color_fragment from "./chunk/gl_frag_color_fragment.glsl";
import precision_fragment from "./chunk/precision_fragment.glsl";
import uniforms_fragment from "./chunk/uniforms_fragment.glsl";
import varyings_fragment from "./chunk/varyings_fragment.glsl";
import extrinsic_vertex from "./chunk/extrinsic_vertex.glsl";
import position_vertex from "./chunk/position_vertex.glsl";
import gl_position_vertex from "./chunk/gl_position_vertex.glsl";
import uniforms_vertex from "./chunk/uniforms_vertex.glsl";
import varyings_vertex from "./chunk/varyings_vertex.glsl";
@ -20,14 +21,15 @@ export const ShaderChunk = {
// Fragment
bearing_fragment,
color_fragment,
map_color_fragment,
gl_frag_color_fragment,
precision_fragment,
uniforms_fragment,
varyings_fragment,
// Vertex
extrinsic_vertex,
position_vertex,
gl_position_vertex,
uniforms_vertex,
varyings_vertex,
};

View File

@ -0,0 +1,3 @@
export default /* glsl */`
gl_FragColor = mapColor;
`;

View File

@ -3,13 +3,11 @@ export default /* glsl */`
float u = uv.x;
float v = uv.y;
vec4 baseColor;
vec4 mapColor;
if (u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
baseColor = texture2D(map, vec2(u, v));
baseColor.a = opacity;
mapColor = texture2D(map, vec2(u, v));
mapColor.a = opacity;
} else {
baseColor = vec4(0.0, 0.0, 0.0, 0.0);
mapColor = vec4(0.0, 0.0, 0.0, 0.0);
}
gl_FragColor = baseColor;
`;

View File

@ -5,7 +5,7 @@ export const vertex = /* glsl */`
void main()
{
#include <extrinsic_vertex>
#include <position_vertex>
#include <gl_position_vertex>
}
`;
@ -23,6 +23,7 @@ void main()
{
#include <bearing_fragment>
#expand <project_to_sfm_invocation>
#include <color_fragment>
#include <map_color_fragment>
#include <gl_frag_color_fragment>
}
`;

View File

@ -1,3 +1,14 @@
import {
Observable,
Subject,
Subscription,
} from "rxjs";
import {
publishReplay,
refCount,
startWith,
} from "rxjs/operators";
import {
FisheyeCamera,
FISHEYE_CAMERA_TYPE,
@ -19,7 +30,11 @@ import { GLShader, Shader } from "../shader/Shader";
export class ProjectionService implements ICameraFactory {
private readonly _cameraFactory: { [type: string]: CameraConstructor; } = {};
private _shader: GLShader;
private _shader$: Observable<GLShader>;
private _shaderChanged$: Subject<GLShader>;
private _shaderSubscription: Subscription;
constructor() {
this.registerCamera(
@ -33,6 +48,21 @@ export class ProjectionService implements ICameraFactory {
SphericalCamera);
this._shader = Shader.texture;
this._shaderChanged$ = new Subject<GLShader>();
this._shader$ = this._shaderChanged$.pipe(
startWith(this._shader),
publishReplay(1),
refCount());
this._shaderSubscription = this._shader$.subscribe();
}
public get shader$(): Observable<GLShader> {
return this._shader$;
}
public dispose(): void {
this._shaderSubscription.unsubscribe();
}
public getShader(): GLShader {
@ -57,5 +87,6 @@ export class ProjectionService implements ICameraFactory {
}
this._shader = shader;
this._shaderChanged$.next(this._shader);
}
}

View File

@ -50,6 +50,7 @@ import { ViewerReferenceEvent } from "./events/ViewerReferenceEvent";
import { IDataProvider } from "../external/api";
import { ViewerResetEvent } from "./events/ViewerResetEvent";
import { CameraConstructor } from "../geometry/interfaces/ICamera";
import { GLShader } from "../shader/Shader";
/**
* @class Viewer
@ -1649,6 +1650,10 @@ export class Viewer extends EventEmitter implements IViewer {
this._container.renderService.renderMode$.next(renderMode);
}
public setShader(shader: GLShader): void {
this._navigator.projectionService.setShader(shader);
}
/**
* Set the viewer's transition mode.
*