Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load and render an envirornment in hdr format (and file) #5552

Closed
foo123 opened this issue Oct 31, 2014 · 12 comments
Closed

Load and render an envirornment in hdr format (and file) #5552

foo123 opened this issue Oct 31, 2014 · 12 comments

Comments

@foo123
Copy link
Contributor

foo123 commented Oct 31, 2014

Hello,

i have an .obj format 3D object and an environment in .hdr file format i want to use for a project

How can an hdr file be used with three.js?

Thanks

@mrdoob
Copy link
Owner

mrdoob commented Oct 31, 2014

We don't have a loader for hdr files. Added it to #5524.

@foo123
Copy link
Contributor Author

foo123 commented Oct 31, 2014

Great thanks, any way i can start developing a loader myself ( can also add it as a pull quest if you like)

since i need this for a project right now?

@mrdoob
Copy link
Owner

mrdoob commented Oct 31, 2014

@foo123
Copy link
Contributor Author

foo123 commented Oct 31, 2014

Thanks mrdoob, i'll try that,
if i have sth i can create a pull request.

@foo123 foo123 closed this as completed Oct 31, 2014
@foo123
Copy link
Contributor Author

foo123 commented Nov 2, 2014

mrdoob, a further question

i think of adding a generic BinaryTextuireLoader (or DataTextureLoader) class (similar to the CompressedTextureLoader class) so that allfurther loaders can etxed it and just use a different parser

i have already added the classes (BinaryTextureLoader and RGBELoader which extends it) and use a DataTexture, but how is the DataTexture supposed to be used?

( i have already some code for .obj and .stl 3D loaders as well )

BinaryTextureLoader.js

THREE.DataTextureLoader = THREE.BinaryTextureLoader = function () {

    // override in sub classes
    this._parser = null;

};

THREE.BinaryTextureLoader.prototype = {

    constructor: THREE.BinaryTextureLoader,

    load: function ( url, onLoad, onError ) {

        var scope = this;

        var texture = new THREE.DataTexture( );

        var loader = new THREE.XHRLoader();
        loader.setResponseType( 'arraybuffer' );

        loader.load( url, function ( buffer ) {

            var texData = scope._parser( buffer );

            texture.image.width = texData.width;
            texture.image.height = texData.height;
            texture.image.data = texData.data;

            texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : THREE.ClampToEdgeWrapping;
            texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : THREE.ClampToEdgeWrapping;

            texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : THREE.LinearFilter;
            texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : THREE.LinearMipMapLinearFilter;

            texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1;

            texture.format = texData.format;
            texture.type = texData.type;

            if ( texData.mipmaps ) {

                texture.mipmaps = texData.mipmaps;

            }

            if ( texData.mipmapCount === 1 ) {

                texture.minFilter = THREE.LinearFilter;

            }

            texture.needsUpdate = true;

            if ( onLoad ) onLoad( texture );

        } );


        return texture;

    }

};

RGBELoader (in progress)

THREE.HDRLoader = THREE.RGBELoader = function ( manager ) {

    this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

};

// extend THREE.BinaryTextureLoader
THREE.RGBELoader.prototype = Object.create( THREE.BinaryTextureLoader.prototype );

// adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
THREE.RGBELoader.prototype._parser = function( buffer ) {

    var byteArray = new Uint8Array( buffer ); // byteArray.byteLength;

    var 
        /* return codes for rgbe routines */
        RGBE_RETURN_SUCCESS =  0,
        RGBE_RETURN_FAILURE = -1,

        /* default error routine.  change this to change error handling */
        rgbe_read_error = 1,
        rgbe_write_error = 2,
        rgbe_format_error = 3,
        rgbe_memory_error = 4,
        rgbe_error = function(rgbe_error_code, msg) {
            switch (rgbe_error_code) {
                case rgbe_read_error: throw new Error("RGBE read error");
                break;
                case rgbe_write_error: throw new Error("RGBE write error");
                break;
                case rgbe_format_error:  throw new Error("RGBE bad file format: " + msg);
                break;
                default:
                case rgbe_memory_error:  throw new Error("RGBE error: " + msg);
            }
            return RGBE_RETURN_FAILURE;
        },

        /* offsets to red, green, and blue components in a data (float) pixel */
        RGBE_DATA_RED      = 0,
        RGBE_DATA_GREEN    = 1,
        RGBE_DATA_BLUE     = 2,

        /* number of floats per pixel */
        RGBE_DATA_SIZE     = 3,

        rgbe_header_info = {

          valid: 0,                         /* indicate which fields are valid */

          programtype: 'RGBE',              /* listed at beginning of file to identify it 
                                            * after "#?".  defaults to "RGBE" */ 
          width: 0, height: 0,
          gamma: 1.0,                       /* image has already been gamma corrected with 
                                            * given gamma.  defaults to 1.0 (no correction) */
          exposure: 1.0                     /* a value of 1.0 in an image corresponds to
                                            * <exposure> watts/steradian/m^2. 
                                            * defaults to 1.0 */
        },

        /* flags indicating which fields in an rgbe_header_info are valid */
        RGBE_VALID_PROGRAMTYPE = 1,
        RGBE_VALID_GAMMA       = 2,
        RGBE_VALID_EXPOSURE    = 4,

        /* standard conversion from rgbe to float pixels */
        /* note: Ward uses ldexp(col+0.5,exp-(128+8)).  However we wanted pixels */
        /*       in the range [0,1] to map back into the range [0,1].            */
        rgbe2float = function( rgb, index, rgbe ) {
            var f;

            if ( rgbe[3] ) {   /*nonzero pixel*/
                f = Math.exp(1.0, rgbe[3]-(128+8) );
                rgb[index+RGBE_DATA_RED] = rgbe[0] * f;
                rgb[index+RGBE_DATA_GREEN] = rgbe[1] * f;
                rgb[index+RGBE_DATA_BLUE] = rgbe[2] * f;
            }
            else {
                rgb[index+RGBE_DATA_RED] = rgb[index+RGBE_DATA_GREEN] = rgb[index+RGBE_DATA_BLUE] = 0.0;
            }
        },

        /* minimal header reading.  modify if you want to parse more information */
        RGBE_ReadHeader = function( buffer, width, height, info ) {
            var buf[128], found_format, tempf, i;

            found_format = 0;
            if ( info ) {
                info.valid = 0;
                info.programtype[0] = '';
                info.gamma = info.exposure = 1.0;
            }
            if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == NULL)
                return rgbe_error(rgbe_read_error,NULL);

            if ( (buf[0] != '#')||(buf[1] != '?') ) {
                /* if you want to require the magic token then uncomment the next line */
                /*return rgbe_error(rgbe_format_error,"bad initial token"); */
            }

            else if ( info ) {
                info.valid |= RGBE_VALID_PROGRAMTYPE;
                for (i=0;i<sizeof(info.programtype)-1;i++) {
                    if ((buf[i+2] == 0) || isspace(buf[i+2]))
                    break;
                    info.programtype[i] = buf[i+2];
                }
                info.programtype[i] = 0;
                if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
                    return rgbe_error(rgbe_read_error,NULL);
            }

            for(;;) {
                if ((buf[0] == 0)||(buf[0] == '\n'))
                    return rgbe_error(rgbe_format_error,"no FORMAT specifier found");

                else if (strcmp(buf,"FORMAT=32-bit_rle_rgbe\n") == 0)
                    break;       /* format found so break out of loop */

                else if (info && (sscanf(buf,"GAMMA=%g",&tempf) == 1)) {
                    info.gamma = tempf;
                    info.valid |= RGBE_VALID_GAMMA;
                }

                else if (info && (sscanf(buf,"EXPOSURE=%g",&tempf) == 1)) {
                    info.exposure = tempf;
                    info.valid |= RGBE_VALID_EXPOSURE;
                }
                if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
                    return rgbe_error(rgbe_read_error,NULL);
            }

            if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
                return rgbe_error(rgbe_read_error,NULL);

            if (strcmp(buf,"\n") != 0)
                return rgbe_error(rgbe_format_error, "missing blank line after FORMAT specifier");

            if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
                return rgbe_error(rgbe_read_error,NULL);
            if (sscanf(buf,"-Y %d +X %d",height,width) < 2)
                return rgbe_error(rgbe_format_error,"missing image size specifier");
            return RGBE_RETURN_SUCCESS;
        },


        /* simple read routine.  will not correctly handle run length encoding */
        RGBE_ReadPixels = function( buffer, data, numpixels ) {
            var rgbe[4], index = 0;

            while( numpixels-- > 0 ) {
                if (fread(rgbe, sizeof(rgbe), 1, fp) < 1)
                    return rgbe_error(rgbe_read_error,NULL);

                rgbe2float( data, index, rgbe );
                index += RGBE_DATA_SIZE;
            }
            return RGBE_RETURN_SUCCESS;
        },

        /* read routine.  will handle run length encoding */
        RGBE_ReadPixels_RLE = function( buffer, data, scanline_width, num_scanlines )  {
            var rgbe[4], scanline_buffer, ptr, ptr_end;
            var i, count;
            var buf[2];

            if ( (scanline_width < 8)||(scanline_width > 0x7fff) )
                /* run length encoding is not allowed so read flat*/
                return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines);

            scanline_buffer = NULL;
            /* read in each successive scanline */
            while( num_scanlines > 0 ) {
                if (fread(rgbe,sizeof(rgbe),1,fp) < 1) {
                free(scanline_buffer);
                return rgbe_error(rgbe_read_error,NULL);
                }
                if ((rgbe[0] != 2)||(rgbe[1] != 2)||(rgbe[2] & 0x80)) {
                /* this file is not run length encoded */
                rgbe2float(&data[0],&data[1],&data[2],rgbe);
                data += RGBE_DATA_SIZE;
                free(scanline_buffer);
                return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines-1);
                }
                if ((((int)rgbe[2])<<8 | rgbe[3]) != scanline_width) {
                free(scanline_buffer);
                return rgbe_error(rgbe_format_error,"wrong scanline width");
                }
                if (scanline_buffer == NULL)
                scanline_buffer = (unsigned char *)
                malloc(sizeof(unsigned char)*4*scanline_width);
                if (scanline_buffer == NULL) 
                return rgbe_error(rgbe_memory_error,"unable to allocate buffer space");

                ptr = &scanline_buffer[0];
                /* read each of the four channels for the scanline into the buffer */
                for(i=0;i<4;i++) {
                ptr_end = &scanline_buffer[(i+1)*scanline_width];
                while(ptr < ptr_end) {
                if (fread(buf,sizeof(buf[0])*2,1,fp) < 1) {
                free(scanline_buffer);
                return rgbe_error(rgbe_read_error,NULL);
                }
                if (buf[0] > 128) {
                /* a run of the same value */
                count = buf[0]-128;
                if ((count == 0)||(count > ptr_end - ptr)) {
                free(scanline_buffer);
                return rgbe_error(rgbe_format_error,"bad scanline data");
                }
                while(count-- > 0)
                *ptr++ = buf[1];
                }
                else {
                /* a non-run */
                count = buf[0];
                if ((count == 0)||(count > ptr_end - ptr)) {
                free(scanline_buffer);
                return rgbe_error(rgbe_format_error,"bad scanline data");
                }
                *ptr++ = buf[1];
                if (--count > 0) {
                if (fread(ptr,sizeof(*ptr)*count,1,fp) < 1) {
                free(scanline_buffer);
                return rgbe_error(rgbe_read_error,NULL);
                }
                ptr += count;
                }
                }
                }
                }
                /* now convert data from buffer into floats */
                for(i=0;i<scanline_width;i++) {
                rgbe[0] = scanline_buffer[i];
                rgbe[1] = scanline_buffer[i+scanline_width];
                rgbe[2] = scanline_buffer[i+2*scanline_width];
                rgbe[3] = scanline_buffer[i+3*scanline_width];
                rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],
                &data[RGBE_DATA_BLUE],rgbe);
                data += RGBE_DATA_SIZE;
                }
                num_scanlines--;
            }
            free(scanline_buffer);
            return RGBE_RETURN_SUCCESS;
        }

        RGBE_ReadHeader( buffer, rgbe_header_info );

        return {
            width: w, height: h,
            data: RGBE_ReadPixels_RLE( buffer ),
            exposure: rgbe_header_info.exposure
            gamma: rgbe_header_info.gamma,
            format: THREE.RGBEFormat,
            type: THREE.UnsignedByteType
        };
    ;
};

@mrdoob
Copy link
Owner

mrdoob commented Nov 2, 2014

That's looking great!
I think you can use the TGALoader and its example as reference.

@foo123
Copy link
Contributor Author

foo123 commented Nov 3, 2014

Good, should i make the TGALoader extend the BinaryTextureLoader as well (all these loaders are binary and only the parsing changes)?

Also, the RGBE is dynamic range pixels (this means they are float and not unsigned bytes), hw would this be better handled by Three.js, can a float texture be used, what do you think?

In the webgl_hfr example a simple.png is used and any dynamic range is handled by shaders

@mrdoob
Copy link
Owner

mrdoob commented Nov 4, 2014

Good, should i make the TGALoader extend the BinaryTextureLoader as well (all these loaders are binary and only the parsing changes)?

That sounds good.

Also, the RGBE is dynamic range pixels (this means they are float and not unsigned bytes), hw would this be better handled by Three.js, can a float texture be used, what do you think?

Hmmm, not sure... if we can avoid floating point texture would be better I guess? As far as I understand the webgl_hdr example uses the alpha channel to encode the levels?

@foo123
Copy link
Contributor Author

foo123 commented Nov 4, 2014

Hmm, yes any dynamic range be encoded in alpha channel and let the shaders handle it. i can do that

This has the following issues however:

Any dynamic range already encpded in image is truncated and re-simulated in the shaders
What about .hdr images which do have alpha channels? (do .hdr images with alpha channels exist, i dont know)

@mrdoob
Copy link
Owner

mrdoob commented Nov 4, 2014

What about .hdr images which do have alpha channels? (do .hdr images with alpha channels exist, i dont know)

I don't know either! I've never seen one.

@foo123
Copy link
Contributor Author

foo123 commented Nov 13, 2014

Created pull request here (#5620)

foo123 pushed a commit to foo123/three.js that referenced this issue Nov 13, 2014
…js (plus example), TGALoader.js extends BinaryTextureLoader.js
@foo123
Copy link
Contributor Author

foo123 commented Nov 13, 2014

prev PR closed, new PR here #5624

mrdoob added a commit that referenced this issue Nov 14, 2014
(three.js issue: #5552) add BinaryTextireLoader.js, RGBELoader.js (plus ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants