ThreeJs 学习之旅(十八)—Realistic render(真实渲染)

初始代码 

import "./style.css";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

/**
 * Base
 */
// Debug
const gui = new dat.GUI();

// Canvas
const canvas = document.querySelector("canvas.webgl");

// Scene
const scene = new THREE.Scene();
const dirLight = new THREE.DirectionalLight("#ffffff", 3);
dirLight.position.set(0.25, 3, -2.25);
scene.add(dirLight);

/**
 * Test sphere
 */
const testSphere = new THREE.Mesh(
    new THREE.SphereGeometry(1, 32, 32),
    new THREE.MeshStandardMaterial()
)
scene.add(testSphere)




/**
 * Sizes
 */
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};

window.addEventListener("resize", () => {
  // Update sizes
  sizes.width = window.innerWidth;
  sizes.height = window.innerHeight;

  // Update camera
  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();

  // Update renderer
  renderer.setSize(sizes.width, sizes.height);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(
  75,
  sizes.width / sizes.height,
  0.1,
  100
);
camera.position.set(4, 1, -4);
scene.add(camera);

// Controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));



/**
 * Animate
 */
const tick = () => {
  // Update controls
  controls.update();

  // Render
  renderer.render(scene, camera);

  // Call tick again on the next frame
  window.requestAnimationFrame(tick);
};

tick();

初始效果: 

 添加灯光GUI

gui
  .add(dirLight, "intensity")
  .min(0)
  .max(10)
  .step(0.001)
  .name("lightIntensity");
gui.add(dirLight.position, "x").min(-5).max(5).step(0.001).name("lightX");
gui.add(dirLight.position, "y").min(-5).max(5).step(0.001).name("lightY");
gui.add(dirLight.position, "z").min(-5).max(5).step(0.001).name("lightZ");

 physicallyCorrectLights

        : Boolean 默认false ,物理的正确的照明方式

const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.physicallyCorrectLights=true

 加载渲染模型

const gltfLoader=new GLTFLoader()
gltfLoader.load("/models/FlightHelmet/glTF/FlightHelmet.gltf", (gltf) => {
  const helmet = gltf.scene;
  helmet.scale.set(10, 10, 10);
  helmet.position.set(0, -4, 0);
  helmet.rotation.y = THREE.MathUtils.degToRad(90);
  scene.add(helmet);
  gui
    .add(helmet.rotation, "y")
    .min(-Math.PI)
    .max(Math.PI)
    .step(0.001)
    .name("rotation");
});

 

渲染环境

const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMap = cubeTextureLoader.load([
  "/textures/environmentMaps/0/px.jpg",
  "/textures/environmentMaps/0/nx.jpg",
  "/textures/environmentMaps/0/py.jpg",
  "/textures/environmentMaps/0/ny.jpg",
  "/textures/environmentMaps/0/pz.jpg",
  "/textures/environmentMaps/0/nz.jpg",
]);
scene.background=envMap

模型加入环境贴图

.traverse ( callback : Function ) : null

callback - 以一个object3D对象作为第一个参数的函数。

通过此函数我们可以遍历加载的对象 从而分析出加载模型的类型

目的 在以后的每个分部分上可以添加阴影等其他效果

1.创建函数


const updateAllMats = () => {
  scene.traverse((child) => {
    console.log(child)
    
  });
};

 2.调用函数

gltfLoader.load("/models/FlightHelmet/glTF/FlightHelmet.gltf", (gltf) => {
  const helmet = gltf.scene;
  helmet.scale.set(10, 10, 10);
  helmet.position.set(0, -4, 0);
  helmet.rotation.y = THREE.MathUtils.degToRad(90);
  scene.add(helmet);
  updateAllMats()
  gui
    .add(helmet.rotation, "y")
    .min(-Math.PI)
    .max(Math.PI)
    .step(0.001)
    .name("rotation");
});

 通过判断类型可以给 mesh加入envmap材质

const updateAllMats = () => {
  scene.traverse((child) => {
    console.log(child)
    if (
      child instanceof THREE.Mesh &&
      child.material instanceof THREE.MeshStandardMaterial
    ) {
      child.material.envMap = envMap;
      child.material.needsUpdate = true;
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
};

加入后效果变化不是很明显

调节envMapIntensity可以看到效果

 渲染器

outputEncoding

outputEncoding属性控制输出渲染编码。默认情况下,outputEncoding的值为THREE.LinearEncoding,看起来还行但是不真实,建议将值改为THREE.sRGBEncoding

const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.physicallyCorrectLights=true
renderer.outputEncoding = THREE.sRGBEncoding

除此之外还有另一个属性值为THREE.GammaEncoding,这种编码的优点在于它允许我们使用一种表现像亮度brightness的叫gammaFactor的值。GammaEncoding是一种存储颜色的方法,根据人眼的敏感度优化明暗值的存储方式。当使用sRGBEncoding时,其实就像使用默认gammaFactor值为2.2的GammaEncoding。
下面链接可以提供更多信息关于GammaEncoding和sRGBEncoding

Color management in three.js
https://medium.com/game-dev-daily/the-srgb-learning-curve-773b7f68cf7a
尽管这样就可能会有人认为GammaEncoding优于sRGBEncoding,因为我们可以在更暗或更亮的场景里控制gammaFactor,但是实际上这样做在物理层面上并不正确,下面会讲到如何更好管理亮度brightness

Textures encoding 

我们可以发现设置完渲染器的输出编码outputEncoding为THREE.sRGBEncoding后,我们的环境贴图颜色也改变了,虽然看起来效果不错,但我们还是要选择保留其原先正确的颜色。问题就在于我们设置完渲染器的输出编码之后,环境贴图的纹理还是默认的THREE.LinearEncoding。
其实规则很直接,所有我们能够直接看到的纹理贴图,比如map,就应该使用THREE.sRGBEncoding作为编码;而其他的纹理贴图比如法向纹理贴图normalMap就该使用THREE.LinearEncoding。
我们可以直接看到环境贴图,所以应该将其编码设为THREE.sRGBEncoding

 

 Tone mapping

renderer.toneMapping = THREE.ACESFilmicToneMapping

色调映射Tone mapping旨在将超高的动态范围HDR转换到我们日常显示的屏幕上的低动态范围LDR的过程。
说明一下HDR和LDR(摘自知乎LDR和HDR):

因为不同的厂家生产的屏幕亮度(物理)实际上是不统一的,那么我们在说LDR时,它是一个0到1范围的值,对应到不同的屏幕上就是匹配当前屏幕的最低亮度(0)和最高亮度(1)
自然界中的亮度差异是非常大的。例如,蜡烛的光强度大约为15,而太阳光的强度大约为10w。这中间的差异是非常大的,有着超级高的动态范围。
我们日常使用的屏幕,其最高亮度是经过一系列经验积累的,所以使用、用起来不会对眼睛有伤害;但自然界中的,比如我们直视太阳时,实际上是会对眼睛产生伤害的。
那为了改变色调映射tone mapping,则要更新WebGLRenderer上的toneMapping属性,有以下这些值

THREE.NoToneMapping (默认)
THREE.LinearToneMapping
THREE.ReinhardToneMapping
THREE.CineonToneMapping
THREE.ACESFilmicToneMapping
尽管我们的贴图不是HDR,但使用tone mapping可以塑造更真实的效果。

我们也可以通过GUI 来改变查看效果对比 

gui.add(renderer,"toneMapping",{
 No: THREE.NoToneMapping ,
 Linear:THREE.LinearToneMapping,
 Reinhard:THREE.ReinhardToneMapping,
 CineonTone:THREE.CineonToneMapping,
 ACESFilmicTone:THREE.ACESFilmicToneMapping
}).onFinishChange(()=>{
  renderer.toneMapping=Number(renderer.toneMapping)
})

但是这个效果只在环境上出现如果我们需要模型也相应的变化需要 在完成时调用updateAllMats

同时让材质开启更新

child.material.needsUpdate = true;
const updateAllMats = () => {
  scene.traverse((child) => {
    console.log(child)
    if (
      child instanceof THREE.Mesh &&
      child.material instanceof THREE.MeshStandardMaterial
    ) {
      child.material.envMap = envMap;
      child.material.needsUpdate = true;
      child.material.envMapIntensity=debugObject.envMapIntensity
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
};
....

gui.add(renderer,"toneMapping",{
 No: THREE.NoToneMapping ,
 Linear:THREE.LinearToneMapping,
 Reinhard:THREE.ReinhardToneMapping,
 CineonTone:THREE.CineonToneMapping,
 ACESFilmicTone:THREE.ACESFilmicToneMapping
}).onFinishChange(()=>{
  renderer.toneMapping=Number(renderer.toneMapping)
  updateAllMats()
})

角色曝光度 Three.js中文文档http://www.webgl3d.cn/threejs/docs/#api/zh/renderers/WebGLRenderer

 

gui.add(renderer,"toneMappingExposure").min(0).max(10).step(0.01).name("toneMappingExposure")

 

 抗锯齿化

当我们渲染的时候不开启 antialias

const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
});

const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
});

 当开启之后

 明显消除了锯齿化效果

shardows阴影

dirLight.castShadow=truedirLight.shadow.mapSize.set(1024,1024)
dirLight.shadow.camera.far=15
dirLight.shadow.normalBias=0.05

...
...

const updateAllMats = () => {
  scene.traverse((child) => {
    console.log(child)
    if (
      child instanceof THREE.Mesh &&
      child.material instanceof THREE.MeshStandardMaterial
    ) {
      child.material.envMap = envMap;
      child.material.needsUpdate = true;
      child.material.envMapIntensity=debugObject.envMapIntensity
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
};

...
...

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

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值