diff --git a/manual/en/indexed-textures.html b/manual/en/indexed-textures.html index 502710d26fbcfd..83d3533e6e1787 100644 --- a/manual/en/indexed-textures.html +++ b/manual/en/indexed-textures.html @@ -40,10 +40,10 @@
The first idea that comes to mind is to generate geometry for each country. We could use a picking solution like we covered before. -We'd build 3D geometry for each country. If the user clicks on the mesh for +We'd build 3D geometry for each country. If the user clicks on the mesh for that country we'd know what country was clicked.
So, just to check that solution I tried generating 3D meshes of all the countries -using the same data I used to generate the outlines +using the same data I used to generate the outlines in the previous article. The result was a 15.5meg binary GLTF (.glb) file. Making the user download 15.5meg sounds like too much to me.
@@ -53,11 +53,11 @@Another solution would be to use just actual data compression. For example gzipping the file brought it down to 11meg. That's 30% less but arguably not enough.
-We could store all the data as 16bit ranged values instead of 32bit float values. +
We could store all the data as 16bit ranged values instead of 32bit float values. Or we could use something like draco compression -and maybe that would be enough. I didn't check and I would encourage you to check +and maybe that would be enough. I didn't check and I would encourage you to check yourself and tell me how it goes as I'd love to know. ๐
-In my case I thought about the GPU picking solution +
In my case I thought about the GPU picking solution we covered at the end of the article on picking. In that solution we drew every mesh with a unique color that represented that mesh's id. We then drew all the meshes and looked at the color that was clicked @@ -65,13 +65,13 @@
Taking inspiration from that we could pre-generate a map of countries where each country's color is its index number in our array of countries. We could then use a similar GPU picking technique. We'd draw the globe off screen using -this index texture. Looking at the color of the pixel the user clicks would +this index texture. Looking at the color of the pixel the user clicks would tell us the country id.
-So, I wrote some code +
So, I wrote some code to generate such a texture. Here it is.
-Note: The data used to generate this texture comes from this website +
Note: The data used to generate this texture comes from this website and is therefore licensed as CC-BY-SA.
It's only 217k, much better than the 14meg for the country meshes. In fact we could probably even lower the resolution but 217k seems good enough for now.
@@ -244,14 +244,14 @@The code stills shows countries based on their area but if you click one just that one will have a label.
-So that seems like a reasonable solution for picking countries +
So that seems like a reasonable solution for picking countries but what about highlighting the selected countries?
For that we can take inspiration from paletted graphics.
Paletted graphics -or Indexed Color is -what older systems like the Atari 800, Amiga, NES, +or Indexed Color is +what older systems like the Atari 800, Amiga, NES, Super Nintendo, and even older IBM PCs used. Instead of storing bitmaps -as RGB colors 8bits per color, 24 bytes per pixel or more, they stored +as RGBA colors 8bits per color, 32 bytes per pixel or more, they stored bitmaps as 8bit values or less. The value for each pixel was an index into a palette. So for example a value of 3 in the image means "display color 3". What color color#3 is is @@ -259,7 +259,7 @@
In JavaScript you can think of it like this
const face7x7PixelImageData = [ 0, 1, 1, 1, 1, 1, 0, - 1, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 1, 1, 0, 2, 0, 2, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 3, 3, 3, 0, 1, @@ -284,8 +284,8 @@Indexed Textures for Picking and Color
texture we can color each individual country. For example by setting the entire palette texture to black and then for one country's entry in the palette a different color, we can highlight just that country. -To do paletted index graphics requires some custom shader code. -Let's modify the default shaders in three.js. +
To do paletted index graphics requires some custom shader code. +Let's modify the default shaders in three.js. That way we can use lighting and other features if we want.
Like we covered in the article on animating lots of objects we can modify the default shaders by adding a function to a material's @@ -331,14 +331,14 @@
Indexed Textures for Picking and Color
}
Digging through all those snippets
-we find that three.js uses a variable called diffuseColor
to manage the
+we find that three.js uses a variable called diffuseColor
to manage the
base material color. It sets this in the <color_fragment>
snippet
so we should be able to modify it after that point.
diffuseColor
at that point in the shader should already be the color from
-our outline texture so we can look up the color from a palette texture
+
diffuseColor
at that point in the shader should already be the color from
+our outline texture so we can look up the color from a palette texture
and mix them for the final result.
Like we did before we'll make an array
-of search and replacement strings and apply them to the shader in
+of search and replacement strings and apply them to the shader in
Material.onBeforeCompile
.
{ const loader = new THREE.TextureLoader(); @@ -389,7 +389,7 @@Indexed Textures for Picking and Color
Above can see above we add 3 uniforms, indexTexture
, paletteTexture
,
and paletteTextureWidth
. We get a color from the indexTexture
-and convert it to an index. vUv
is the texture coordinates provided by
+and convert it to an index. vUv
is the texture coordinates provided by
three.js. We then use that index to get a color out of the palette texture.
We then mix the result with the current diffuseColor
. The diffuseColor
at this point is our black and white outline texture so if we add the 2 colors
@@ -407,21 +407,21 @@
const maxNumCountries = 512; const paletteTextureWidth = maxNumCountries; const paletteTextureHeight = 1; -const palette = new Uint8Array(paletteTextureWidth * 3); +const palette = new Uint8Array(paletteTextureWidth * 4); const paletteTexture = new THREE.DataTexture( - palette, paletteTextureWidth, paletteTextureHeight, THREE.RGBFormat); + palette, paletteTextureWidth, paletteTextureHeight); paletteTexture.minFilter = THREE.NearestFilter; paletteTexture.magFilter = THREE.NearestFilter;
A DataTexture
let's us give a texture raw data. In this case
-we're giving it 512 RGB colors, 3 bytes each where each byte is
+we're giving it 512 RGBA colors, 4 bytes each where each byte is
red, green, and blue respectively using values that go from 0 to 255.
Let's fill it with random colors just to see it work
for (let i = 1; i < palette.length; ++i) { palette[i] = Math.random() * 256; } // set the ocean color (index #0) -palette.set([100, 200, 255], 0); +palette.set([100, 200, 255, 255], 0); paletteTexture.needsUpdate = true;
Anytime we want three.js to update the palette texture with @@ -454,11 +454,13 @@
const tempColor = new THREE.Color(); function get255BasedColor(color) { tempColor.set(color); - return tempColor.toArray().map(v => v * 255); + const base = tempColor.toArray().map(v => v * 255); + base.push(255); // alpha + return base; }
Calling it like this color = get255BasedColor('red')
will
-return an array like [255, 0, 0]
.
[255, 0, 0, 255]
.
Next let's use it to make a few colors and fill out the palette.
const selectedColor = get255BasedColor('red'); @@ -467,7 +469,7 @@Indexed Textures for Picking and Color
resetPalette(); function setPaletteColor(index, color) { - palette.set(color, index * 3); + palette.set(color, index * 4); } function resetPalette() { @@ -628,11 +630,11 @@Indexed Textures for Picking and Color
- + -