diff --git a/README.md b/README.md index 191a4bb7..f1f1905b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ## v0.1.1 即将更新 1. 修复mmd动画和音频不同步问题。支持多个mmd模型与模型动画、相机动画同步。 -2. 新增点阵化特效、颜色偏移特效、残影特效、背景虚化、快速近似抗锯齿(FXAA)、毛刺特效。 +2. 新增点阵化特效、颜色偏移特效、残影特效、背景虚化、快速近似抗锯齿(FXAA)、毛刺特效、半色调特效。 3. 新增粒子、预设体、角色面板。(暂未实现具体功能) ## 主要功能 @@ -58,6 +58,8 @@ ![image](images/scene20181223.png) +[点击此处](images/README.md)查看更多截图。 + ## 相关链接 * Three.js官网:https://threejs.org/ diff --git a/ShadowEditor.Web/index.html b/ShadowEditor.Web/index.html index f7d0acf2..30d5a589 100644 --- a/ShadowEditor.Web/index.html +++ b/ShadowEditor.Web/index.html @@ -46,6 +46,9 @@ + + + @@ -56,6 +59,7 @@ + diff --git a/ShadowEditor.Web/src/component/postProcessing/AfterimageComponent.js b/ShadowEditor.Web/src/component/postProcessing/AfterimageComponent.js index 299af11d..81f8f781 100644 --- a/ShadowEditor.Web/src/component/postProcessing/AfterimageComponent.js +++ b/ShadowEditor.Web/src/component/postProcessing/AfterimageComponent.js @@ -113,6 +113,8 @@ AfterimageComponent.prototype.onChange = function () { damp: damp.getValue() }, }); + + this.app.call(`postProcessingChanged`, this); }; export default AfterimageComponent; \ No newline at end of file diff --git a/ShadowEditor.Web/src/component/postProcessing/BokehComponent.js b/ShadowEditor.Web/src/component/postProcessing/BokehComponent.js index b8609f56..45872003 100644 --- a/ShadowEditor.Web/src/component/postProcessing/BokehComponent.js +++ b/ShadowEditor.Web/src/component/postProcessing/BokehComponent.js @@ -145,6 +145,8 @@ BokehComponent.prototype.onChange = function () { maxBlur: maxBlur.getValue(), }, }); + + this.app.call(`postProcessingChanged`, this); }; export default BokehComponent; \ No newline at end of file diff --git a/ShadowEditor.Web/src/component/postProcessing/DotScreenComponent.js b/ShadowEditor.Web/src/component/postProcessing/DotScreenComponent.js index dd194733..cefc86ac 100644 --- a/ShadowEditor.Web/src/component/postProcessing/DotScreenComponent.js +++ b/ShadowEditor.Web/src/component/postProcessing/DotScreenComponent.js @@ -113,6 +113,8 @@ DotScreenComponent.prototype.onChange = function () { scale: scale.getValue(), }, }); + + this.app.call(`postProcessingChanged`, this); }; export default DotScreenComponent; \ No newline at end of file diff --git a/ShadowEditor.Web/src/component/postProcessing/FxaaComponent.js b/ShadowEditor.Web/src/component/postProcessing/FxaaComponent.js index 03beb8ae..938e64ae 100644 --- a/ShadowEditor.Web/src/component/postProcessing/FxaaComponent.js +++ b/ShadowEditor.Web/src/component/postProcessing/FxaaComponent.js @@ -97,6 +97,8 @@ FxaaComponent.prototype.onChange = function () { enabled: enabled.getValue(), }, }); + + this.app.call(`postProcessingChanged`, this); }; export default FxaaComponent; \ No newline at end of file diff --git a/ShadowEditor.Web/src/component/postProcessing/GlitchComponent.js b/ShadowEditor.Web/src/component/postProcessing/GlitchComponent.js index 1c5a60a7..78bad0ce 100644 --- a/ShadowEditor.Web/src/component/postProcessing/GlitchComponent.js +++ b/ShadowEditor.Web/src/component/postProcessing/GlitchComponent.js @@ -112,6 +112,8 @@ GlitchComponent.prototype.onChange = function () { wild: wild.getValue() }, }); + + this.app.call(`postProcessingChanged`, this); }; export default GlitchComponent; \ No newline at end of file diff --git a/ShadowEditor.Web/src/component/postProcessing/HalftoneComponent.js b/ShadowEditor.Web/src/component/postProcessing/HalftoneComponent.js new file mode 100644 index 00000000..ebf6eed1 --- /dev/null +++ b/ShadowEditor.Web/src/component/postProcessing/HalftoneComponent.js @@ -0,0 +1,263 @@ +import BaseComponent from '../BaseComponent'; + +/** + * 半色调特效组件 + * @author tengge / https://github.com/tengge1 + * @param {*} options + */ +function HalftoneComponent(options) { + BaseComponent.call(this, options); + this.selected = null; +} + +HalftoneComponent.prototype = Object.create(BaseComponent.prototype); +HalftoneComponent.prototype.constructor = HalftoneComponent; + +HalftoneComponent.prototype.render = function () { + var data = { + xtype: 'div', + id: 'panel', + scope: this.id, + parent: this.parent, + cls: 'Panel', + style: { + display: 'none' + }, + children: [{ + xtype: 'row', + children: [{ + xtype: 'label', + style: { + color: '#555', + fontWeight: 'bold', + width: '100%' + }, + text: '半色调特效' + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '启用状态' + }, { + xtype: 'checkbox', + id: 'enabled', + scope: this.id, + value: false, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '形状' + }, { + xtype: 'select', + id: 'shape', + scope: this.id, + options: { + 1: '点', + 2: '椭圆', + 3: '线', + 4: '正方形' + }, + value: 1, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '半径' + }, { + xtype: 'number', + id: 'radius', + scope: this.id, + range: [1, 25], + value: 4, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '红色偏转' + }, { + xtype: 'number', + id: 'rotateR', + scope: this.id, + value: 15, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '绿色偏转' + }, { + xtype: 'number', + id: 'rotateG', + scope: this.id, + value: 45, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '蓝色偏转' + }, { + xtype: 'number', + id: 'rotateB', + scope: this.id, + value: 30, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '分散' + }, { + xtype: 'number', + id: 'scatter', + scope: this.id, + value: 0, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '混合' + }, { + xtype: 'number', + id: 'blending', + scope: this.id, + range: [0, 1], + value: 1, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '混合模式' + }, { + xtype: 'select', + id: 'blendingMode', + scope: this.id, + options: { + 1: '线性', + 2: '乘法', + 3: '相加', + 4: '变亮', + 5: '变暗' + }, + value: 1, + onChange: this.onChange.bind(this) + }] + }, { + xtype: 'row', + children: [{ + xtype: 'label', + text: '灰阶' + }, { + xtype: 'checkbox', + id: 'greyscale', + scope: this.id, + value: false, + onChange: this.onChange.bind(this) + }] + }] + }; + + var control = UI.create(data); + control.render(); + + this.app.on(`objectSelected.${this.id}`, this.onObjectSelected.bind(this)); + this.app.on(`objectChanged.${this.id}`, this.onObjectChanged.bind(this)); +}; + +HalftoneComponent.prototype.onObjectSelected = function () { + this.updateUI(); +}; + +HalftoneComponent.prototype.onObjectChanged = function () { + this.updateUI(); +}; + +HalftoneComponent.prototype.updateUI = function () { + var container = UI.get('panel', this.id); + var editor = this.app.editor; + if (editor.selected && editor.selected instanceof THREE.Scene) { + container.dom.style.display = ''; + } else { + container.dom.style.display = 'none'; + return; + } + + this.selected = editor.selected; + + var enabled = UI.get('enabled', this.id); + var shape = UI.get('shape', this.id); + var radius = UI.get('radius', this.id); + var rotateR = UI.get('rotateR', this.id); + var rotateB = UI.get('rotateB', this.id); + var rotateG = UI.get('rotateG', this.id); + var scatter = UI.get('scatter', this.id); + var blending = UI.get('blending', this.id); + var blendingMode = UI.get('blendingMode', this.id); + var greyscale = UI.get('greyscale', this.id); + + var scene = this.selected; + var postProcessing = scene.userData.postProcessing || {}; + + if (postProcessing.halftone) { + enabled.setValue(postProcessing.halftone.enabled); + shape.setValue(postProcessing.halftone.shape); + radius.setValue(postProcessing.halftone.radius); + rotateR.setValue(postProcessing.halftone.rotateR); + rotateB.setValue(postProcessing.halftone.rotateB); + rotateG.setValue(postProcessing.halftone.rotateG); + scatter.setValue(postProcessing.halftone.scatter); + blending.setValue(postProcessing.halftone.blending); + blendingMode.setValue(postProcessing.halftone.blendingMode); + greyscale.setValue(postProcessing.halftone.greyscale); + } +}; + +HalftoneComponent.prototype.onChange = function () { + var enabled = UI.get('enabled', this.id); + var shape = UI.get('shape', this.id); + var radius = UI.get('radius', this.id); + var rotateR = UI.get('rotateR', this.id); + var rotateB = UI.get('rotateB', this.id); + var rotateG = UI.get('rotateG', this.id); + var scatter = UI.get('scatter', this.id); + var blending = UI.get('blending', this.id); + var blendingMode = UI.get('blendingMode', this.id); + var greyscale = UI.get('greyscale', this.id); + + var scene = this.selected; + scene.userData.postProcessing = scene.userData.postProcessing || {}; + + Object.assign(scene.userData.postProcessing, { + halftone: { + enabled: enabled.getValue(), + shape: shape.getValue(), + radius: radius.getValue(), + rotateR: rotateR.getValue(), + rotateB: rotateB.getValue(), + rotateG: rotateG.getValue(), + scatter: scatter.getValue(), + blending: blending.getValue(), + blendingMode: blendingMode.getValue(), + greyscale: greyscale.getValue() + }, + }); + + this.app.call(`postProcessingChanged`, this); +}; + +export default HalftoneComponent; \ No newline at end of file diff --git a/ShadowEditor.Web/src/component/postProcessing/RgbShiftComponent.js b/ShadowEditor.Web/src/component/postProcessing/RgbShiftComponent.js index 141f63bd..6970ad1f 100644 --- a/ShadowEditor.Web/src/component/postProcessing/RgbShiftComponent.js +++ b/ShadowEditor.Web/src/component/postProcessing/RgbShiftComponent.js @@ -113,6 +113,8 @@ RgbShiftComponent.prototype.onChange = function () { amount: amount.getValue() }, }); + + this.app.call(`postProcessingChanged`, this); }; export default RgbShiftComponent; \ No newline at end of file diff --git a/ShadowEditor.Web/src/editor/sidebar/PropertyPanel.js b/ShadowEditor.Web/src/editor/sidebar/PropertyPanel.js index 443b8e49..eed85adf 100644 --- a/ShadowEditor.Web/src/editor/sidebar/PropertyPanel.js +++ b/ShadowEditor.Web/src/editor/sidebar/PropertyPanel.js @@ -33,6 +33,7 @@ import AfterimageComponent from '../../component/postProcessing/AfterimageCompon import BokehComponent from '../../component/postProcessing/BokehComponent'; import FxaaComponent from '../../component/postProcessing/FxaaComponent'; import GlitchComponent from '../../component/postProcessing/GlitchComponent'; +import HalftoneComponent from '../../component/postProcessing/HalftoneComponent'; /** * 属性面板 @@ -71,6 +72,7 @@ PropertyPanel.prototype.render = function () { new BokehComponent({ app: this.app }), new FxaaComponent({ app: this.app }), new GlitchComponent({ app: this.app }), + new HalftoneComponent({ app: this.app }), new SkyComponent({ app: this.app }), new PerlinTerrainComponent({ app: this.app }), new AudioListenerComponent({ app: this.app }), diff --git a/ShadowEditor.Web/src/effect/OutlineEffect.js b/ShadowEditor.Web/src/effect/OutlineEffect.js index 524d3f6f..b9bfc516 100644 --- a/ShadowEditor.Web/src/effect/OutlineEffect.js +++ b/ShadowEditor.Web/src/effect/OutlineEffect.js @@ -10,6 +10,7 @@ function OutlineEffect(app) { this.init(); this.app.on(`sceneLoaded.${this.id}`, this.onSceneLoaded.bind(this)); + this.app.on(`postProcessingChanged.${this.id}`, this.onPostProcessingChanged.bind(this)); }; OutlineEffect.prototype = Object.create(BaseEffect.prototype); @@ -36,39 +37,107 @@ OutlineEffect.prototype.init = function () { } var composer = new THREE.EffectComposer(renderer); + composer.passes.length = 0; - var renderPass1 = new THREE.RenderPass(scene, camera); - renderPass1.clear = true; - composer.addPass(renderPass1); + var effects = []; + var postProcessing = this.app.editor.scene.userData.postProcessing || {}; - var renderPass2 = new THREE.RenderPass(sceneHelpers, camera); - renderPass2.clear = false; - composer.addPass(renderPass2); + var effect = new THREE.RenderPass(scene, camera); + effect.clear = true; + composer.addPass(effect); + effects.push(effect); - var outlinePass = new THREE.OutlinePass(new THREE.Vector2(renderer.domElement.width, renderer.domElement.height), scene, camera); - outlinePass.edgeStrength = params.edgeStrength; - outlinePass.edgeGlow = params.edgeGlow; - outlinePass.edgeThickness = params.edgeThickness; - outlinePass.pulsePeriod = params.pulsePeriod; - // outlinePass.usePatternTexture = true; - outlinePass.visibleEdgeColor.set(params.visibleEdgeColor); - outlinePass.hiddenEdgeColor.set(params.hiddenEdgeColor); - composer.addPass(outlinePass); + effect = new THREE.RenderPass(sceneHelpers, camera); + effect.clear = false; + composer.addPass(effect); + effects.push(effect); - var loader = new THREE.TextureLoader(); + effect = new THREE.OutlinePass(new THREE.Vector2(renderer.domElement.width, renderer.domElement.height), scene, camera); + effect.edgeStrength = 10; + effect.edgeGlow = 0.4; + effect.edgeThickness = 1.8; + effect.pulsePeriod = 2; + effect.visibleEdgeColor.set('#ffffff'); + effect.hiddenEdgeColor.set('#190a05'); + composer.addPass(effect); + effects.push(effect); - // loader.load('assets/textures/tri_pattern.jpg', texture => { - // outlinePass.patternTexture = texture; - // texture.wrapS = THREE.RepeatWrapping; - // texture.wrapT = THREE.RepeatWrapping; - // }); + this.outlinePass = effect; - var effectFXAA = new THREE.ShaderPass(THREE.FXAAShader); - effectFXAA.uniforms['resolution'].value.set(1 / renderer.domElement.width, 1 / renderer.domElement.height); - effectFXAA.renderToScreen = true; - composer.addPass(effectFXAA); + // 后期处理 + if (postProcessing.fxaa && postProcessing.fxaa.enabled) { + effect = new THREE.ShaderPass(THREE.FXAAShader); + effect.uniforms['resolution'].value.set(1 / renderer.domElement.width, 1 / renderer.domElement.height); + composer.addPass(effect); + effects.push(effect); + } + + if (postProcessing.dotScreen && postProcessing.dotScreen.enabled) { + effect = new THREE.ShaderPass(THREE.DotScreenShader); + effect.uniforms['scale'].value = postProcessing.dotScreen.scale; + composer.addPass(effect); + effects.push(effect); + } + + if (postProcessing.rgbShift && postProcessing.rgbShift.enabled) { + effect = new THREE.ShaderPass(THREE.RGBShiftShader); + effect.uniforms['amount'].value = postProcessing.rgbShift.amount; + composer.addPass(effect); + effects.push(effect); + } + + if (postProcessing.afterimage && postProcessing.afterimage.enabled) { + effect = new THREE.AfterimagePass(); + effect.uniforms['damp'].value = postProcessing.afterimage.damp; + composer.addPass(effect); + effects.push(effect); + } + + if (postProcessing.halftone && postProcessing.halftone.enabled) { + effect = new THREE.HalftonePass( + renderer.domElement.width, + renderer.domElement.height, { + shape: postProcessing.halftone.shape, + radius: postProcessing.halftone.radius, + rotateR: postProcessing.halftone.rotateR * (Math.PI / 180), + rotateB: postProcessing.halftone.rotateB * (Math.PI / 180), + rotateG: postProcessing.halftone.rotateG * (Math.PI / 180), + scatter: postProcessing.halftone.scatter, + blending: postProcessing.halftone.blending, + blendingMode: postProcessing.halftone.blendingMode, + greyscale: postProcessing.halftone.greyscale, + }); + composer.addPass(effect); + effects.push(effect); + } + + if (postProcessing.bokeh && postProcessing.bokeh.enabled) { + effect = new THREE.BokehPass(scene, camera, { + focus: postProcessing.bokeh.focus, + aperture: postProcessing.bokeh.aperture / 100000, + maxblur: postProcessing.bokeh.maxBlur, + width: renderer.domElement.width, + height: renderer.domElement.height + }); + composer.addPass(effect); + effects.push(effect); + } + + if (postProcessing.glitch && postProcessing.glitch.enabled) { + effect = new THREE.GlitchPass(); + effect.goWild = postProcessing.glitch.wild; + composer.addPass(effect); + effects.push(effect); + } + + for (var i = 0; i < effects.length; i++) { + if (i === effects.length - 1) { + effects[i].renderToScreen = true; + } else { + effects[i].renderToScreen = false; + } + } - this.outlinePass = outlinePass; this.composer = composer; }; @@ -86,4 +155,8 @@ OutlineEffect.prototype.onSceneLoaded = function () { this.init(); }; +OutlineEffect.prototype.onPostProcessingChanged = function () { + this.init(); +}; + export default OutlineEffect; \ No newline at end of file diff --git a/ShadowEditor.Web/src/event/EventList.js b/ShadowEditor.Web/src/event/EventList.js index c65b28c7..19a92eaf 100644 --- a/ShadowEditor.Web/src/event/EventList.js +++ b/ShadowEditor.Web/src/event/EventList.js @@ -72,6 +72,7 @@ var EventList = [ 'refreshScriptEditor', // 刷新脚本编辑器事件 'sceneLoaded', // 场景载入 + 'postProcessingChanged', // 后期处理设置改变 // 场景编辑区 'transformControlsChange', // 变形控件改变 diff --git a/ShadowEditor.Web/src/player/component/PlayerRenderer.js b/ShadowEditor.Web/src/player/component/PlayerRenderer.js index 1ee5f8a1..9bdeaa33 100644 --- a/ShadowEditor.Web/src/player/component/PlayerRenderer.js +++ b/ShadowEditor.Web/src/player/component/PlayerRenderer.js @@ -55,6 +55,24 @@ PlayerRenderer.prototype.create = function (scene, camera, renderer) { effects.push(effect); } + if (postProcessing.halftone && postProcessing.halftone.enabled) { + effect = new THREE.HalftonePass( + renderer.domElement.width, + renderer.domElement.height, { + shape: postProcessing.halftone.shape, + radius: postProcessing.halftone.radius, + rotateR: postProcessing.halftone.rotateR * (Math.PI / 180), + rotateB: postProcessing.halftone.rotateB * (Math.PI / 180), + rotateG: postProcessing.halftone.rotateG * (Math.PI / 180), + scatter: postProcessing.halftone.scatter, + blending: postProcessing.halftone.blending, + blendingMode: postProcessing.halftone.blendingMode, + greyscale: postProcessing.halftone.greyscale, + }); + composer.addPass(effect); + effects.push(effect); + } + if (postProcessing.bokeh && postProcessing.bokeh.enabled) { effect = new THREE.BokehPass(scene, camera, { focus: postProcessing.bokeh.focus, diff --git a/images/README.md b/images/README.md new file mode 100644 index 00000000..8d2e7ef1 --- /dev/null +++ b/images/README.md @@ -0,0 +1,11 @@ +# 更多截图 + +![image](scene20181223.png) + +![image](scene20181215.png) + +![image](scene20181007.png) + +![image](scene20180919.png) + +![image](scene20180917.png) \ No newline at end of file diff --git a/images/history/scene20180917.png b/images/scene20180917.png similarity index 100% rename from images/history/scene20180917.png rename to images/scene20180917.png diff --git a/images/history/scene20180919.png b/images/scene20180919.png similarity index 100% rename from images/history/scene20180919.png rename to images/scene20180919.png diff --git a/images/history/scene20181007.png b/images/scene20181007.png similarity index 100% rename from images/history/scene20181007.png rename to images/scene20181007.png