diff --git a/Shaders/common/mask_apply.frag.glsl b/Shaders/common/mask_apply.frag.glsl new file mode 100644 index 00000000..d598edca --- /dev/null +++ b/Shaders/common/mask_apply.frag.glsl @@ -0,0 +1,10 @@ +#version 330 +uniform sampler2D tex0; +uniform sampler2D texa; +in vec2 texCoord; +out vec4 FragColor; +void main() { + vec4 col0 = textureLod(tex0, texCoord, 0); + float mask = textureLod(texa, texCoord, 0).r; + FragColor = vec4(col0.rgb, col0.a * mask); +} diff --git a/Shaders/common/mask_merge.frag.glsl b/Shaders/common/mask_merge.frag.glsl index d598edca..f9b6649d 100644 --- a/Shaders/common/mask_merge.frag.glsl +++ b/Shaders/common/mask_merge.frag.glsl @@ -1,10 +1,59 @@ #version 330 uniform sampler2D tex0; uniform sampler2D texa; +uniform float opac; +uniform int blending; in vec2 texCoord; out vec4 FragColor; void main() { - vec4 col0 = textureLod(tex0, texCoord, 0); - float mask = textureLod(texa, texCoord, 0).r; - FragColor = vec4(col0.rgb, col0.a * mask); + float col0 = textureLod(tex0, texCoord, 0).r; + float cola = textureLod(texa, texCoord, 0).r; + float str = opac; + float outColor = 0.0; + if (blending == 0) { // Mix + outColor = mix(cola, col0, str); + } + else if (blending == 1) { // Darken + outColor = mix(cola, min(cola, col0), str); + } + else if (blending == 2) { // Multiply + outColor = mix(cola, cola * col0, str); + } + else if (blending == 3) { // Burn + outColor = mix(cola, 1.0 - (1.0 - cola) / col0, str); + } + else if (blending == 4) { // Lighten + outColor = max(cola, col0 * str); + } + else if (blending == 5) { // Screen + outColor = (1.0 - ((1.0 - str) + str * (1.0 - col0)) * (1.0 - cola)); + } + else if (blending == 6) { // Dodge + outColor = mix(cola, cola / (1.0 - col0), str); + } + else if (blending == 7) { // Add + outColor = mix(cola, cola + col0, str); + } + else if (blending == 8) { // Overlay + outColor = mix(cola, cola < 0.5 ? 2.0 * cola * col0 : 1.0 - 2.0 * (1.0 - cola) * (1.0 - col0), str); + } + else if (blending == 9) { // Soft Light + outColor = ((1.0 - str) * cola + str * ((1.0 - cola) * col0 * cola + cola * (1.0 - (1.0 - col0) * (1.0 - cola)))); + } + else if (blending == 10) { // Linear Light + outColor = (cola + str * (2.0 * (col0 - 0.5))); + } + else if (blending == 11) { // Difference + outColor = mix(cola, abs(cola - col0), str); + } + else if (blending == 12) { // Subtract + outColor = mix(cola, cola - col0, str); + } + else if (blending == 13) { // Divide + outColor = (1.0 - str) * cola + str * cola / col0; + } + else { // Hue, Saturation, Color, Value + outColor = mix(cola, col0, str); + } + FragColor = vec4(outColor, outColor, outColor, 1.0); } diff --git a/Sources/arm/Layers.hx b/Sources/arm/Layers.hx index 5ebb2f89..872cce43 100644 --- a/Sources/arm/Layers.hx +++ b/Sources/arm/Layers.hx @@ -34,7 +34,8 @@ class Layers { public static var pipeCopyBGRA: PipelineState; public static var pipeCopyRGB: PipelineState = null; public static var pipeInvert8: PipelineState; - public static var pipeMask: PipelineState; + public static var pipeApplyMask: PipelineState; + public static var pipeMergeMask: PipelineState; public static var tex0: TextureUnit; public static var tex1: TextureUnit; public static var texmask: TextureUnit; @@ -43,6 +44,10 @@ class Layers { public static var blending: ConstantLocation; public static var tex0Mask: TextureUnit; public static var texaMask: TextureUnit; + public static var tex0MergeMask: TextureUnit; + public static var texaMergeMask: TextureUnit; + public static var opacMergeMask: ConstantLocation; + public static var blendingMergeMask: ConstantLocation; public static var tempImage: Image = null; public static var tempMaskImage: Image = null; public static var expa: Image = null; @@ -210,15 +215,27 @@ class Layers { pipeCopy8.colorAttachments[0] = TextureFormat.L8; pipeInvert8.compile(); - pipeMask = new PipelineState(); - pipeMask.vertexShader = kha.Shaders.getVertex("layer_merge.vert"); - pipeMask.fragmentShader = kha.Shaders.getFragment("mask_merge.frag"); + pipeApplyMask = new PipelineState(); + pipeApplyMask.vertexShader = kha.Shaders.getVertex("layer_merge.vert"); + pipeApplyMask.fragmentShader = kha.Shaders.getFragment("mask_apply.frag"); var vs = new VertexStructure(); vs.add("pos", VertexData.Float2); - pipeMask.inputLayout = [vs]; - pipeMask.compile(); - tex0Mask = pipeMask.getTextureUnit("tex0"); - texaMask = pipeMask.getTextureUnit("texa"); + pipeApplyMask.inputLayout = [vs]; + pipeApplyMask.compile(); + tex0Mask = pipeApplyMask.getTextureUnit("tex0"); + texaMask = pipeApplyMask.getTextureUnit("texa"); + + pipeMergeMask = new PipelineState(); + pipeMergeMask.vertexShader = kha.Shaders.getVertex("layer_merge.vert"); + pipeMergeMask.fragmentShader = kha.Shaders.getFragment("mask_merge.frag"); + var vs = new VertexStructure(); + vs.add("pos", VertexData.Float2); + pipeMergeMask.inputLayout = [vs]; + pipeMergeMask.compile(); + tex0MergeMask = pipeMergeMask.getTextureUnit("tex0"); + texaMergeMask = pipeMergeMask.getTextureUnit("texa"); + opacMergeMask = pipeMergeMask.getConstantLocation("opac"); + blendingMergeMask = pipeMergeMask.getConstantLocation("blending"); } public static function makePipeCopyRGB() { @@ -354,7 +371,11 @@ class Layers { // Apply masks var masks = l1.getMasks(); if (masks != null) { - for (m in masks) m.applyMask(); + for (i in 0...masks.length - 1) { + mergeLayer(masks[i + 1], masks[i]); + masks[i].delete(); + } + masks[masks.length - 1].applyMask(); Context.setLayer(l1); } @@ -389,15 +410,13 @@ class Layers { mask = l1masks[0].texpaint; } - if (l1.isMask() || l1.paintBase) { + if (l1.isMask()) { l0.texpaint.g4.begin(); - l0.texpaint.g4.setPipeline(pipeMerge); - l0.texpaint.g4.setTexture(tex0, l1.texpaint); - l0.texpaint.g4.setTexture(tex1, empty); - l0.texpaint.g4.setTexture(texmask, mask); - l0.texpaint.g4.setTexture(texa, tempImage); - l0.texpaint.g4.setFloat(opac, l1.getOpacity()); - l0.texpaint.g4.setInt(blending, l1.blending); + l0.texpaint.g4.setPipeline(pipeMergeMask); + l0.texpaint.g4.setTexture(Layers.tex0MergeMask, l1.texpaint); + l0.texpaint.g4.setTexture(Layers.texaMergeMask, tempImage); + l0.texpaint.g4.setFloat(opacMergeMask, l1.getOpacity()); + l0.texpaint.g4.setInt(blendingMergeMask, l1.blending); l0.texpaint.g4.setVertexBuffer(iron.data.ConstData.screenAlignedVB); l0.texpaint.g4.setIndexBuffer(iron.data.ConstData.screenAlignedIB); l0.texpaint.g4.drawIndexedVertices(); @@ -405,6 +424,21 @@ class Layers { } if (l1.isLayer()) { + if (l1.paintBase) { + l0.texpaint.g4.begin(); + l0.texpaint.g4.setPipeline(pipeMerge); + l0.texpaint.g4.setTexture(tex0, l1.texpaint); + l0.texpaint.g4.setTexture(tex1, empty); + l0.texpaint.g4.setTexture(texmask, mask); + l0.texpaint.g4.setTexture(texa, tempImage); + l0.texpaint.g4.setFloat(opac, l1.getOpacity()); + l0.texpaint.g4.setInt(blending, l1.blending); + l0.texpaint.g4.setVertexBuffer(iron.data.ConstData.screenAlignedVB); + l0.texpaint.g4.setIndexBuffer(iron.data.ConstData.screenAlignedIB); + l0.texpaint.g4.drawIndexedVertices(); + l0.texpaint.g4.end(); + } + tempImage.g2.begin(false); tempImage.g2.pipeline = pipeCopy; tempImage.g2.drawImage(l0.texpaint_nor, 0, 0); @@ -513,10 +547,10 @@ class Layers { tempImage.g2.pipeline = null; tempImage.g2.end(); - // Merge mask + // Apply mask if (iron.data.ConstData.screenAlignedVB == null) iron.data.ConstData.createScreenAlignedData(); l.texpaint.g4.begin(); - l.texpaint.g4.setPipeline(Layers.pipeMask); + l.texpaint.g4.setPipeline(Layers.pipeApplyMask); l.texpaint.g4.setTexture(Layers.tex0Mask, tempImage); l.texpaint.g4.setTexture(Layers.texaMask, m.texpaint); l.texpaint.g4.setVertexBuffer(iron.data.ConstData.screenAlignedVB); diff --git a/Sources/arm/data/LayerSlot.hx b/Sources/arm/data/LayerSlot.hx index 6fd556d7..ba4b9096 100644 --- a/Sources/arm/data/LayerSlot.hx +++ b/Sources/arm/data/LayerSlot.hx @@ -90,7 +90,7 @@ class LayerSlot { else { // Mask name = "Mask " + (id + 1); var format = "RGBA32"; // Full bits for undo support, R8 is used - blending = BlendDarken; + blending = BlendAdd; { var t = new RenderTargetRaw(); diff --git a/Sources/arm/io/ExportTexture.hx b/Sources/arm/io/ExportTexture.hx index 25d57aa1..74042806 100644 --- a/Sources/arm/io/ExportTexture.hx +++ b/Sources/arm/io/ExportTexture.hx @@ -174,11 +174,10 @@ class ExportTexture { if (l1masks != null && !bakeMaterial) { if (l1masks.length > 1) { Layers.makeTempMaskImg(); - Layers.tempMaskImage.g2.begin(false); - Layers.tempMaskImage.g2.drawImage(l1masks[0].texpaint, 0, 0); + Layers.tempMaskImage.g2.begin(true, 0x00000000); Layers.tempMaskImage.g2.end(); var l1 = { texpaint: Layers.tempMaskImage }; - for (i in 1...l1masks.length) { + for (i in 0...l1masks.length) { Layers.mergeLayer(untyped l1, l1masks[i]); } mask = Layers.tempMaskImage; diff --git a/Sources/arm/node/MakeMaterial.hx b/Sources/arm/node/MakeMaterial.hx index 20399148..3d798f2d 100644 --- a/Sources/arm/node/MakeMaterial.hx +++ b/Sources/arm/node/MakeMaterial.hx @@ -440,6 +440,54 @@ class MakeMaterial { } } + public static function blendModeMask(frag: NodeShader, blending: Int, cola: String, colb: String, opac: String): String { + if (blending == BlendMix) { + return 'mix($cola, $colb, $opac)'; + } + else if (blending == BlendDarken) { + return 'mix($cola, min($cola, $colb), $opac)'; + } + else if (blending == BlendMultiply) { + return 'mix($cola, $cola * $colb, $opac)'; + } + else if (blending == BlendBurn) { + return 'mix($cola, 1.0 - (1.0 - $cola) / $colb, $opac)'; + } + else if (blending == BlendLighten) { + return 'max($cola, $colb * $opac)'; + } + else if (blending == BlendScreen) { + return '(1.0 - ((1.0 - $opac) + $opac * (1.0 - $colb)) * (1.0 - $cola))'; + } + else if (blending == BlendDodge) { + return 'mix($cola, $cola / (1.0 - $colb), $opac)'; + } + else if (blending == BlendAdd) { + return 'mix($cola, $cola + $colb, $opac)'; + } + else if (blending == BlendOverlay) { + return 'mix($cola, $cola < 0.5 ? 2.0 * $cola * $colb : 1.0 - 2.0 * (1.0 - $cola) * (1.0 - $colb), $opac)'; + } + else if (blending == BlendSoftLight) { + return '((1.0 - $opac) * $cola + $opac * ((1.0 - $cola) * $colb * $cola + $cola * (1.0 - (1.0 - $colb) * (1.0 - $cola))))'; + } + else if (blending == BlendLinearLight) { + return '($cola + $opac * (2.0 * ($colb - 0.5)))'; + } + else if (blending == BlendDifference) { + return 'mix($cola, abs($cola - $colb), $opac)'; + } + else if (blending == BlendSubtract) { + return 'mix($cola, $cola - $colb, $opac)'; + } + else if (blending == BlendDivide) { + return '(1.0 - $opac) * $cola + $opac * $cola / $colb'; + } + else { // BlendHue, BlendSaturation, BlendColor, BlendValue + return 'mix($cola, $colb, $opac)'; + } + } + public static inline function getDisplaceStrength():Float { var sc = Context.mainObject().transform.scale.x; return Config.raw.displace_strength * 0.02 * sc; diff --git a/Sources/arm/node/MakeMesh.hx b/Sources/arm/node/MakeMesh.hx index aec1524f..9cfd8ab0 100644 --- a/Sources/arm/node/MakeMesh.hx +++ b/Sources/arm/node/MakeMesh.hx @@ -202,14 +202,24 @@ class MakeMesh { var masks = l.getMasks(); if (masks != null) { - var texpaint_mask = 'texpaint_mask' + l.id; - frag.write('float $texpaint_mask = 1.0;'); + var hasVisible = false; for (m in masks) { - if (!m.isVisible()) continue; - frag.add_shared_sampler('sampler2D texpaint' + m.id); - frag.write('$texpaint_mask -= 1.0 - textureLodShared(texpaint' + m.id + ', texCoord, 0.0).r;'); + if (m.isVisible()) { + hasVisible = true; + break; + } + } + if (hasVisible) { + var texpaint_mask = 'texpaint_mask' + l.id; + frag.write('float $texpaint_mask = 0.0;'); + for (m in masks) { + if (!m.isVisible()) continue; + frag.add_shared_sampler('sampler2D texpaint' + m.id); + frag.write('float texpaint_mask_sample' + m.id + ' = textureLodShared(texpaint' + m.id + ', texCoord, 0.0).r;'); + frag.write('$texpaint_mask = ' + MakeMaterial.blendModeMask(frag, m.blending, '$texpaint_mask', 'texpaint_mask_sample' + m.id, m.getOpacity() + "") + ';'); + } + frag.write('texpaint_opac *= clamp($texpaint_mask, 0.0, 1.0);'); } - frag.write('texpaint_opac *= max($texpaint_mask, 0.0);'); } if (l.getOpacity() < 1) {