Manually create a Physical Material

Hi there,

I’m doing some experiments and I was trying to re-create the physical material using the ShaderMaterial (so I can later modify it).

I tried both

var customMaterial = new THREE.ShaderMaterial(THREE.ShaderLib.physical);


var customMaterial = new THREE.ShaderMaterial({
    uniforms: THREE.ShaderLib.physical.uniforms,
    vertexShader: THREE.ShaderLib.physical.vertexShader,
    fragmentShader: THREE.ShaderLib.physical.fragmentShader,

but it doesn’t seem to work. Any ideas why??


Long answer is required to fully understand why. I will try to be brief instead.

The answer is:
“magic” :rainbow:

Three.js doesn’t actually have a uniform material framework, those materials that come pre-packed with three.js? - the library treats them specially, if you read the rendering code, it’s full of “is this material X? - then do Y” as a result you will not be able to do what you want using existing material shader code.

There are implementations of these “magic” :rainbow: shaders using node-based shader library (currently part of examples) - so if you’re interested in going down the route of custom shaders - I suggest you start looking there first and save yourself a lot of pain.

Please read in this context the following answer at stackoverflow:

The following topic might also be interesting for you:


You may also want to check out this recent thread: Where do material's properties come?

great, many thanks for the resources guys. I’ll give them a read/try!

1 Like

Another tutorial on extending materials that outlines a few basic patterns:

Hi All,

Many thanks again for all your replies and apologies for my late reply, but some urgent work came in so I had to suspend my research.

I think Shader Chunks is the best approach for me, but I’m having an issue.
Bellow is the fragment shader I wrote based on the physical fragment shader I found on GitHub.

        varying vec3 vViewPosition;
        varying vec3 vNormal;

        // Defines
        #define STANDARD
        #define GAMMA_FACTOR 2
        #define USE_ENVMAP
        #define ENVMAP_TYPE_CUBE_UV
        #define TEXTURE_LOD_EXT
        #define TONE_MAPPING

        // Uniforms
        uniform vec3 color;
        uniform float roughness;
        uniform float metalness;
        uniform float envMapIntensity;
        uniform sampler2D envMap;
        uniform float flipEnvMap;
        uniform int maxMipLevel;
        // Shader chunks
        #include <common>
        #include <bsdfs>
        #include <cube_uv_reflection_fragment>
        #include <envmap_pars_fragment>
        #include <envmap_physical_pars_fragment>
        #include <lights_physical_pars_fragment>

        void main() {
            vec3 diffuseColor = color;
            float roughnessFactor = clamp( roughness, 0.04, 1.0 );
            float metalnessFactor = metalness;
            // Geometry
            GeometricContext geometry;
            geometry.normal = normalize( vNormal );
            geometry.viewDir = normalize( vViewPosition );

            // material
            PhysicalMaterial material;
            material.diffuseColor = diffuseColor;
            material.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );
            material.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );
            // Calculate reflection
            vec3 radiance = getLightProbeIndirectRadiance( geometry.viewDir, geometry.normal, material.specularRoughness, maxMipLevel );
            gl_FragColor = vec4( radiance, 1.0 );

            #include <tonemapping_fragment>
            #include <encodings_fragment>

This is supposed to just show the HDR environment reflection with roughness support but the result looks a bit odd. It works when I switch the HDRs DataType to FloatType, but then, it doesn’t work on mobiles.

Any ideas?

I’ve also attached the full code. (1.2 MB)

Any help would be more than welcome.

Best regards,

Any ideas anyone? @Mugen87?

still interested for an answer on this one if anyone knows how to fix it.

The problem is that custom shader materials do not benefit of certain features provided for built-in materials. E.g. defines are not automatically set and encoding functions are not properly configured. In your fragment shader, you have the following function for decoding texels of an env map:

vec4 envMapTexelToLinear( vec4 value ) { return LinearToLinear( value ); }

It should be

vec4 envMapTexelToLinear( vec4 value ) { return RGBMToLinear( value, 16.0 ); }

If you are not aware of such automatisms, I suggest you use Material.onBeforeCompile() or no shader chunks at all (so write the entire shader code by yourself).

If you want to keep the current approach, reset the loader’s data type to THREE.UnsignedByteType and assign the environment map to your custom material like so:

cusSphereMesh.material.envMap = newEnvMap;

The renderer can now detect the map and derive the respective encoding.

WOW! That’s awesome! Many thanks @Mugen87!! much appreciated! That worked like a charm!

1 Like