在插件中从UInt8Array加载Three.js纹理

前端开发 2026-07-09

我正在尝试为JPEG 360格式实现一个three.js纹理加载器。为此,它其实是一个嵌入在一组“盒子”中的JPEG图像,与MP4 / HEIF / QuickTime中使用的结构非常相似。

我可以从嵌套的JUMBF盒子中解析出JPEG编码流,没问题。

在这一点上,我本来希望能够直接调用现有的TextureLoader,传入JPEG编码流内容。可是结果却只是返回 undefined

正在进行中的实验代码看起来是这样的:

class JPEG360Loader extends DataTextureLoader {

    /**
     * Constructs a new JPEG 360 loader.
     *
     * @param {LoadingManager} [manager] - The loading manager.
     */
    constructor(manager) {

        super(manager);

    }

    /**
     * Parses the given JPEG 360 texture data.
     *
     * @param {ArrayBuffer} buffer - The raw texture data.
     * @return {DataTextureLoader~TexData} An object representing the parsed texture data.
     */
    parse(buffer) {
        let codestream = null;
        let stream = new DataStream(buffer);
        let size = stream.readUint32();
        console.log(size);
        // TODO: check size is consistent with buffer size
        let type = stream.readString(4);
        console.log(type);
        // TODO: check type == "jumb"
        while (stream.hasRemaining()) {
            let size = stream.readUint32();
            console.log(size);
            let type = stream.readString(4);
            console.log(type);
            if (type === "jumd") {
                let toggles = stream.readUint8();
                console.log("JUMD TOGGLE: " + toggles.toString(16));
                // TODO: parse out remainder
                stream.seek_relative(size - 9);
            } else if (type === "jumb") {
                console.log("second level JUMBF parsing");
                let jumbf_end_position = stream._position + (size - 8);
                let second_level_jumd_size = stream.readUint32();
                let second_level_jumd_fourcc = stream.readString(4);
                console.log("JUMBF descriptor 4CC: " + second_level_jumd_fourcc);
                if (second_level_jumd_fourcc !== "jumd") {
                    console.log("unexpected first entry, expected jumd but got " + second_level_jumd_fourcc);
                }
                // TODO: parse toggles and optional entries instead of skipping over it.
                stream.seek_relative(second_level_jumd_size - 8);
                while (stream._position < jumbf_end_position) {
                    let child_size = stream.readUint32();
                    let child_type = stream.readString(4);
                    if (child_type === "xml ") {
                        let xml_body = stream.readString(child_size - 8);
                        console.log(xml_body);
                    } else if (child_type === "jp2c") {
                        codestream = stream.readUint8Array(child_size - 8);
                    } else {
                        console.log("unhandled JUMBF child: " + child_type + ", size: " + child_size);
                        stream.seek_relative(child_size - 8);
                    }
                }
            } else {
                console.log("Unsupported second level box");
                stream.seek_relative(size - 8);
            }
        }
        if (codestream !== null) {
            console.log("found codestream in JUMBF wrappers");
            let texture = new TextureLoader().parse(codestream);
            console.log(texture);
            return texture;
        }
    }
}

现在,是否有一个合乎常理的办法从JPEG编码流中得到一个解析后的纹理对象(即 DataTextureLoader~TexData)返回?

解决方案

更新:

纹理的类型是 Texture,但 .parse() 的所需返回类型应为 DataTextureLoader~TexData

这里存在利益冲突。你有JPEG数据(它需要 TextureLoader),但你所需要的 DataTextureLoader 是用于加载“原始数据”图像格式的(如RGBA、RGBE/HDR、EXR、TGA)。

选项#1:
制作一个替代函数,将JPEG转换成 Texture 类型,然后按通常方式在你的ThreeJS场景中使用该纹理。它作为返回一个 DataTexture 的替代,返回 Texture

if( codestream !== null ) 
{
    console.log("found codestream in JUMBF wrappers");

    let texture = -1; let url_texture = -1;

    url_texture = URL.createObjectURL( new Blob([new Uint8Array( codestream )], { type: 'application/octet-stream' })  );

    let loader = new THREE.TextureLoader();

    texture = loader.load ( url_texture , onload_texture );
    function onload_texture()
    {
        alert("Got a texture loaded OK");
        console.log("Texture loaded is type : " + texture);
    }

    return texture;
}

选项#2:
如果你确实需要它为 DataTexture,可以尝试跳过 .parse() 部分,而改为仅从已知数据动态创建一个。你在这种情况下获取“数据”的唯一方法是先把Uint8Array数据加载到一个 Image 元素中,然后把该元素的像素绘制到一个 Canvas 中,以便再提取RGBA格式的“原始数据”。

if( codestream !== null ) 
{
    console.log("found codestream in JUMBF wrappers");

    let texture = -1; let url_texture = -1; 
    let canvas_in = document.createElement('canvas');
    let context = canvas_in.getContext('2d');

    url_texture = URL.createObjectURL( new Blob([new Uint8Array( codestream )], { type: 'application/octet-stream' })  );
    let temp_img = new Image(); temp_img.src = url_texture; temp_img.decode();
    temp_img.onload = (event) => {

        canvas_in.width = temp_img.naturalWidth;
        canvas_in.height = temp_img.naturalHeight;
        context.drawImage(temp_img, 0, 0 );
        let obj_imgData = context.getImageData(0, 0, temp_img.naturalWidth, temp_img.naturalHeight);

        //## instead of using .parse() ...
        //texture = new TextureLoader().parse(codestream);

        //## use DataTexture from a dynamically created instance
        //## constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace )
        texture = new THREE.DataTexture( obj_imgData.data , 
        obj_imgData.width , obj_imgData.height , 
        THREE.RGBAFormat , THREE.UnsignedByteType , THREE.DEFAULT_MAPPING ,
        THREE.ClampToEdgeWrapping , THREE.ClampToEdgeWrapping ,
        THREE.NearestFilter , THREE.NearestFilter , 1 , THREE.NoColorSpace 
        );

        //texture.flipY = false;
        texture.generateMipmaps = false; //#try as: true ... if image quality is affected
        texture.needsUpdate = true;
        console.log("Texture loaded v2 is type : " + texture);

        return texture;

    }
}

如果你仍然无法按你想要的方式获取到 TexData,那么最后一个备选是把 obj_imgData.data 传给一个基于JS的外部编码器(RGBE或 EXR或 TGA图像格式)的实现。此类编码数据(例如EXR格式)应当能与 .parse() 方法一起工作,并有望为你返回一个 DataTextureLoader~TexData 对象。

原始答案:

“现在有没有一个合乎常理的方式从JPEG编码流中取回纹理?”

假设 "codestream" 的类型是 Uint8Array(或者可以转换成这样的类型),那么你可以尝试把它作为一个“虚拟”的图像文件加载:

if (codestream !== null) 
{
    console.log("found codestream in JUMBF wrappers");

    let texture = -1; let url_texture = -1; 
    let tmp_hex_str = ""; 
    let len_bytes = codestream.length;

    //### (1) Check if JPEG data is valid ...

    //# check first two bytes of codestream bytes array
    tmp_hex_str = ( (codestream[0] << 8) | codestream[1] ).toString(16).toUpperCase();
    console.log("JPEG bytes START : 0x" + tmp_hex_str + " ... (expected: 0xFFD8)" );

    //# check last two bytes of codestream bytes array
    tmp_hex_str = ( (codestream[len_bytes-2] << 8) | codestream[len_bytes-1] ).toString(16).toUpperCase();
    console.log("JPEG bytes END : 0x" + tmp_hex_str + " ... (expected: 0xFFD9)" );

    //### (2) Try to load as virtual Image file ...

    url_texture = URL.createObjectURL( codestream );
    //let url_texture = URL.createObjectURL( new Uint8Array( codestream ) ); //# if array type converting is needed

    let loader = new THREE.TextureLoader();

    texture = loader.load ( url_texture , onload_texture );
    function onload_texture()
    {
        alert("Got a texture loaded OK");
        console.log("texture loaded is : " + texture);
    }

    return texture;
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章