ShaderMaterial 的 Threejs 反射和光泽度问题
Threejs reflection and glossiness issues with ShaderMaterial
我正在尝试为 threejs 开发自己的自定义 glsl 着色器,但我正在使用 HDR 图像叠加光泽反射。它适用于 LDR 图像,但不适用于 HDR。
我开始使用 this example 来生成 mipmap。
然后我使用浏览器扩展从默认的 MeshStandardMaterial 中获取编译后的代码,我隔离了我需要的与环境贴图相关的位,我编写了我的着色器(我在着色器),但我可能遗漏了一些东西,因为这似乎对我不起作用。
在下图中,您可以看到在具有普通 HDRI(没有 mipmap)的版本 1) 中,我不得不将 DataType 更改为 "THREE.FloatType"。尽管这看起来仍然是错误的(没有光泽而且很暗),但它是我设法得到的最接近的。
版本 2) 使用默认 "THREE.UnsignedDataType" 但图像看起来完全错误。
版本 3) 这是包含 mipmap 的版本,只是错误。
这里有一个 link 下载所有文件或只看下面的代码。
<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>
您必须在片段着色器中将 envMapTexelToLinear()
函数更改为 RGBEToLinear()
。
这是 WebGLProgram 在编译着色器时通常 does automatically 的内容,它会找到 envMap.encoding = THREE.RGBEEncoding
。但是,由于您使用的是自定义着色器,而不是默认着色器 material,因此您必须手动编写此步骤。
这是一个 HDRI 图像的简单示例。在右侧,我正在读取线性 space 中的纹素,它会产生时髦的过饱和颜色。在左侧,我将纹素从 RGBE 转换为线性,它很好地映射到预期输出:
我正在尝试为 threejs 开发自己的自定义 glsl 着色器,但我正在使用 HDR 图像叠加光泽反射。它适用于 LDR 图像,但不适用于 HDR。
我开始使用 this example 来生成 mipmap。
然后我使用浏览器扩展从默认的 MeshStandardMaterial 中获取编译后的代码,我隔离了我需要的与环境贴图相关的位,我编写了我的着色器(我在着色器),但我可能遗漏了一些东西,因为这似乎对我不起作用。
在下图中,您可以看到在具有普通 HDRI(没有 mipmap)的版本 1) 中,我不得不将 DataType 更改为 "THREE.FloatType"。尽管这看起来仍然是错误的(没有光泽而且很暗),但它是我设法得到的最接近的。 版本 2) 使用默认 "THREE.UnsignedDataType" 但图像看起来完全错误。 版本 3) 这是包含 mipmap 的版本,只是错误。
这里有一个 link 下载所有文件或只看下面的代码。
<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>
您必须在片段着色器中将 envMapTexelToLinear()
函数更改为 RGBEToLinear()
。
这是 WebGLProgram 在编译着色器时通常 does automatically 的内容,它会找到 envMap.encoding = THREE.RGBEEncoding
。但是,由于您使用的是自定义着色器,而不是默认着色器 material,因此您必须手动编写此步骤。
这是一个 HDRI 图像的简单示例。在右侧,我正在读取线性 space 中的纹素,它会产生时髦的过饱和颜色。在左侧,我将纹素从 RGBE 转换为线性,它很好地映射到预期输出: