diff --git a/img/matcap.png b/img/matcap.png new file mode 100644 index 0000000..f34f252 Binary files /dev/null and b/img/matcap.png differ diff --git a/shaders/matcap_frag.glsl b/shaders/matcap_frag.glsl new file mode 100644 index 0000000..5395324 --- /dev/null +++ b/shaders/matcap_frag.glsl @@ -0,0 +1,29 @@ +uniform float opacity; +uniform sampler2D tMatcap; +uniform vec3 color; +varying vec2 vNormal; + +// color blending from https://www.w3.org/TR/compositing-1/#blendingcolor +float lum(vec3 c) { + return c.r * .3 + c.g * .59 + c.b * .11; +} + +vec3 clipColor(vec3 c) { + float l = lum(c); + float n = min(min(c.r, c.g), c.b); + float x = max(max(c.r, c.g), c.b); + if (n < 0.) c = l + (((c - l) * l) / (l - n)); + if (x > 1.) c = l + (((c - l) * (1. - l)) / (x - l)); + return c; +} + +vec3 setLum(vec3 c, float l) { + float d = l - lum(c); + return clipColor(c + d); +} + +void main() { + vec4 matcap = texture2D(tMatcap, vNormal); + vec3 coloredMatcap = setLum(color, lum(matcap.rgb)); + gl_FragColor = vec4(coloredMatcap, opacity); +} diff --git a/shaders/matcap_vert.glsl b/shaders/matcap_vert.glsl new file mode 100644 index 0000000..db6f191 --- /dev/null +++ b/shaders/matcap_vert.glsl @@ -0,0 +1,8 @@ +varying vec2 vNormal; + +void main() { + vNormal = normalize(normalMatrix * normal).xy / 2. + .5; + + #include + #include +} diff --git a/src/components/D3Panel.js b/src/components/D3Panel.js index c62d0d9..080642b 100644 --- a/src/components/D3Panel.js +++ b/src/components/D3Panel.js @@ -20,6 +20,7 @@ import RenderChain from '../d3/RenderChain'; import BaseTransformer from '../d3/transformers/BaseTransformer.js'; import Camera from '../d3/Camera.js'; import ReactResizeDetector from 'react-resize-detector'; +import { load as loadMatcapMaterial } from '../d3/MatcapMaterial.js'; // import createDebug from 'debug'; // const debug = createDebug('d3d:d3'); @@ -74,6 +75,7 @@ class D3Panel extends React.Component { plane: this.plane }); + loadMatcapMaterial.then(this.renderRequest); this.DOM = null; } @@ -117,7 +119,7 @@ class D3Panel extends React.Component { const ambientLight = new THREE.AmbientLight(0x505050); this.scene.add(ambientLight); - this.shapesManager = new ShapesManager({ toonShader: hasExtensionsFor.toonShaderPreview }); + this.shapesManager = new ShapesManager(); this.UIContainer = new EventObject3D(); this.UIContainer.matrixAutoUpdate = false; diff --git a/src/components/DoodlePreview.js b/src/components/DoodlePreview.js index fd188e0..cc1652b 100644 --- a/src/components/DoodlePreview.js +++ b/src/components/DoodlePreview.js @@ -8,6 +8,7 @@ import createScene from '../d3/createScene.js'; import injectSheet from 'react-jss'; import ReactResizeDetector from 'react-resize-detector'; import requestAnimationFrame from 'raf'; +import { load as loadMatcapMaterial } from '../d3/MatcapMaterial.js'; const styles = { container: { @@ -55,7 +56,9 @@ class DoodlePreview extends React.Component { this.setState(scene); this.editorControls = new THREE.EditorControls(scene.camera, canvas); - this.editorControls.addEventListener('change', () => scene.render()); + this.editorControls.addEventListener('change', scene.render); + + loadMatcapMaterial.then(scene.render; } componentWillUnmount() { diff --git a/src/d3/MatcapMaterial.js b/src/d3/MatcapMaterial.js new file mode 100644 index 0000000..3f0eb54 --- /dev/null +++ b/src/d3/MatcapMaterial.js @@ -0,0 +1,44 @@ +import * as THREE from 'three'; +import matcapVert from '../../shaders/matcap_vert.glsl'; +import matcapFrag from '../../shaders/matcap_frag.glsl'; +import matcapURL from '../../img/matcap.png'; + +let matcapTexture; +export const load = new Promise((resolve, reject) => { + matcapTexture = new THREE.TextureLoader().load(matcapURL, resolve, () => {}, reject); +}); + +export default class MatcapMaterial extends THREE.ShaderMaterial { + constructor({ color = new THREE.Color(), opacity = 1 }) { + super({ + uniforms: { + "opacity": { type: 'f', value: opacity }, + "tMatcap": { type: 't', value: matcapTexture }, + "color": { type: 'vec3', value: new THREE.Vector3() } + }, + vertexShader: matcapVert, + fragmentShader: matcapFrag + }); + + this.color = color; + this.side = THREE.DoubleSide; + } + + set color(color) { + this.uniforms.color.value.fromArray(color.toArray()); + return color; + } + + get color() { + return new THREE.Color().fromArray(this.uniforms.color.value.toArray()); + } + + set opacity(opacity) { + if (!this.uniforms) return opacity; + return this.uniforms.opacity.value = opacity; + } + + clone() { + return new MatcapMaterial({ color: this.color, opacity: this.uniforms.opacity.value }); + } +} diff --git a/src/d3/ShapeMesh.js b/src/d3/ShapeMesh.js index 5e51566..afae6a2 100644 --- a/src/d3/ShapeMesh.js +++ b/src/d3/ShapeMesh.js @@ -3,6 +3,7 @@ import { applyMatrixOnPath } from '../utils/vectorUtils.js'; import { shapeToPointsCornered } from '../shape/shapeToPoints.js'; import * as THREE from 'three'; import { getPointsBounds, shapeChanged } from '../shape/shapeDataUtils.js'; +import MatcapMaterial from './MatcapMaterial.js'; import { DESELECT_TRANSPARENCY, LEGACY_HEIGHT_STEP } from '../constants/d3Constants.js'; import ThreeBSP from 'three-js-csg'; @@ -16,32 +17,18 @@ const MAX_HEIGHT_BASE = 5; const isValidNumber = (num) => typeof num === 'number' && !isNaN(num); class ShapeMesh extends THREE.Object3D { - constructor(shapeData, active, toonShader) { + constructor(shapeData, active) { super(); this.name = shapeData.UID; const { sculpt, rotate, twist, height, type, transform, z, color, fill, solid } = shapeData; - let material; - if (toonShader) { - material = new THREE.MeshToonMaterial({ - color: new THREE.Color(color), - shading: THREE.SmoothShading, - side: THREE.DoubleSide - }); - } else { - material = new THREE.MeshLambertMaterial({ - color: new THREE.Color(color), - side: THREE.DoubleSide - }); - } + const material = new MatcapMaterial({ color: new THREE.Color(color) }); this._mesh = new THREE.Mesh(new THREE.BufferGeometry(), material.clone()); this._mesh.name = shapeData.UID; this._mesh.isShapeMesh = true; - this._toonShader = toonShader; - this._shapes = []; this._shapesMap = []; @@ -221,7 +208,8 @@ class ShapeMesh extends THREE.Object3D { throw new Error(`Cannot update object ${this.name}: color is an invalid value.`); } - this._holeMesh.material.color.setHex(color); + this._mesh.material.color = new THREE.Color(color); + this._holeMesh.material.color = new THREE.Color(color); this._color = color; } diff --git a/src/d3/ShapesManager.js b/src/d3/ShapesManager.js index 2823181..4953e20 100644 --- a/src/d3/ShapesManager.js +++ b/src/d3/ShapesManager.js @@ -7,11 +7,9 @@ import ThreeBSP from 'three-js-csg'; const THREE_BSP = ThreeBSP(THREE); export default class ShapesManager extends THREE.Object3D { - constructor({ toonShader }) { + constructor() { super(); - this._toonShader = toonShader; - this._meshes = {}; this._spaces = {}; this.name = 'shapes-manager'; @@ -137,7 +135,7 @@ export default class ShapesManager extends THREE.Object3D { _handleShapeAdded(shapeData, active) { if (!SHAPE_TYPE_PROPERTIES[shapeData.type].D3Visible) return; const { space } = shapeData; - const mesh = new ShapeMesh(shapeData, active, this._toonShader); + const mesh = new ShapeMesh(shapeData, active); this._meshes[shapeData.UID] = { mesh, space }; this._spaces[space].add(mesh);