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>

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.

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

@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);
}