An example of text to canvas to texture to material to mesh. Not too difficult

I think there is a need for an example of creating text by writing the text on a canvas, then creating a texture from the canvas, then using the texture in a material. It sounds complicated – I spent a lot of time getting this together – but it works well. There is a routine (dcText) which should be of help (actually it does it all).
I tried to offer this to mrdoob’s github (issue #18698), but I didn’t sell it very well.

<!doctype html>
<html>
<!-- Dave Dbarc - this is freely given to threejs.org & https://github.com/mrdoob/three.js
     in the hopes that they will modify it to their taste as a text via texture example,
     and reference it in https://threejs.org/docs/#manual/en/introduction/Creating-text (item#2).
-->
  <head>
    <title> threejs Text via Texture Example </title>
    <style type="text/css">
    </style>
    <script src="http://threejs.org/build/three.js"></script>
    <script type="text/javascript">
  "use strict";
  var js3canvas, js3w, js3h;
  var renderer, camera, world, piggy, geometry, material, mesh;
window.onload = function() {
  init3js(); // the standard 3js stuff
  // ======
  // The call is dcText(text, worldTextHeight, worldRectangleHeight, fontPixelHeight, fgcolor, bgcolor);.
  // The widths are calculated and returned if desired.
  // bg is transparent if omitted.
  // ====== text#1, "Hello, world" ====== shows basic call
  mesh = dcText("Hello, world", 15, 20, 50, 0x000000, 0xcccccc); // text #1, Hello, world
  world.add(mesh);
  var tmp = mesh.wWorldAll; // is width of rectangle; for later use
  // text#2, "TRANSPARENT"         ====== transparent
  mesh = dcText("TRANSPARENT", 10, 10, 50, 0xff00ff);      // text #2, TRANSPARENT
  mesh.position.set(0,-12,1); // move geometry up and out
  world.add(mesh);
  // ====== text#3, "XpiggyX"      ====== shows you can customize 
  piggy = new THREE.Group(); // for rotating sign
  world.add(piggy);
  mesh = dcText("XpiggyX", 15, 20, 50, 0xffffff, 0x0000ff); // text #3, XpiggyX (descenders)
  mesh.ctx.lineWidth = 3; // can add glitter to texture here
  mesh.ctx.strokeStyle = "white";
  mesh.ctx.strokeRect(3, 3, mesh.wPxAll-6, mesh.hPxAll-6);
  piggy.position.set(tmp/2,20,0); // placed at end of "Hello, world"
  piggy.add(mesh);
  animate();
};
function init3js() { // standard 3js stuff
  js3canvas = document.getElementById("js3canvas");
  renderer = new THREE.WebGLRenderer( { canvas:js3canvas } );
  js3w = js3canvas.width;
  js3h = js3canvas.height;
  camera = new THREE.PerspectiveCamera(50, js3w/js3h, 1, 200);
  camera.position.set(15,0,100);
  camera.lookAt(15,0,0);
  world = new THREE.Scene();
  world.background = new THREE.Color(0x888888);
}
function animate() {
  renderer.render( world, camera );
  piggy.rotateY(.02);
  requestAnimationFrame(animate);
}
function dcText(txt, hWorldTxt, hWorldAll, hPxTxt, fgcolor, bgcolor) { // the routine
  // txt is the text.
  // hWorldTxt is world height of text in the plane.
  // hWorldAll is world height of whole rectangle containing the text.
  // hPxTxt is px height of text in the texture canvas; larger gives sharper text.
  // The plane and texture canvas are created wide enough to hold the text.
  // And wider if hWorldAll/hWorldTxt > 1 which indicates padding is desired.
  var kPxToWorld = hWorldTxt/hPxTxt;                // Px to World multplication factor
  // hWorldTxt, hWorldAll, and hPxTxt are given; get hPxAll
  var hPxAll = Math.ceil(hWorldAll/kPxToWorld);     // hPxAll: height of the whole texture canvas
  // create the canvas for the texture
  var txtcanvas = document.createElement("canvas"); // create the canvas for the texture
  var ctx = txtcanvas.getContext("2d");
  ctx.font = hPxTxt + "px sans-serif";        
  // now get the widths
  var wPxTxt = ctx.measureText(txt).width;         // wPxTxt: width of the text in the texture canvas
  var wWorldTxt = wPxTxt*kPxToWorld;               // wWorldTxt: world width of text in the plane
  var wWorldAll = wWorldTxt+(hWorldAll-hWorldTxt); // wWorldAll: world width of the whole plane
  var wPxAll = Math.ceil(wWorldAll/kPxToWorld);    // wPxAll: width of the whole texture canvas
  // next, resize the texture canvas and fill the text
  txtcanvas.width =  wPxAll;
  txtcanvas.height = hPxAll;
  if (bgcolor != undefined) { // fill background if desired (transparent if none)
    ctx.fillStyle = "#" + bgcolor.toString(16).padStart(6, '0');
    ctx.fillRect( 0,0, wPxAll,hPxAll);
  } 
  ctx.textAlign = "center";
  ctx.textBaseline = "middle"; 
  ctx.fillStyle = "#" + fgcolor.toString(16).padStart(6, '0'); // fgcolor
  ctx.font = hPxTxt + "px sans-serif";   // needed after resize
  ctx.fillText(txt, wPxAll/2, hPxAll/2); // the deed is done
  // next, make the texture
  var texture = new THREE.Texture(txtcanvas); // now make texture
  texture.minFilter = THREE.LinearFilter;     // eliminate console message
  texture.needsUpdate = true;                 // duh
  // and make the world plane with the texture
  geometry = new THREE.PlaneGeometry(wWorldAll, hWorldAll);
  var material = new THREE.MeshBasicMaterial( 
    { side:THREE.DoubleSide, map:texture, transparent:true, opacity:1.0 } );
  // and finally, the mesh
  var mesh = new THREE.Mesh(geometry, material);
  mesh.wWorldTxt = wWorldTxt; // return the width of the text in the plane
  mesh.wWorldAll = wWorldAll; //    and the width of the whole plane
  mesh.wPxTxt = wPxTxt;       //    and the width of the text in the texture canvas
                              // (the heights of the above items are known)
  mesh.wPxAll = wPxAll;       //    and the width of the whole texture canvas
  mesh.hPxAll = hPxAll;       //    and the height of the whole texture canvas
  mesh.ctx = ctx;             //    and the 2d texture context, for any glitter
  // console.log(wPxTxt, hPxTxt, wPxAll, hPxAll);
  // console.log(wWorldTxt, hWorldTxt, wWorldAll, hWorldAll);
  return mesh;
}
    </script>
  </head>
  <body>
    <canvas id="js3canvas" width="800" height="600"></canvas>
  </body>
</html>
2 Likes

selling them examples was much easier few years back now they have almost 1GB of examples and it does not make much sense to keep adding them.

a word of advice: do not create a canvas every single time. instead, do this.

1 Like

there is :slightly_smiling_face:

new THREE.CanvasTexture(canvas)

yeah and you should not use that, too :smiley: because it is for the case where the canvas is constantly updated, not for static labels

1 Like

@makc3d Добрый день Thanks for the comments.
Re:“do not create a canvas every single time”,

  1. I would then reset the canvas width and height and clear the previous text by re-filling the canvas? What if I wanted a transparent canvas? I would somehow “unfill”? I have nearly 1000 canvases on my “Jabberwocky globe” (dbarc.net).

Re:“you should not use that, too” (CanvasTexture?)
Good, I won’t.

Да. When you set canvas width or height properties, it is completely cleared.

@makc3d Just for the record, by setting “mesh.material.map.needsUpdate = true;”, you can dynamically update my texts. Replace these 2 routines in my code for a demo. Thanks again.

window.onload = function() {
  init3js(); // the standard 3js stuff
  mesh = dcText("Hello, world", 15, 20, 50, 0x000000, 0xcccccc); // text #1, Hello, world
  world.add(mesh);
  animate();
};
function animate() {
  mesh.ctx.fillStyle = "#0000ff"; 
  mesh.ctx.fillRect(mesh.wPxAll*.7, mesh.hPxAll*.1, mesh.wPxAll*.2, mesh.hPxAll*.8);
  mesh.ctx.fillStyle = "#ffffff"; 
  var txt = new Date().getSeconds();
  mesh.material.map.needsUpdate = true;
  mesh.ctx.fillText(txt,mesh.wPxAll*.8, mesh.hPxAll*.5);
  renderer.render( world, camera );
  world.rotateY(.01);
  requestAnimationFrame(animate);
}

Are you able to wrap the text with this method in order to have multiple rows of text in one canvas? Adding \n doesn’t seem to help.

Multiple lines

SpriteCanvasText

from the Collection of examples from discourse.threejs.org

and
BeginnerExample step 11

1 Like

Thank you very much hofk!

Quality of the text is really bad though.

What does this statement refer to?

There are many ways to display text, depending on the requirements.

See

1 Like

Text gets blury when used as a texture. The sprite technique seems to give better results but then you have to make a plane behind it and have another mesh.

It’s a question of the requirement you make.

You can influence this by changing the values.

2021-04-10 20.56.47

see <<<<<<<<<

const cv = document.createElement( 'canvas' );
cv.width = 1536 //  3 * 512   // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
cv.height = 512;// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
const ctx = cv.getContext( '2d' );

const txtGeometry = new THREE.BoxGeometry( 2.4, 0.8, 0.1 ); // w 3 : h 1 // <<<<<<<<<
const cvTexture = new THREE.Texture( cv );

I forgot to play with ctx.font = 'bold 6vh Arial';

1 Like

caleyo, I would like to see an example of your “blury texture”. Please pare down an example and email it to dave at dbarc dot net. Or maybe the proper thing is to post it somewhere? I’ll be watching.

Just look at the two pictures posted in the thread, text has always blury edges. But i already know: it gets blurier when ratio is not correct. The only way to get sharp text seems to be css2render, but i think its no good performance to update hundrets ofs divs positions on every frame and also the positions dont seem correct.