diff --git a/js/ThreeCanvas.js b/js/ThreeCanvas.js index 591aadb..cc601bb 100644 --- a/js/ThreeCanvas.js +++ b/js/ThreeCanvas.js @@ -10,7 +10,7 @@ import { LuminosityShader } from './lib/jsm/shaders/LuminosityShader.js'; import { ExposureShader } from './lib/jsm/shaders/ExposureShader.js'; import { BrightnessContrastShader } from './lib/jsm/shaders/BrightnessContrastShader.js'; -import { Outline, Inline, BlackAll, sobelShader, thresholdShader } from "./lib/Toon.js"; +import { Outline, Inline, BlackAll, sobelShader, cannyEdgeShader, thresholdShader } from "./lib/Toon.js"; import { Hub } from "./lib/Hub.js"; import { Files } from "./lib/Files.js"; import { Tools } from "./lib/Tools.js"; @@ -68,7 +68,7 @@ export class ThreeCanvas { createWrappers(canvasNames){ // Operators "Sobel", "Scharr", "Prewitt" - const linesOperators = ["Sobel", "Prewitt", "Scharr"]; + const linesOperators = ["Sobel", "Prewitt", "Scharr", "Canny"]; const listLines = $el("select.linesMethod_box",{ style: { fontSize: "0.5rem", @@ -80,7 +80,8 @@ export class ThreeCanvas { outline: 0 }, onchange: (e)=> { - this.sobelPassRadio = e.target.value + this.sobelPassSelect = e.target.value + this.composer = null this.render() }, title: "Selectd operator for lines" @@ -150,11 +151,11 @@ export class ThreeCanvas { $el("button.threeCanvasButton.threeCanvasViews3", { textContent: "All Views", onclick: (e) => { - this.VIEWS3 = !this.VIEWS3 + this.VIEWS3 = !this.VIEWS3; - this.panelWrapper.querySelector(".threeCanvasViews3_camerafix").style.display = this.VIEWS3 ? "block": "none" + this.panelWrapper.querySelector(".threeCanvasViews3_camerafix").style.display = this.VIEWS3 ? "block": "none"; // threeCanvas.fixCamers = threeCanvas.VIEWS3 - this.setViews3(this.wrappers) + this.setViews3(this.wrappers); }, }), $el("input.threeCanvasViews3_camerafix", { @@ -166,6 +167,7 @@ export class ThreeCanvas { }, onchange: (e)=>{ this.fixCamers = !!e.target.checked + this.composer = null }, })]), ] @@ -368,6 +370,12 @@ export class ThreeCanvas { } setViews3(){ + if( this.VIEWS3 ){ + this.threeWrapper.classList.add("threeWrapper3Views"); + } else { + this.threeWrapper.classList.remove("threeWrapper3Views"); + } + this.wrappers.slice(1).map((v)=> Object.assign(v.style, { display: this.VIEWS3 ? "block" : "none" })) @@ -610,18 +618,17 @@ export class ThreeCanvas { this.render(); } - initComposer(renderer){ + initComposer(renderer, camera){ + const operatorLines = this?.sobelPassSelect ?? 0 // init only one composer if(this.composer){ - // Set changed line type - this.effectSobel.uniforms.lineType.value = this?.sobelPassRadio ?? 0 return; } const composer = new EffectComposer(renderer); - const renderPass = new RenderPass(this.scene, this.camera); + const renderPass = new RenderPass(this.scene, camera); composer.addPass(renderPass); // color to grayscale conversion @@ -633,12 +640,23 @@ export class ThreeCanvas { effectExposure.uniforms[ 'exposure' ].value = 0.75; composer.addPass( effectExposure ) - const effectSobel = new ShaderPass( SobelOperatorShader ); - effectSobel.uniforms[ 'resolution' ].value.x = this.size.w; - effectSobel.uniforms[ 'resolution' ].value.y = this.size.h; + if(operatorLines <= 2){ + const effectSobel = new ShaderPass( SobelOperatorShader ); + effectSobel.uniforms[ 'resolution' ].value.x = this.size.w; + effectSobel.uniforms[ 'resolution' ].value.y = this.size.h; + + // Set changed line type + effectSobel.uniforms.lineType.value = operatorLines - this.effectSobel = effectSobel - composer.addPass( effectSobel ) + composer.addPass( effectSobel ) + + } else if(operatorLines == 3){ + // Canny edge + const effectCannyEdge = new ShaderPass( cannyEdgeShader ); + effectCannyEdge.uniforms.resolution.value.x = this.size.w; + effectCannyEdge.uniforms.resolution.value.y = this.size.h; + composer.addPass( effectCannyEdge ) + } // this.effectSobel = new ShaderPass(sobelShader); // composer.addPass(this.effectSobel); @@ -669,7 +687,7 @@ export class ThreeCanvas { if(this.useLinesComposer){ - this.initComposer(data.renderer) + this.initComposer(data.renderer, camera) return this.composer.render(this.scene) } diff --git a/js/css/styles.css b/js/css/styles.css index 633d6d9..7a1a0ca 100644 --- a/js/css/styles.css +++ b/js/css/styles.css @@ -1,5 +1,11 @@ .threeWrapper { - /* wrapper all canvases */ + display: grid; + grid-template-columns: 1fr; + gap: 5px; +} + +.threeWrapper3Views{ + grid-template-columns: 1fr 1fr; } .threeCanvasPanelWrapper { @@ -35,7 +41,7 @@ } .wrapperThreeViewCanvas { - position: absolute; + position: relative; margin: 0px; padding: 0px; display: block; diff --git a/js/index.html b/js/index.html index 85b5ec4..73b9bf1 100644 --- a/js/index.html +++ b/js/index.html @@ -9,18 +9,33 @@ THREE DIRECT - + + @@ -28,18 +43,34 @@ \ No newline at end of file diff --git a/js/lib/Toon.js b/js/lib/Toon.js index 69e1f09..35f57fe 100644 --- a/js/lib/Toon.js +++ b/js/lib/Toon.js @@ -123,6 +123,119 @@ export const sobelShader = { ` }; +// Canny edge detection shader +export const cannyEdgeShader = { + uniforms: { + tDiffuse: { value: null }, + resolution: { value: new THREE.Vector2() }, + lowThreshold: { value: 0.1 }, + highThreshold: { value: 0.3 } + }, + vertexShader: ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + // Canny edge detection shader (copy the GLSL code here from the previous implementation) + precision highp float; + + uniform sampler2D tDiffuse; + uniform vec2 resolution; + uniform float lowThreshold; + uniform float highThreshold; + + varying vec2 vUv; + + const float kernel[9] = float[9]( + 1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0, + 2.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0, + 1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0 + ); + + void main() { + vec2 texel = vec2(1.0 / resolution.x, 1.0 / resolution.y); + + vec3 blurred = vec3(0.0); + int k = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + blurred += texture2D(tDiffuse, vUv + vec2(i, j) * texel).rgb * kernel[k++]; + } + } + + float gray = dot(blurred, vec3(0.2989, 0.5870, 0.1140)); + + float Gx[9] = float[9]( + -1.0, 0.0, 1.0, + -2.0, 0.0, 2.0, + -1.0, 0.0, 1.0 + ); + float Gy[9] = float[9]( + -1.0, -2.0, -1.0, + 0.0, 0.0, 0.0, + 1.0, 2.0, 1.0 + ); + + float gradientX = 0.0; + float gradientY = 0.0; + k = 0; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + float xsample = texture2D(tDiffuse, vUv + vec2(i, j) * texel).r; + gradientX += xsample * Gx[k]; + gradientY += xsample * Gy[k++]; + } + } + + float magnitude = length(vec2(gradientX, gradientY)); + float direction = atan(gradientY, gradientX); + + float angle = mod(direction + 3.14159265, 3.14159265) / 3.14159265 * 4.0; + float neighbor1 = 0.0; + float neighbor2 = 0.0; + + if (angle <= 1.0 || angle > 3.0) { + neighbor1 = texture2D(tDiffuse, vUv + vec2(texel.x, 0.0)).r; + neighbor2 = texture2D(tDiffuse, vUv - vec2(texel.x, 0.0)).r; + } else if (angle <= 2.0) { + neighbor1 = texture2D(tDiffuse, vUv + vec2(texel.x, -texel.y)).r; + neighbor2 = texture2D(tDiffuse, vUv - vec2(texel.x, -texel.y)).r; + } else { + neighbor1 = texture2D(tDiffuse, vUv + vec2(0.0, texel.y)).r; + neighbor2 = texture2D(tDiffuse, vUv - vec2(0.0, texel.y)).r; + } + + if (magnitude < neighbor1 || magnitude < neighbor2) { + magnitude = 0.0; + } + + float edge = 0.0; + if (magnitude >= highThreshold) { + edge = 1.0; + } else if (magnitude >= lowThreshold) { + edge = 0.5; + } + + if (edge == 0.5) { + bool connected = false; + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (texture2D(tDiffuse, vUv + vec2(i, j) * texel).r >= highThreshold) { + connected = true; + } + } + } + if (!connected) edge = 0.0; + } + + gl_FragColor = vec4(vec3(edge), 1.0); + } + ` +}; + export const thresholdShader = { uniforms: { tDiffuse: { value: null }, diff --git a/js/three_view.js b/js/three_view.js index 3e3c656..c5a9f7f 100644 --- a/js/three_view.js +++ b/js/three_view.js @@ -71,7 +71,7 @@ async function widgetThreeJS(node, nodeData, inputData, app, params = {}) { origDraw?.apply(this, arguments); const [ctx, nodeThree, widgetWidth, posY] = arguments; - const w = !threeCanvas.VIEWS3 ? widgetWidth - 25: (widgetWidth - 25)/2; + const w = widgetWidth - 25; const aspect_ratio = threeCanvas.size.h / threeCanvas.size.w; Object.assign(threeCanvas.threeWrapper.style, { @@ -79,30 +79,6 @@ async function widgetThreeJS(node, nodeData, inputData, app, params = {}) { height: w * aspect_ratio + "px", }); - if( threeCanvas.VIEWS3 ){ - Object.assign(threeCanvas.getWrapper(1).style, { - width: w + "px", - height: w * aspect_ratio + "px", - left: w + 10 + "px" - }); - - Object.assign(threeCanvas.getWrapper(2).style, { - width: w + "px", - height: w * aspect_ratio + "px", - top: parseFloat(threeCanvas.getWrapper(1).style.height) + 10 + "px" - - }); - - Object.assign(threeCanvas.getWrapper(3).style, { - width: w + "px", - height: w * aspect_ratio + "px", - left: w + 10 + "px", - top: parseFloat(threeCanvas.getWrapper(1).style.height) + 10 + "px" - - }); - } - - // Update renderer // threeCanvas.update(node.size[0]); //app.graph.setDirtyCanvas(true, false);