# THREE.WebGLRenderer 的参数设置

THREE.WebGLRenderer 对象的参数设置非常影响渲染效果

要想获得比较好的渲染效果,对 THREE.WebGLRenderer 可进行以下参数设置。

var renderer;
renderer = new THREE.WebGLRenderer({
      antialias:true
});
renderer.physicallyCorrectLights = true;
renderer.setPixelRatio( window.devicePixelRatio * 2);
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.gammaOutput = true;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputEncoding = THREE.sRGBEncoding;   
document.body.appendChild(renderer.domElement);

上述设置中,

# 抗锯齿

renderer = new THREE.WebGLRenderer({
      antialias:true,
      //alpha:true
});

antialias 设置为 true 表示开启抗锯齿。

# 物理灯光

renderer.physicallyCorrectLights = true;

表示将启用正确的物理灯光。

# 像素采样率

renderer.setPixelRatio( window.devicePixelRatio * 2);

表示使用 2 倍设备像素点采样,这有利于消除锯齿获得更加高质量的效果。

# 阴影贴图

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

上述设置表示启用阴影贴图,并将阴影贴图类型设置为 THREE.PCFSoftShadowMap ,其中不同阴影贴图类型的设置如下:

  • THREE.BasicShadowMap :能够给出没有经过过滤的阴影映射,速度最快,但质量最差
  • THREE.PCFShadowMap :默认值,使用 Percentage-Closer Filtering (PCF) 算法来过滤阴影映射
  • THREE.PCFSoftShadowMap :和 PCFShadowMap 一样使用 Percentage-Closer Filtering (PCF) 算法过滤阴影映射,但在使用低分辨率阴影图时具有更好的软阴影
  • THREE.VSMShadowMap :使用 Variance Shadow Map (VSM) 算法来过滤阴影映射。当使用 VSMShadowMap 时,所有阴影接收者也将会投射阴影

# 色彩映射

renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;

设置这个属性主要是为了在普通计算机显示器或者移动设备屏幕等低动态范围介质上,模拟、逼近高动态范围(HDR)效果。

这个属性如果要生效就是需要将 hdr 贴图作为场景的环境贴图,获取 IBL 的光照效果。使用 hdr 贴图作为场景的环境贴图也将大幅度提升场景或者模型的渲染效果。

其中 toneMapping 算法可选择:

  • THREE.NoToneMapping
  • THREE.LinearToneMapping
  • THREE.ReinhardToneMapping
  • THREE.CineonToneMapping
  • THREE.ACESFilmicToneMapping

这里我们使用的是效果最好的 ACES 算法,当然你也可以自定义自己的 tonemapping 算法,如何自定义可参考官方示例。

toneMappingExposure 表示曝光级别,值越大曝光程度越高,场景中的光线越充足,模型就越亮。

# 渲染器输出编码

renderer.outputEncoding = THREE.sRGBEncoding;

设置渲染器的输出编码,默认的输出编码为 THREE.LinearEncoding ,我们这里设置为 THREE.sRGBEncoding

# 调整贴图的 encoding

three.js 将贴图的编码都默认设置为 THREE.LinearEncoding ,导致图片色彩失真(色彩不像正常那么鲜艳,会灰蒙蒙的),所以务必将场景中的所有贴图的编码都调整为 THREE.sRGBEncoding

# 贴图修改 encoding

const textureLoader = new THREE.TextureLoader();
textureLoader.load( "./model_D.png", function(texture){
     texture.encoding = THREE.sRGBEncoding;
});

# 环境贴图修改 encoding

const hdrUrl = './res/hdr/ballroom_1k.hdr'
new THREE.RGBELoader().load(hdrUrl, texture => {
    const gen = new THREE.PMREMGenerator(renderer)
    envMap = gen.fromEquirectangular(texture).texture;
    envMap.encoding = THREE.sRGBEncoding;
    scene.environment = envMap;
    scene.background = envMap;
    texture.dispose();
    gen.dispose();
})

# 场景中的光照调整

# 固定光源

# 环境光 AmbientLight

环境光会均匀的照亮场景中的所有物体,环境光不能产生阴影,因为环境光没有方向。

const light = new THREE.AmbientLight( 0x404040 ); // soft white light
scene.add( light );

# 半球光 HemisphereLight

半球光同样也是照亮场景中的所有物体,并且光照颜色会从天空光照颜色慢慢的渐变到地面光照颜色。

const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 );
scene.add( light );

# 平行光,定向光 DirectionalLight

平行光是沿着特定方向发射的光。这种光的表现像是无限远,发出的平行光线,常常用来模拟太阳光。

平行光可以产生阴影,所以我们可以对平行光的阴影进行更多的设置。

const directional_light = new THREE.DirectionalLight( 0xffffff, 1 );
directional_light.position.set( 0, 1, 0 ); 
directional_light.castShadow = true; 
scene.add( directional_light );
directional_light.shadow.mapSize.width = 512 * 2; 
directional_light.shadow.mapSize.height = 512 * 2; 
directional_light.shadow.bias = 0.05;
directional_light.shadow.normalBias = 0.05;
directional_light.shadow.camera.near = 0.5; 
directional_light.shadow.camera.far = 500;

# 点光源 PointLight

点光源为为一个点向各个方向发射的光源,可以理解为一个灯泡发出的光。

点光源同样也可以产生阴影,所以我们也可以对点光源的阴影进行更多设置。

const point_light = new THREE.PointLight( 0xffffff, 1, 100 );
point_light.position.set( 0, 10, 4 );
point_light.castShadow = true;
scene.add( point_light );
point_light.shadow.mapSize.width = 512 * 2; 
point_light.shadow.mapSize.height = 512 * 2; 
point_light.shadow.bias = 0.05;
point_light.shadow.normalBias = 0.05;
point_light.shadow.camera.near = 0.5; 
point_light.shadow.camera.far = 500;

# 聚光灯 SpotLight

聚光灯光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大。

聚光灯同样也可以产生阴影,所以我们也可以对点光源的阴影进行更多设置。

const spot_light = new THREE.SpotLight( 0xffffff );
spot_light.position.set( 100, 1000, 100 );
spot_light.castShadow = true;
scene.add( spot_light );
spot_light.shadow.mapSize.width = 512 * 2; 
spot_light.shadow.mapSize.height = 512 * 2; 
spot_light.shadow.camera.near = 500;
spot_light.shadow.camera.far = 4000;
spot_light.shadow.camera.fov = 30;

# 区域光 RectAreaLight

区域光不支持阴影,因此没有更多的阴影设置。

const width = 10;
const height = 10;
const intensity = 1;
const rect_light = new THREE.RectAreaLight( 0xffffff, intensity,  width, height );
rect_light.position.set( 5, 5, 0 );
rect_light.lookAt( 0, 0, 0 );
scene.add( rect_light )

# BL(基于图像的光照)

基于图像的光照(Image based lighting, IBL)通过一张环境贴图(Environment Map)来保存物体周围的环境信息,并通过一系列处理模拟真实环境中的光照效果。IBL 与固定光源相比能够提供更加真实的光照效果,所以如果你用固定光源得到的效果很差,可以使用一张好的 hdr 作为场景的环境贴图,相信渲染效果会更加真实。

这里我们一般使用 hdr 作为我们的环境贴图,找好符合场景渲染要求的 hdr 环境贴图,然后设置场景的 toneMapping,

renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;

然后加载 hdr 环境贴图,如下:

const hdrUrl = './res/hdr/ballroom_1k.hdr'
new THREE.RGBELoader().load(hdrUrl, texture => {
    const gen = new THREE.PMREMGenerator(renderer)
    envMap = gen.fromEquirectangular(texture).texture;
    envMap.encoding = THREE.sRGBEncoding;
    envMap.material = THREE.EquirectangularReflectionMapping;
    scene.environment = envMap;
    scene.background = envMap;
    texture.dispose();
    gen.dispose();
})

然后修改场景中所有的 mesh 的材质的环境贴图

scene.traverse(child => {
    if (child instanceof THREE.Mesh) {
        child.material.envMap = envMap;
        child.material.envMapIntensity = envMapIntensity;
        child.material.needsUpdate = true;
        child.castShadow = true;
        child.receiveShadow = true;
    }
})

# 加载模型的设置

在加载模型时也需要对模型进行一些设置才能有好的效果,如何设置可参考以下代码

new THREE.GLTFLoader().load('./example.glb', result => {
    model = result.scene || result.scenes[0];
    model.traverse(child => {
        if ( child.isMesh ) {
            child.castShadow = true;
            child.receiveShadow = true;
            if(child.material.map) {
                child.material.map.encoding = THREE.sRGBEncoding;
                child.material.map.anisotropy = 1;
            }
            if (child.material.emissiveMap) {
                child.material.emissiveMap.encoding = THREE.sRGBEncoding;
            }
            if (child.material.map || child.material.emissiveMap) {
                child.material.needsUpdate = true;
            }
            if(envMap) {
                child.material.envMap = envMap;
                child.material.envMapIntensity = 1;
            }
        }
    })
})

在加载模型完成之后,需要遍历模型的所有网格,并将所有网格材质的贴图的 encoding 修改为 THREE.sRGBEncoding ,然后设置所有网格材质贴图的环境贴图以及强度。

# 三维模型

建议使用 glb 格式的模型