diff --git a/shaders/anaglyph_frag.glsl b/shaders/anaglyph_frag.glsl new file mode 100644 index 0000000..114e311 --- /dev/null +++ b/shaders/anaglyph_frag.glsl @@ -0,0 +1,36 @@ +uniform sampler2D mapLeft; +uniform sampler2D mapRight; +varying vec2 vUv; + +uniform mat3 colorMatrixLeft; +uniform mat3 colorMatrixRight; + +float lin( float c ) { + return c <= 0.04045 ? c * 0.0773993808 : + pow( c * 0.9478672986 + 0.0521327014, 2.4 ); +} + +vec4 lin( vec4 c ) { + return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a ); +} + +float dev( float c ) { + return c <= 0.0031308 ? c * 12.92 : pow( c, 0.41666 ) * 1.055 - 0.055; +} + +void main() { + + vec2 uv = vUv; + + vec4 colorL = lin( texture2D( mapLeft, uv ) ); + vec4 colorR = lin( texture2D( mapRight, uv ) ); + + vec3 color = clamp( + colorMatrixLeft * colorL.rgb + + colorMatrixRight * colorR.rgb, 0., 1. ); + + gl_FragColor = vec4( + dev( color.r ), dev( color.g ), dev( color.b ), + max( colorL.a, colorR.a ) ); + +} diff --git a/shaders/anaglyph_vert.glsl b/shaders/anaglyph_vert.glsl new file mode 100644 index 0000000..610d70b --- /dev/null +++ b/shaders/anaglyph_vert.glsl @@ -0,0 +1,5 @@ +varying vec2 vUv; +void main() { + vUv = vec2( uv.x, uv.y ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); +} diff --git a/src/components/D3Panel.js b/src/components/D3Panel.js index 52f18fc..2a043dd 100644 --- a/src/components/D3Panel.js +++ b/src/components/D3Panel.js @@ -16,7 +16,7 @@ import TwistTransformer from '../d3/transformers/TwistTransformer.js'; import SculptTransformer from '../d3/transformers/SculptTransformer.js'; import StampTransformer from '../d3/transformers/StampTransformer.js'; import SelectionBox from '../d3/SelectionBox.js'; -import RenderChain from '../d3/RenderChain'; +import RenderChain, { TOONSHADER_OUTLINE, TOONSHADER } from '../d3/RenderChain'; import BaseTransformer from '../d3/transformers/BaseTransformer.js'; import Camera from '../d3/Camera.js'; import ReactResizeDetector from 'react-resize-detector'; @@ -62,8 +62,8 @@ class D3Panel extends React.Component { componentWillMount() { this.createScene(); - const toonShader = hasExtensionsFor.toonShaderPreview; - this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, toonShader, { + const shader = hasExtensionsFor.toonShaderPreview ? TOONSHADER_OUTLINE : TOONSHADER; + this.renderChain = new RenderChain(this.renderer, this.scene, this.camera, shader, { UI: this.UIContainer, shapes: this.shapesManager, boundingBox: this.selectionBox, diff --git a/src/d3/RenderChain.js b/src/d3/RenderChain.js index 0d5b1cb..dccfa34 100644 --- a/src/d3/RenderChain.js +++ b/src/d3/RenderChain.js @@ -1,43 +1,62 @@ import * as THREE from 'three'; -import OutlinePass from './OutlinePass.js'; -import RenderPass from './RenderPass.js'; +import OutlinePass from './effects/OutlinePass.js'; +import RenderPass from './effects/RenderPass.js'; +import AnaglyphPass from './effects/AnaglyphPass.js'; import 'three/examples/js/shaders/CopyShader.js'; import 'three/examples/js/postprocessing/EffectComposer.js'; import 'three/examples/js/postprocessing/ShaderPass.js'; +export const TOONSHADER_OUTLINE = 'toonshader-outline'; +export const ANAGLYPH = 'anaglyph'; +export const TOONSHADER = 'toonshader'; + export default class RenderChain extends THREE.EffectComposer { - constructor(renderer, scene, camera, toonShader, groups) { + constructor(renderer, scene, camera, shader, groups) { super(renderer); this._groups = groups; - if (toonShader) { - const renderPass = new RenderPass(scene, camera, () => { - this._setVisible(this._initalValues, [groups.shapes, groups.plane, groups.boundingBox]); - }); - this.addPass(renderPass); + switch (shader) { + case TOONSHADER_OUTLINE: { + const renderPass = new RenderPass(scene, camera, () => { + this._setVisible(this._initalValues, [groups.shapes, groups.plane, groups.boundingBox]); + }); + this.addPass(renderPass); - const outlinePass = new OutlinePass(scene, camera, () => { - this._setVisible(this._initalValues, [groups.shapes]); - }); - outlinePass.renderToScreen = true; - this.addPass(outlinePass); + const outlinePass = new OutlinePass(scene, camera, () => { + this._setVisible(this._initalValues, [groups.shapes]); + }); + outlinePass.renderToScreen = true; + this.addPass(outlinePass); - const renderPassUI = new RenderPass(scene, camera, () => { - this._setVisible(this._initalValues, [groups.UI]); - }); - renderPassUI.clear = false; - renderPassUI.renderToScreen = true; - this.addPass(renderPassUI); - } else { - const renderPass = new RenderPass(scene, camera); - renderPass.renderToScreen = true; - this.addPass(renderPass); + const renderPassUI = new RenderPass(scene, camera, () => { + this._setVisible(this._initalValues, [groups.UI]); + }); + renderPassUI.clear = false; + renderPassUI.renderToScreen = true; + this.addPass(renderPassUI); + break; + } + + case ANAGLYPH: { + const anaglyphPass = new AnaglyphPass(scene, camera); + anaglyphPass.renderToScreen = true; + this.addPass(anaglyphPass); + break; + } + + case TOONSHADER: + default: { + const renderPass = new RenderPass(scene, camera); + renderPass.renderToScreen = true; + this.addPass(renderPass); + break; + } } this._renderer = renderer; this._camera = camera; this._scene = scene; - this._toonShader = toonShader; + this._shader = shader; } _getCurrentVisibleValues() { @@ -74,13 +93,13 @@ export default class RenderChain extends THREE.EffectComposer { } render() { - if (this._toonShader) { + if (this._shader === TOONSHADER_OUTLINE) { this._initalValues = this._getCurrentVisibleValues(); } super.render(); - if (this._toonShader) { + if (this._shader === TOONSHADER_OUTLINE) { const { shapes, UI, plane, boundingBox } = this._groups; this._setVisible(this._initalValues, [shapes, UI, plane, boundingBox]); } diff --git a/src/d3/createScene.js b/src/d3/createScene.js index 9987e7c..c50e392 100644 --- a/src/d3/createScene.js +++ b/src/d3/createScene.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; import ShapesManager from './ShapesManager.js'; -import RenderChain from './RenderChain.js'; +import RenderChain, { TOONSHADER, TOONSHADER_OUTLINE } from './RenderChain.js'; import { hasExtensionsFor } from '../utils/webGLSupport.js'; import { CANVAS_SIZE } from '../constants/d2Constants.js'; @@ -17,7 +17,7 @@ export default function createScene(state, canvas) { scene.add(camera); - const shapesManager = new ShapesManager({ toonShader: hasExtensionsFor.toonShaderThumbnail }); + const shapesManager = new ShapesManager(); shapesManager.update(state); scene.add(shapesManager); @@ -37,7 +37,8 @@ export default function createScene(state, canvas) { const renderer = new THREE.WebGLRenderer({ canvas, alpha: true }); - const renderChain = new RenderChain(renderer, scene, camera, hasExtensionsFor.toonShaderThumbnail, { + const shader = hasExtensionsFor.toonShaderThumbnail ? TOONSHADER_OUTLINE : TOONSHADER; + const renderChain = new RenderChain(renderer, scene, camera, shader, { plane, UI: new THREE.Object3D(), shapes: shapesManager, diff --git a/src/d3/effects/AnaglyphPass.js b/src/d3/effects/AnaglyphPass.js new file mode 100644 index 0000000..990baf5 --- /dev/null +++ b/src/d3/effects/AnaglyphPass.js @@ -0,0 +1,66 @@ +import * as THREE from 'three'; +import anaglyphVert from '../../../shaders/anaglyph_vert.glsl'; +import anaglyphFrag from '../../../shaders/anaglyph_frag.glsl'; + +const COLOR_MATRIX_LEFT = new THREE.Matrix3().fromArray([ + 1.0671679973602295, -0.0016435992438346148, 0.0001777536963345483, + -0.028107794001698494, -0.00019593400065787137, -0.0002875397040043026, + -0.04279090091586113, 0.000015809757314855233, -0.00024287120322696865 +]); +const COLOR_MATRIX_RIGHT = new THREE.Matrix3().fromArray([ + -0.0355340838432312, -0.06440307199954987, 0.018319187685847282, + -0.10269022732973099, 0.8079727292060852, -0.04835830628871918, + 0.0001224992738571018, -0.009558862075209618, 0.567823588848114 +]); + +export default class AnaglyphPass { + constructor(scene, camera) { + this.scene = scene; + this.camera = camera; + + this.clear = true; + this.renderToScreen = false; + + const params = { + minFilter: THREE.LinearFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat + }; + + this._stereo = new THREE.StereoCamera(); + + this._renderTargetL = new THREE.WebGLRenderTarget(1, 1, params); + this._renderTargetR = new THREE.WebGLRenderTarget(1, 1, params); + + this._material = new THREE.ShaderMaterial({ + uniforms: { + mapLeft: { value: this._renderTargetL.texture }, + mapRight: { value: this._renderTargetR.texture }, + colorMatrixLeft: { value: COLOR_MATRIX_LEFT }, + colorMatrixRight: { value: COLOR_MATRIX_RIGHT } + }, + vertexShader: anaglyphVert, + fragmentShader: anaglyphFrag + }); + + this._camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); + this._scene = new THREE.Scene(); + this._quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this._material); + this._quad.frustumCulled = false; + this._scene.add(this._quad); + } + + setSize(width, height, pixelRatio = 1) { + this._renderTargetL.setSize(width * pixelRatio, height * pixelRatio); + this._renderTargetR.setSize(width * pixelRatio, height * pixelRatio); + } + + render(renderer, writeBuffer, readBuffer, delta, maskActive) { + this.scene.updateMatrixWorld(); + this._stereo.update(this.camera); + + renderer.render(this.scene, this._stereo.cameraL, this._renderTargetL, true); + renderer.render(this.scene, this._stereo.cameraR, this._renderTargetR, true); + renderer.render(this._scene, this._camera, this.renderToScreen ? null : readBuffer, this.clear); + } +} diff --git a/src/d3/OutlinePass.js b/src/d3/effects/OutlinePass.js similarity index 86% rename from src/d3/OutlinePass.js rename to src/d3/effects/OutlinePass.js index 606021f..993077c 100644 --- a/src/d3/OutlinePass.js +++ b/src/d3/effects/OutlinePass.js @@ -1,10 +1,10 @@ import * as THREE from 'three'; -import normalDepthVert from '../../shaders/normal_depth_vert.glsl'; -import normalDepthFrag from '../../shaders/normal_depth_frag.glsl'; -import edgeVert from '../../shaders/edge_vert.glsl'; -import edgeFrag from '../../shaders/edge_frag.glsl'; -import combineVert from '../../shaders/combine_vert.glsl'; -import combineFrag from '../../shaders/combine_frag.glsl'; +import normalDepthVert from '../../../shaders/normal_depth_vert.glsl'; +import normalDepthFrag from '../../../shaders/normal_depth_frag.glsl'; +import edgeVert from '../../../shaders/edge_vert.glsl'; +import edgeFrag from '../../../shaders/edge_frag.glsl'; +import combineVert from '../../../shaders/combine_vert.glsl'; +import combineFrag from '../../../shaders/combine_frag.glsl'; export default class OutlinePass { constructor(scene, camera, callbackBeforeRender) { diff --git a/src/d3/RenderPass.js b/src/d3/effects/RenderPass.js similarity index 100% rename from src/d3/RenderPass.js rename to src/d3/effects/RenderPass.js