mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.71k stars 35.38k forks source link

HDRI not working with ShaderMaterial #24617

Closed coramat closed 2 years ago

coramat commented 2 years ago

Hi Everyone,

I am having issues using an environment map with ShaderMaterial.

I am loading the envMap using THREE.CubeTextureLoader, which works perfectly with MeshStandardMaterial but has no effect if I use instead ShaderMaterial, as you can see in this fiddle example: https://jsfiddle.net/3jo6Ldpf/2/ .

The cube on the right has a ShaderMaterial applied and the HDRI is not showing up. In the same example, I tried using the option: (line 51)

materialShader.defines.ENVMAP_TYPE_CUBE_UV = ''

and I get the following error:

ERROR: 0:484: 'CUBEUV_MAX_MIP' : undeclared identifier
ERROR: 0:485: 'CUBEUV_TEXEL_WIDTH' : undeclared identifier
ERROR: 0:486: 'CUBEUV_TEXEL_HEIGHT' : undeclared identifier
ERROR: 0:523: 'CUBEUV_MAX_MIP' : undeclared identifier

There is a function in WebGLProgram.js called generateCubeUVSize that is suppose to compute the values of CUBEUV_MAX_MIP, CUBEUV_TEXEL_WIDTH, and CUBEUV_TEXEL_HEIGHT, and then automatically generating the corresponding definitions(defines), so I was puzzled why it was not working in this case.

When I tried manually defining those options by adding in the same fiddle example these lines of code:

  materialShader.defines.CUBEUV_MAX_MIP = '4'
  materialShader.defines.CUBEUV_TEXEL_WIDTH = '0.03'
  materialShader.defines.CUBEUV_TEXEL_HEIGHT = '0.03'

I got this error:

'textureCubeUV' : no matching overloaded function found

regarding the shader chunk envmap_physical_pars_fragment.glsl.js.

This gave me the hint that envMap passed in the function textureCubeUV is not the sampler2D uniform expected by the function but something else. The type of envmap seems to derive from the shaderChunk envmap_common_pars_fragment.glsl.js, which define envmap as uniform samplerCube envMap; rather than uniform sampler2D envMap; if ENVMAP_TYPE_CUBE has been defined.

So I wanted to understand who defines ENVMAP_TYPE_CUBE, since in the fiddle code I defined ENVMAP_TYPE_CUBE_UV, and in WebGLPrograms.js I found this piece of code:

const environment = material.isMeshStandardMaterial ? scene.environment : null;
const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment );
const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null;

where it seems that if you do not have a MeshStandardMaterial you cannot have cubeuvmaps.

This code could also explain why I was getting the error about CUBEUV_MAX_MIP etc not being defined, because those defines are happening only if envMapCubeUVHeight is not null.

Is there a way to have an HDRI working with ShaderMaterial?

I am not a threejs expert so I might be not considering something trivial. I just tried to debug as much as I could until I got stuck to avoid bothering you folks <3


Mugen87 commented 2 years ago

Nit: You are not using a HDR environment map in your fiddle. Try using RGBELoader or EXRLoader.

Mugen87 commented 2 years ago

Updated fiddle: https://jsfiddle.net/c5hjnpzm/1/

If you copy the code from MeshStandardMaterial to a custom shader, you should know that the GLSL is not compatible with an environment map in the cube map or equirectangular format. Only the cubeUV format works which is generated by using PMREMGenerator.

I suggest you only create shader materials from built-in material shader code if you really know the internals. Otherwise I recommend to use onBeforeCompile() and extend a built-in material with custom logic. This also requires knowledge about the shader source however less things can break.

If you need more help, please use the forum.

coramat commented 2 years ago

Nit: You are not using a HDR environment map in your fiddle. Try using RGBELoader or EXRLoader.

You are right, I am using an HDR in my project and I forgot to update that part in the fiddle.

Thanks a lot for the quick help!

arpu commented 2 years ago

@Mugen87 would it be possible to direct use the scene.enviroment Texture for a ShaderMaterial? updated the example https://jsfiddle.net/rudcsb16/3/

max-sym commented 1 year ago

@arpu I'm also trying to create a shader including scene.environment (or scene.background) as envMap. @Mugen87 can you explain a bit on how defines get automatically passed in your initial updated fiddle (https://jsfiddle.net/c5hjnpzm/1/) ? The one OP provided included defines but yours doesn't and it works.. How can that also be applied for ShaderMaterial + scene.environment as envMap. Thanks!

Mugen87 commented 1 year ago

would it be possible to direct use the scene.enviroment Texture for a ShaderMaterial?

No, that does not work since Scene.environment only affects PBR materials. And you can not guarantee that a custom shader material is always PBR conform.

can you explain a bit on how defines get automatically passed in your initial updated fiddle

The environment map defines are automatically generated when the renderer encounters the envMap property when processing a material. IIRC, the original fiddle did not work since the env map type was wrong (so it was unrelated to defines).

viperphase1 commented 8 months ago

I, like @max-sym, am interested in writing a ShaderMaterial that can use the scene's environment when the material's envMap property is not defined. Just to clarify @Mugen87, are you saying that is not possible? If it is possible what would we need to do in fragment code to make that happen? I'm having a hard time reverse engineering how it works on the built in materials.

Mugen87 commented 8 months ago

Just to clarify @Mugen87, are you saying that is not possible?

No it isn't. You have to apply hacks and modifications to your project to make this work which I do not recommend. Consider to use onBeforeCompile() and modify an instance of MeshStandardMaterial or MeshPhysicalMaterial.

Please use the forum if you need more guidance in this context.