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 @@

+[点击此处](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 @@
+# 更多截图
+
+
+
+
+
+
+
+
+
+
\ 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