diff --git a/packages/troika-3d-text/src/facade/Text3DFacade.js b/packages/troika-3d-text/src/facade/Text3DFacade.js index ad9e8a31..bb47578a 100644 --- a/packages/troika-3d-text/src/facade/Text3DFacade.js +++ b/packages/troika-3d-text/src/facade/Text3DFacade.js @@ -40,6 +40,7 @@ export const TEXT_MESH_PROPS = [ 'sdfGlyphSize', 'unicodeFontsURL', 'gpuAccelerateSDF', + 'fillOutline', 'debugSDF' ] diff --git a/packages/troika-examples/text/TextExample.jsx b/packages/troika-examples/text/TextExample.jsx index 5e2dadbf..c13c40c7 100644 --- a/packages/troika-examples/text/TextExample.jsx +++ b/packages/troika-examples/text/TextExample.jsx @@ -142,6 +142,7 @@ class TextExample extends React.Component { selectable: false, colorRanges: false, sdfGlyphSize: 6, + fillOutline: true, debugSDF: false } @@ -221,6 +222,7 @@ class TextExample extends React.Component { anchorY: state.anchorY, selectable: state.selectable, debugSDF: state.debugSDF, + fillOutline: state.fillOutline, fillOpacity: state.fillOpacity, outlineWidth: state.outlineWidth, outlineOffsetX: state.outlineOffsetX, @@ -355,6 +357,7 @@ class TextExample extends React.Component { {type: 'number', path: "strokeWidth", min: 0, max: 0.01, step: 0.0001}, {type: 'number', path: "sdfGlyphSize", label: 'SDF size (2^n):', min: 3, max: 8}, + {type: 'boolean', path: "fillOutline", label: "Fill Outline"}, {type: 'boolean', path: "debugSDF", label: "Show SDF"}, ] } diff --git a/packages/troika-three-text/README.md b/packages/troika-three-text/README.md index 0e675ce6..58f22c5d 100644 --- a/packages/troika-three-text/README.md +++ b/packages/troika-three-text/README.md @@ -181,6 +181,12 @@ When `true`, the SDF generation process will be GPU-accelerated with WebGL when Default: `true` +### `fillOutline` + +When `true`, the outline is drawn under the fill as well as around it. When `false`, the outline is only drawn where there is no fill, like a halo. Defaults to `true`. Setting this to `false` will produce cleaner edges and more readable text when the text appears small on screen. + +Default: `true` + ### `letterSpacing` Sets a uniform adjustment to spacing between letters after kerning is applied, in local world units. Positive numbers increase spacing and negative numbers decrease it. diff --git a/packages/troika-three-text/src/BatchedText.js b/packages/troika-three-text/src/BatchedText.js index f81c90f2..fe276b57 100644 --- a/packages/troika-three-text/src/BatchedText.js +++ b/packages/troika-three-text/src/BatchedText.js @@ -19,14 +19,16 @@ Data texture packing strategy: 24: diffuse (color/outlineColor) 25: uTroikaFillOpacity (fillOpacity/outlineOpacity) 26: uTroikaCurveRadius -27: # Main: +27: unused 28: uTroikaStrokeWidth 29: uTroikaStrokeColor 30: uTroikaStrokeOpacity +31: unused # Outline: +27: fillOutline (0/1) 28-29: uTroikaPositionOffset 30: uTroikaEdgeOffset 31: uTroikaBlurRadius @@ -210,6 +212,7 @@ export class BatchedText extends Text { uTroikaStrokeOpacity, uTroikaFillOpacity, uTroikaCurveRadius, + uTroikaFillOutline } = material.uniforms; // Total bounds for uv @@ -237,6 +240,7 @@ export class BatchedText extends Text { if (isOutline) { // Outline properties + setTexData(startIndex + 27, uTroikaFillOutline.value); setTexData(startIndex + 28, uTroikaPositionOffset.value.x); setTexData(startIndex + 29, uTroikaPositionOffset.value.y); setTexData(startIndex + 30, uTroikaEdgeOffset.value); @@ -415,6 +419,7 @@ function createBatchedTextMaterial (baseMaterial) { 'uTroikaStrokeOpacity', 'uTroikaFillOpacity', 'uTroikaCurveRadius', + 'uTroikaFillOutline', 'diffuse' ] varyingUniforms.forEach(uniformName => { @@ -444,6 +449,7 @@ function createBatchedTextMaterial (baseMaterial) { diffuse = troikaFloatToColor(data.x); uTroikaFillOpacity = data.y; uTroikaCurveRadius = data.z; + uTroikaFillOutline = data.w; data = troikaBatchTexel(7.0); if (uTroikaIsOutline) { diff --git a/packages/troika-three-text/src/Text.js b/packages/troika-three-text/src/Text.js index b4b9977d..7f03d48e 100644 --- a/packages/troika-three-text/src/Text.js +++ b/packages/troika-three-text/src/Text.js @@ -398,6 +398,14 @@ class Text extends Mesh { */ this.gpuAccelerateSDF = true + /** + * @member {boolean} fillOutline + * When `true`, the outline is drawn under the fill as well as around it. When `false`, the outline + * is only drawn where there is no fill, like a halo. Defaults to `true`. + * Setting this to `false` will produce cleaner edges and more readable text when the text appears small on screen. + */ + this.fillOutline = true + this.debugSDF = false } @@ -663,6 +671,7 @@ class Text extends Mesh { this.geometry.applyClipRect(uniforms.uTroikaClipRect.value) } uniforms.uTroikaSDFDebug.value = !!this.debugSDF + uniforms.uTroikaFillOutline.value = this.fillOutline ? 1 : 0 material.polygonOffset = !!this.depthOffset material.polygonOffsetFactor = material.polygonOffsetUnits = this.depthOffset || 0 diff --git a/packages/troika-three-text/src/TextDerivedMaterial.js b/packages/troika-three-text/src/TextDerivedMaterial.js index 576de108..88ac98d3 100644 --- a/packages/troika-three-text/src/TextDerivedMaterial.js +++ b/packages/troika-three-text/src/TextDerivedMaterial.js @@ -84,6 +84,7 @@ uniform vec3 uTroikaStrokeColor; uniform float uTroikaStrokeWidth; uniform float uTroikaStrokeOpacity; uniform bool uTroikaSDFDebug; +uniform float uTroikaFillOutline; varying vec2 vTroikaGlyphUV; varying vec4 vTroikaTextureUVBounds; varying float vTroikaTextureChannel; @@ -194,6 +195,11 @@ gl_FragColor = mix(fillRGBA, strokeRGBA, smoothstep( fragDistance )); gl_FragColor.a *= edgeAlpha; +// if is outline pass and outline only, "discard" where fill would be +if (uTroikaFillOutline == 0.0 && uTroikaEdgeOffset > 0.0) { + float innerAlpha = 1.0 - troikaGetEdgeAlpha(fragDistance, 0.0, max(aaDist, uTroikaBlurRadius)); + gl_FragColor.a *= innerAlpha; +} #endif if (edgeAlpha == 0.0) { @@ -228,7 +234,8 @@ export function createTextDerivedMaterial(baseMaterial) { uTroikaStrokeOpacity: {value: 1}, uTroikaOrient: {value: new Matrix3()}, uTroikaUseGlyphColors: {value: true}, - uTroikaSDFDebug: {value: false} + uTroikaSDFDebug: {value: false}, + uTroikaFillOutline: {value: 0} }, vertexDefs: VERTEX_DEFS, vertexTransform: VERTEX_TRANSFORM,