I've been looking into creating a texture from data inthree.js
. It is super easy, but there are some caveats, and some parts can be confusing. I fell into some traps many years ago, then fall into it again recently, so I decided to write about it!
Table of contents generated with markdown-toc
What is confusing (me)?
When creating a new texture, from data, you must set aFormat
, aType,
and provide you data in a specific type ofTypedArray
.
consttexture=newDataTexture(data,width,height,format,type,...);
The doc says
"type" must correspond to the "format".
Ok... so how do I know whichType
to set for theFormat
I want to use?
Grayscale textures
In this post, I will only discuss grayscale (single channel) texture for WebGL1 since it is my current focus. Everything in the rest of this post will apply to whatever you are trying to support.
Which format
Alright, I want to create a single channel (grayscale) texture.
In the'internal format' section, you can find whichFormat
is the best fit for you, depending on the number of channels and the bytes per pixel.
LUMINANCE
is the natural fit for grayscale textures. The caveat is that it only supportsUnsignedByte
texture type for Webgl1.
If you work with WebGL2,R*
formats allow you to support a variety of different bit-depth.
To know more about what the different file format means, I found this page useful:https://www.khronos.org/opengl/wiki/Image_Format. It explains what the suffix of the file format means*F
,*_SNORM
means and how those type of textures are interpreted. That is important regarding the data normalization. (keep reading)
Luminance format allows three types (WebGL1)
Alright, we just learned that for WebGL1,Luminance
format goes withUnsignedByte
type.
Can we do better?
If your browser supports theOES_texture_float
extension, a bunch of new types (Float
andHalfFloat
) are available for theLUMINANCE
format. (official documentation)
Format | Type | Byte per Pixel |
---|---|---|
RGBA | FLOAT | 16 |
RGB | FLOAT | 12 |
LUMINANCE_ALPHA | FLOAT | 8 |
LUMINANCE | FLOAT | 4 |
ALPHA | FLOAT | 4 |
RGBA | HALF_FLOAT_OES | 8 |
RGB | HALF_FLOAT_OES | 6 |
LUMINANCE_ALPHA | HALF_FLOAT_OES | 4 |
LUMINANCE | HALF_FLOAT_OES | 2 |
ALPHA | HALF_FLOAT_OES | 2 |
Type to TypedArray containing the data
It is pretty straight forward:
Type | Byte per Pixel | Typed Array |
---|---|---|
UnsignedByte | 1 | Uint8Array |
HalfFloat | 2 | Uint16Array |
Float | 4 | Float32Array |
What is important there is that the number of bits in the type array matches the byte per pixel in the table. Also, forHalfFloat
the data should be prepared appropriately.
Access the data in the fragment shader
All the integer textures (includingUnsignedByteType
) are normalized automatically while uploaded to the shaders, whereas the floating/integral textures (includingFloat
andHalfFloat
) are passed as it is.
Based on theFormat
name, you can know with which type of data you are dealing with and whether that will be normalized for you or not. (ref).
In other words, in the fragment shader, when using anUnsignedByteType
texture, the values you get from the texture 2D are normalized between 0 and 1 automatically. ForFloatType
andHalfFloatType
you get the value that was in the typed array without any normalization.
Gimme some concrete examples!
UnsignedByte Texture
consttextureSize=16constdataSize=10;constdata=newUint8Array(dataSize);for(leti=0;i<dataSize){data[i]=Math.round(Math.random()*255);// pass anything from 0 to 255}consttexture=newDataTexture(data,textureSize,textureSize,LUMINACE,UnsignedByteType);
varyingvec2vUv;uniformsampler2DuData;voidmain(){vec3color;vec4data=texture2D(uData,vUv);gl_FragColor=vec4(data.xyz,1.0);}
HalfFloat Texture
To convert a number to half float, do it thethree.js
way:like this
⚠️ Watch out forprecision errors when converting numbers to half float precision!
consttextureSize=16constdataSize=10;constdata=newUint16Array(dataSize);for(leti=0;i<dataSize){constlargeNumber=Math.random()*10000;// pass anything from 0 to 10000data[i]=toHalfFloat(largeNumber);}consttexture=newDataTexture(data,textureSize,textureSize,LUMINACE,HalfFloatType);
varyingvec2vUv;uniformsampler2DuData;uniformfloatuMax;uniformfloatuMin;voidmain(){vec3color;vec4data=texture2D(uData,vUv);vec4normalizedData=(data-uMin)/(uMax-uMin);gl_FragColor=vec4(data.xyz,1.0);}
Float Texture
consttextureSize=16constdataSize=10;constdata=newFloat32Array(dataSize);for(leti=0;i<dataSize){constlargeNumber=Math.random()*10000;// pass anything from 0 to 10000data[i]=largeNumber;}consttexture=newDataTexture(data,textureSize,textureSize,LUMINACE,FloatType);
varyingvec2vUv;uniformsampler2DuData;uniformfloatuMax;uniformfloatuMin;voidmain(){vec3color;vec4data=texture2D(uData,vUv);vec4normalizedData=(data-uMin)/(uMax-uMin);gl_FragColor=vec4(data.xyz,1.0);}
Until next time!
Until next time 🙋♂️, happy coding!
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse