Custom shader with HDR cube map not working

Hi all,

I have an issue when trying to load HDR cube maps with a custom shader. It works fine with the default shaders.

I’ve also tried the PisaHDR images from the examples but this looks odd too with my shader.

Below is my code

// LDR Loader
var textureCube = CubeTextureLoader.setPath('./assets/textures/cubemaps/studio/').load([ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ]);

and

// HDR Loader
var textureCube = HDRCubeTextureLoader.setPath('./assets/textures/cubemaps/studio/').load([ 'px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr' ]);

Vertex shader

varying mat4 vMat;
varying vec3 vNormal;

void main() {
    vMat = viewMatrix;
    vNormal = normal;

    gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}

Fragment shader

float envMapIntensity = 1.0;
varying mat4 vMat;
varying vec3 vNormal;
uniform samplerCube envMap;

vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {
    return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );
}
vec3 getLightProbeIndirectIrradiance( const in vec3 normal, const in int maxMIPLevel ) {
    vec3 worldNormal = inverseTransformDirection( normal, vMat );
    vec3 queryVec = vec3( worldNormal.x, worldNormal.yz );
    vec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );
    envMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;
    return PI * envMapColor.rgb * envMapIntensity;
}
void main() {
    vec3 irradiance = getLightProbeIndirectIrradiance(vNormal, 1);
    gl_FragColor = vec4( irradiance, 1.0 );
}

Any ideas?

Thanks,
Nick

I’m guessing that the texture is not decoded properly within the shader :ok_hand:

Thanks @Usnul, I managed to fix the issue late last night by setting the DataType to FloatType.

1 Like

Hi all, apparently I’m still having a few issues. I posted the issue on stackoverflow, but I will repost here too in case someone has an idea of how to fix that.

I’m trying to develop my own custom glsl shader for threejs but I’m stack on the glossy reflections with HDR images. It works fine with with LDR images, but not with HDR.

I started by using this example in order to generate the mipmaps.

I then used a browser extension to get the compiled code from the default MeshStandardMaterial and I isolated the bits I needed which were related to the environment maps and I wrote my shader (I’ve added both radiance and irradiance functions in the shader), but I’m probably missing something as this doesn’t seem to work for me.

In the image bellow, you can see that in version 1) which has a plain HDRI (with no mipmaps) I had to change the DataType to “THREE.FloatType”. Even though this is still looking wrong (not glossy and very dark), it is the closest I managed to get.
Version 2) uses the default “THREE.UnsignedDataType” but the image looks completely wrong.
Version 3) which is the version that includes mipmaps, just errors.

enter image description here

Here’s a link to download all the files or just have a look in the code below.

<html>
    <head>
        <script src="./js/three.min.js"></script>
        <script src="./js/RGBELoader.js"></script>
        <script src="./js/HDRCubeTextureLoader.js"></script>
        <script src="./js/PMREMCubeUVPacker.js"></script>
        <script src="./js/PMREMGenerator.js"></script>
        <style>
            html, body{ margin:0px; padding: 0px;}
        </style>
    </head>

    <body>
        <!-- Vertex and Fragment shaders-->
        <script id="VS" type="x-shader/x-vertex">
            varying vec3 vNormal;
            varying vec3 vViewPosition;
            
            void main() {
                vNormal = normal;
                vViewPosition = - (modelViewMatrix * vec4( position, 1.0 )).xyz;
            
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
            }
        </script>
        <script id="FS" type="x-shader/x-fragment">
            varying vec3 vViewPosition;
            varying vec3 vNormal;

            uniform int maxMipLevel;
            uniform samplerCube envMap;
            uniform float envMapIntensity;
            uniform float flipEnvMap;
            
            uniform float roughness;
            
            
            float pow2( const in float x ) {
                return x*x;
            }
            float GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {
                return ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );
            }
            float getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {
                float maxMIPLevelScalar = float( maxMIPLevel );
                float desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );
                return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
            }
            vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {
                return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );
            }
            vec3 getLightProbeIndirectRadiance( const in vec3 viewDir, const in vec3 normal, const in float blinnShininessExponent, const in int maxMIPLevel ) {
                vec3 reflectVec = reflect( -viewDir, normal );
                reflectVec = inverseTransformDirection( reflectVec, viewMatrix );
                float specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );
                
                vec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );
                vec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );
                envMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;
                
                return envMapColor.rgb * envMapIntensity * .75;
            }
            vec3 getLightProbeIndirectIrradiance( const in vec3 normal, const in int maxMIPLevel ) {
                vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );
                vec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );
                vec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );
                
                return PI * envMapColor.rgb * envMapIntensity;
            }
            
            
            void main() {
                vec3 irradiance = getLightProbeIndirectIrradiance(normalize(vNormal), maxMipLevel );
                vec3 radiance = getLightProbeIndirectRadiance( normalize( vViewPosition ), normalize(vNormal), GGXRoughnessToBlinnExponent( roughness ), maxMipLevel );

                gl_FragColor = vec4( radiance, 1.0 );
            }
            
        </script>

        <!-- THREE JS code-->
        <script>
            var CubeTextureLoader = new THREE.CubeTextureLoader();
            var HDRCubeTextureLoader = new THREE.HDRCubeTextureLoader();
            
            // renderer
            var renderer = new THREE.WebGLRenderer({antialias:true});
            renderer.setClearColor( 0xaaaaaa );
            renderer.setSize( window.innerWidth, window.innerHeight );
            document.body.appendChild( renderer.domElement );

            // scene
            scene = new THREE.Scene();
            
            // camera
            camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
            camera.position.set(0, 0, 40);

            // load HDRIs and Generate mipmaps
            // Code from: https://threejs.org/examples/#webgl_materials_envmaps_hdr
            var hdrCubeRenderTarget;
            var hdrUrls = [ 'px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr' ];
            var hdrCubeMap = new THREE.HDRCubeTextureLoader()
            .setPath( './pisaHDR/' )
            .setDataType( THREE.UnsignedByteType )
            // .setDataType( THREE.FloatType )
            .load( hdrUrls, function () {
                var pmremGenerator = new THREE.PMREMGenerator( hdrCubeMap );
                pmremGenerator.update( renderer );
                var pmremCubeUVPacker = new THREE.PMREMCubeUVPacker( pmremGenerator.cubeLods );
                pmremCubeUVPacker.update( renderer );
                hdrCubeRenderTarget = pmremCubeUVPacker.CubeUVRenderTarget;
                hdrCubeMap.magFilter = THREE.LinearFilter;
                hdrCubeMap.needsUpdate = true;
                pmremGenerator.dispose();
                pmremCubeUVPacker.dispose();
            } );

            // materials
            var stdMtl = new THREE.MeshStandardMaterial( { color: 0xffffff, roughness: 0.5, metalness: 1.0 } );
            var cusMtl = new THREE.ShaderMaterial( {
                defines: {
                    PI: 3.14159265359
                },
                uniforms: {
                    roughness: 0.5,
                    envMapIntensity: { value:1.0 },
                    flipEnvMap: { value: -1.0 },
                    envMap: { value:null }
                },
                vertexShader: document.getElementById('VS').text,
                fragmentShader: document.getElementById('FS').text
            } );
        
            // geometries
            var sphereGeometry = new THREE.SphereGeometry( 5, 32, 32 );
            var stdSphereMesh = new THREE.Mesh( sphereGeometry, stdMtl );
            var cusSphereMesh = new THREE.Mesh( sphereGeometry, cusMtl );
            stdSphereMesh.position.set(-7.5, 0, 0);
            cusSphereMesh.position.set(7.5, 0, 0);

            scene.add( stdSphereMesh );
            scene.add( cusSphereMesh );

            // render scene
            function render() {
                var newEnvMap = hdrCubeRenderTarget ? hdrCubeRenderTarget.texture : null;
                if ( newEnvMap && newEnvMap !== stdSphereMesh.material.envMap ) {
                    stdSphereMesh.material.envMap = newEnvMap;
                    stdSphereMesh.material.needsUpdate = true;
                    
                    cusSphereMesh.material.uniforms.envMap.value = newEnvMap; // This isErroring
                    // cusSphereMesh.material.uniforms.envMap.value = hdrCubeMap; // Result show HDRI but with wrong gamma and no mipmaps
                    cusSphereMesh.material.needsUpdate = true;
                }

                requestAnimationFrame( render );
                renderer.render( scene, camera );
            }
            render();
        
        </script>

    </body>
</html>

Please include a link to your post here.

Hi @looeee, here’s the link. No one has really replied. I wonder if what I’m trying to do, is so difficult.

Well, it is the weekend. Even computer programmers need to rest.

I posted it before the weekend though hahaha