当前位置:   article > 正文

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

acesfilmictonemapping

初始代码 

  1. import "./style.css";
  2. import * as THREE from "three";
  3. import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
  4. import * as dat from "dat.gui";
  5. import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
  6. /**
  7. * Base
  8. */
  9. // Debug
  10. const gui = new dat.GUI();
  11. // Canvas
  12. const canvas = document.querySelector("canvas.webgl");
  13. // Scene
  14. const scene = new THREE.Scene();
  15. const dirLight = new THREE.DirectionalLight("#ffffff", 3);
  16. dirLight.position.set(0.25, 3, -2.25);
  17. scene.add(dirLight);
  18. /**
  19. * Test sphere
  20. */
  21. const testSphere = new THREE.Mesh(
  22. new THREE.SphereGeometry(1, 32, 32),
  23. new THREE.MeshStandardMaterial()
  24. )
  25. scene.add(testSphere)
  26. /**
  27. * Sizes
  28. */
  29. const sizes = {
  30. width: window.innerWidth,
  31. height: window.innerHeight,
  32. };
  33. window.addEventListener("resize", () => {
  34. // Update sizes
  35. sizes.width = window.innerWidth;
  36. sizes.height = window.innerHeight;
  37. // Update camera
  38. camera.aspect = sizes.width / sizes.height;
  39. camera.updateProjectionMatrix();
  40. // Update renderer
  41. renderer.setSize(sizes.width, sizes.height);
  42. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  43. });
  44. /**
  45. * Camera
  46. */
  47. // Base camera
  48. const camera = new THREE.PerspectiveCamera(
  49. 75,
  50. sizes.width / sizes.height,
  51. 0.1,
  52. 100
  53. );
  54. camera.position.set(4, 1, -4);
  55. scene.add(camera);
  56. // Controls
  57. const controls = new OrbitControls(camera, canvas);
  58. controls.enableDamping = true;
  59. /**
  60. * Renderer
  61. */
  62. const renderer = new THREE.WebGLRenderer({
  63. canvas: canvas,
  64. antialias: true,
  65. });
  66. renderer.setSize(sizes.width, sizes.height);
  67. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  68. /**
  69. * Animate
  70. */
  71. const tick = () => {
  72. // Update controls
  73. controls.update();
  74. // Render
  75. renderer.render(scene, camera);
  76. // Call tick again on the next frame
  77. window.requestAnimationFrame(tick);
  78. };
  79. tick();

初始效果: 

 添加灯光GUI

  1. gui
  2. .add(dirLight, "intensity")
  3. .min(0)
  4. .max(10)
  5. .step(0.001)
  6. .name("lightIntensity");
  7. gui.add(dirLight.position, "x").min(-5).max(5).step(0.001).name("lightX");
  8. gui.add(dirLight.position, "y").min(-5).max(5).step(0.001).name("lightY");
  9. gui.add(dirLight.position, "z").min(-5).max(5).step(0.001).name("lightZ");

 physicallyCorrectLights

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

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

 加载渲染模型

  1. const gltfLoader=new GLTFLoader()
  2. gltfLoader.load("/models/FlightHelmet/glTF/FlightHelmet.gltf", (gltf) => {
  3. const helmet = gltf.scene;
  4. helmet.scale.set(10, 10, 10);
  5. helmet.position.set(0, -4, 0);
  6. helmet.rotation.y = THREE.MathUtils.degToRad(90);
  7. scene.add(helmet);
  8. gui
  9. .add(helmet.rotation, "y")
  10. .min(-Math.PI)
  11. .max(Math.PI)
  12. .step(0.001)
  13. .name("rotation");
  14. });

 

渲染环境

  1. const cubeTextureLoader = new THREE.CubeTextureLoader();
  2. const envMap = cubeTextureLoader.load([
  3. "/textures/environmentMaps/0/px.jpg",
  4. "/textures/environmentMaps/0/nx.jpg",
  5. "/textures/environmentMaps/0/py.jpg",
  6. "/textures/environmentMaps/0/ny.jpg",
  7. "/textures/environmentMaps/0/pz.jpg",
  8. "/textures/environmentMaps/0/nz.jpg",
  9. ]);
  10. scene.background=envMap

模型加入环境贴图

.traverse ( callback : Function ) : null

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

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

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

1.创建函数

  1. const updateAllMats = () => {
  2. scene.traverse((child) => {
  3. console.log(child)
  4. });
  5. };

 2.调用函数

  1. gltfLoader.load("/models/FlightHelmet/glTF/FlightHelmet.gltf", (gltf) => {
  2. const helmet = gltf.scene;
  3. helmet.scale.set(10, 10, 10);
  4. helmet.position.set(0, -4, 0);
  5. helmet.rotation.y = THREE.MathUtils.degToRad(90);
  6. scene.add(helmet);
  7. updateAllMats()
  8. gui
  9. .add(helmet.rotation, "y")
  10. .min(-Math.PI)
  11. .max(Math.PI)
  12. .step(0.001)
  13. .name("rotation");
  14. });

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

  1. const updateAllMats = () => {
  2. scene.traverse((child) => {
  3. console.log(child)
  4. if (
  5. child instanceof THREE.Mesh &&
  6. child.material instanceof THREE.MeshStandardMaterial
  7. ) {
  8. child.material.envMap = envMap;
  9. child.material.needsUpdate = true;
  10. child.castShadow = true;
  11. child.receiveShadow = true;
  12. }
  13. });
  14. };

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

调节envMapIntensity可以看到效果

 渲染器

outputEncoding

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

  1. const renderer = new THREE.WebGLRenderer({
  2. canvas: canvas,
  3. antialias: true,
  4. });
  5. renderer.setSize(sizes.width, sizes.height);
  6. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  7. renderer.physicallyCorrectLights=true
  8. 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 来改变查看效果对比 

  1. gui.add(renderer,"toneMapping",{
  2. No: THREE.NoToneMapping ,
  3. Linear:THREE.LinearToneMapping,
  4. Reinhard:THREE.ReinhardToneMapping,
  5. CineonTone:THREE.CineonToneMapping,
  6. ACESFilmicTone:THREE.ACESFilmicToneMapping
  7. }).onFinishChange(()=>{
  8. renderer.toneMapping=Number(renderer.toneMapping)
  9. })

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

同时让材质开启更新

child.material.needsUpdate = true;
  1. const updateAllMats = () => {
  2. scene.traverse((child) => {
  3. console.log(child)
  4. if (
  5. child instanceof THREE.Mesh &&
  6. child.material instanceof THREE.MeshStandardMaterial
  7. ) {
  8. child.material.envMap = envMap;
  9. child.material.needsUpdate = true;
  10. child.material.envMapIntensity=debugObject.envMapIntensity
  11. child.castShadow = true;
  12. child.receiveShadow = true;
  13. }
  14. });
  15. };
  16. ....
  17. gui.add(renderer,"toneMapping",{
  18. No: THREE.NoToneMapping ,
  19. Linear:THREE.LinearToneMapping,
  20. Reinhard:THREE.ReinhardToneMapping,
  21. CineonTone:THREE.CineonToneMapping,
  22. ACESFilmicTone:THREE.ACESFilmicToneMapping
  23. }).onFinishChange(()=>{
  24. renderer.toneMapping=Number(renderer.toneMapping)
  25. updateAllMats()
  26. })

角色曝光度 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

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

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

 当开启之后

 明显消除了锯齿化效果

shardows阴影

  1. dirLight.castShadow=truedirLight.shadow.mapSize.set(1024,1024)
  2. dirLight.shadow.camera.far=15
  3. dirLight.shadow.normalBias=0.05
  4. ...
  5. ...
  6. const updateAllMats = () => {
  7. scene.traverse((child) => {
  8. console.log(child)
  9. if (
  10. child instanceof THREE.Mesh &&
  11. child.material instanceof THREE.MeshStandardMaterial
  12. ) {
  13. child.material.envMap = envMap;
  14. child.material.needsUpdate = true;
  15. child.material.envMapIntensity=debugObject.envMapIntensity
  16. child.castShadow = true;
  17. child.receiveShadow = true;
  18. }
  19. });
  20. };
  21. ...
  22. ...
  23. renderer.castShadow=true
  24. renderer.shadowMap.enabled = true;
  25. renderer.shadowMap.type = THREE.PCFSoftShadowMap;

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/96022
推荐阅读
相关标签
  

闽ICP备14008679号