美文网首页
threejs-day2(场景/材质/纹理/光照)

threejs-day2(场景/材质/纹理/光照)

作者: 小杰66 | 来源:发表于2021-04-30 23:40 被阅读0次

    场景

    主要就是父子关系坐标系关系。直接上例子

    import * as THREE from "../../three/build/three";
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    renderer.setClearColor(0xaaaaaa);
    renderer.shadowMap.enable = true; //开启阴影贴图
    
    function makeCamera(fov = 40) {
      const aspect = 2;
      const Znear = 0.1;
      const ZFar = 1000;
      return new THREE.PerspectiveCamera(fov, aspect, Znear, ZFar);
    }
    
    const camera = makeCamera();
    camera.position.set(8, 4, 10).multiplyScalar(3);
    camera.lookAt(0, 0, 0);
    
    const scene = new THREE.Scene();
    
    {
      const light = new THREE.DirectionalLight(0xffffff, 1);
      light.position.set(0, 20, 0);
      scene.add(light);
    
      light.castShadow = true;
      light.shadow.mapSize.width = 2048;
      light.shadow.mapSize.height = 2048;
    
      const d = 50;
      light.shadow.camera.left = -d;
      light.shadow.camera.right = d;
      light.shadow.camera.top = d;
      light.shadow.camera.bottom = -d;
      light.shadow.camera.near = 1;
      light.shadow.camera.far = 50;
      light.shadow.bias = 0.001;
    }
    
    {
      const light = new THREE.DirectionalLight(0xffffff, 1);
      light.position.set(1, 2, 4);
      scene.add(light);
    }
    
    const groundGeometry = new THREE.PlaneGeometry(50, 50);
    const groundMaterial = new THREE.MeshPhongMaterial({ color: 0xcc8866 });
    const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
    groundMesh.rotation.x = Math.PI * -0.5;
    groundMesh.receiveShadow = true;
    scene.add(groundMesh);
    
    const [carWidth, carHeight, carLength] = [4, 1, 8];
    const tank = new THREE.Object3D();
    scene.add(tank);
    
    const bodyGeometry = new THREE.BoxGeometry(carWidth, carHeight, carLength);
    const bodyMaterial = new THREE.MeshPhongMaterial({ color: 0x6688aa });
    const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial);
    bodyMesh.position.y = 1.4;
    bodyMesh.castShadow = true;
    tank.add(bodyMesh);
    
    const tankCameraFov = 75;
    const tankCamera = makeCamera(tankCameraFov);
    tankCamera.position.set(0, 3, -6);
    tankCamera.rotation.y = Math.PI;
    bodyMesh.add(tankCamera);
    
    const [wheelRadius, wheelThickness, wheelSegments] = [1, 0.5, 6];
    const wheelGeometry = new THREE.CylinderGeometry(
      wheelRadius,
      wheelRadius,
      wheelThickness,
      wheelSegments
    );
    const wheelMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 });
    const wheelPositions = [
      [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, carLength / 3],
      [carWidth / 2 + wheelThickness / 2, -carHeight / 2, carLength / 3],
      [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, 0],
      [carWidth / 2 + wheelThickness / 2, -carHeight / 2, 0],
      [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, -carLength / 3],
      [carWidth / 2 + wheelThickness / 2, -carHeight / 2, -carLength / 3],
    ];
    const wheelMeshes = wheelPositions.map((position) => {
      const mesh = new THREE.Mesh(wheelGeometry, wheelMaterial);
      mesh.position.set(...position);
      mesh.rotation.z = Math.PI * 0.5;
      mesh.castShadow = true;
      bodyMesh.add(mesh);
      return mesh;
    });
    
    const [
      domeRadius,
      domeWidthSubdivisions,
      domeHeightSubdivisions,
      domePhiStart,
      domePhiEnd,
      domeThetaStart,
      domeThetaEnd,
    ] = [2, 12, 12, 0, Math.PI * 2, 0, Math.PI * 0.5];
    
    const domeGeometry = new THREE.SphereGeometry(
      domeRadius,
      domeWidthSubdivisions,
      domeHeightSubdivisions,
      domePhiStart,
      domePhiEnd,
      domeThetaStart,
      domeThetaEnd
    );
    const domeMesh = new THREE.Mesh(domeGeometry, bodyMaterial);
    domeMesh.castShadow = true;
    bodyMesh.add(domeMesh);
    domeMesh.position.y = 0.5;
    
    const [turretWidth, turretHeight, turretLength] = [
      0.1,
      0.1,
      carLength * (0.75 * 0.2),
    ];
    const turretGeometry = new THREE.BoxGeometry(
      turretWidth,
      turretHeight,
      turretLength
    );
    const turretMesh = new THREE.Mesh(turretGeometry, bodyMaterial);
    turretMesh.castShadow = true;
    
    const turretPivot = new THREE.Object3D();
    turretPivot.scale.set(5, 5, 5);
    turretPivot.position.y = 0.5;
    turretPivot.position.z = turretLength * 0.5;
    turretPivot.add(turretMesh);
    bodyMesh.add(turretPivot);
    
    const turretCamera = makeCamera();
    turretCamera.position.y = 0.75 * 0.2;
    turretMesh.add(turretCamera);
    
    const targetGeometry = new THREE.SphereGeometry(0.5, 6, 3);
    const targetMaterial = new THREE.MeshPhongMaterial({
      color: 0x00ff00,
      flatShading: true,
    });
    const targetMesh = new THREE.Mesh(targetGeometry, targetMaterial);
    targetMesh.castShadow = true;
    
    const targetBob = new THREE.Object3D();
    targetBob.add(targetMesh);
    
    const targetElevation = new THREE.Object3D();
    targetElevation.position.y = 8;
    targetElevation.position.z = carLength * 2;
    targetElevation.add(targetBob);
    
    const targetOrbit = new THREE.Object3D();
    targetOrbit.add(targetElevation);
    scene.add(targetOrbit);
    
    const targetCamera = makeCamera();
    targetCamera.position.set(0, 1, -2);
    targetCamera.rotation.y = Math.PI;
    
    const targetCameraPivot = new THREE.Object3D();
    targetCameraPivot.add(targetCamera);
    targetBob.add(targetCameraPivot);
    
    const curve = new THREE.SplineCurve([
      new THREE.Vector2(-10, 0),
      new THREE.Vector2(-5, 5),
      new THREE.Vector2(0, 0),
      new THREE.Vector2(5, -5),
      new THREE.Vector2(10, 0),
      new THREE.Vector2(5, 10),
      new THREE.Vector2(-5, 10),
      new THREE.Vector2(-10, -10),
      new THREE.Vector2(-15, -8),
      new THREE.Vector2(-10, 0),
    ]);
    const points = curve.getPoints(50);
    const geometry = new THREE.BufferGeometry().setFromPoints(points);
    const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
    const splineObject = new THREE.Line(geometry, material);
    splineObject.rotation.x = Math.PI * 0.5;
    splineObject.position.y = 0.05;
    scene.add(splineObject);
    
    const targetPosition = new THREE.Vector3();
    const tankPosition = new THREE.Vector2();
    const tankTarget = new THREE.Vector2();
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      let needResize = width !== canvas.width || height !== canvas.height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    const infoElem = document.createElement("div");
    document.body.appendChild(infoElem);
    
    const cameras = [
      { cam: camera, desc: "detached camera" },
      { cam: turretCamera, desc: "on turret looking at target" },
      { cam: targetCamera, desc: "near target looking at tank" },
      { cam: tankCamera, desc: "above back of tank" },
    ];
    
    function render(time) {
      time *= 0.001;
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        cameras.forEach((cameraInfo) => {
          const camera = cameraInfo.cam;
          camera.aspect = canvas.clientWidth / canvas.clientHeight;
          camera.updateProjectionMatrix();
        });
      }
    
      const tankTime = time * 0.05;
      curve.getPointAt(tankTime % 1, tankPosition);
      curve.getPointAt((tankTime + 0.01) % 1, tankTarget);
      tank.position.set(tankPosition.x, 0, tankPosition.y);
      tank.lookAt(tankTarget.x, 0, tankTarget.y);
    
      targetOrbit.rotation.y = time * 0.27;
      targetBob.position.y = Math.sin(time * 2) * 4;
      targetMesh.rotation.x = time * 7;
      targetMesh.rotation.y = time * 13;
      targetMaterial.emissive.setHSL((time * 10) % 1, 1, 0.25);
      targetMaterial.color.setHSL((time * 10) % 1, 1, 0.25);
    
      targetMesh.getWorldPosition(targetPosition);
      turretPivot.lookAt(targetPosition);
    
      turretCamera.lookAt(targetPosition);
    
      tank.getWorldPosition(targetPosition);
      targetCameraPivot.lookAt(targetPosition);
    
      const camera = cameras[(time * 0.25) % cameras.length | 0];
      infoElem.textContent = camera.desc;
    
      renderer.render(scene, camera.cam);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    
    动画.gif

    材质

    MeshBasicMaterial 不受光照影响
    MeshLambertMaterial 只在顶点计算光照
    MeshPhongMaterial在每个像素计算光照,支持镜面高光shininess
    MeshToonMaterial 类似MeshPhongMaterial,使用渐变图着色。
    MeshStandardMaterial相比MeshPhongMaterial它通过roughness和metalness 设置粗糙度和金属度
    MeshPhysicalMaterial相比MeshStandardMaterial增加了clearcoat表示透明涂层的厚度,clearCoatRoughness 表示透明涂层的粗糙度
    shadowMaterial用于接收阴影
    MeshDepthMaterial渲染每个像素的深度
    MeshNormalMaterial显示几何体的法线
    ShaderMaterial通过着色器自定义材质
    RawShaderMaterial制作完全自定义的着色器

    有些材质设置需要手动更新材质变化material.needsUpdate
    例如修改flatShading,添加删除纹理等等。(删除纹理改成使用1*1像素的白色纹理更好)。

    纹理

    1. 加载纹理
    const loader = new THREE.TextureLoader();
    const texture = loader.load('resources/images/flower-1.jpg');
    
    1. 不等待纹理加载完成,可能会出现下面的情况。优点是会立即开始渲染。可以通过回调保证加载完成loaderload('img',(texture)=>{})

      动画1.gif
    2. 等待多纹理加载可以使用loadManager

    const loadManager = new THREE.LoadingManager();
    const loader = new THREE.TextureLoader(loadManager);
     
    const materials = [
      new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
      new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
    ];
     // 完成回调
    loadManager.onLoad = () => {
      const cube = new THREE.Mesh(geometry, materials);
      scene.add(cube);
    };
    // 进度回调
    loadManager.onProgress = (urlOfLastItemLoaded, itemsLoaded, itemsTotal) => {
    // todo
    };
    
    1. 纹理会占用 宽度*高度*4*1.33字节的内存。所以尽可能文件小且尺寸小。
    2. PNG支持透明度,可以非图像数据的格式,比如法线图等。
    3. magFilter当纹理绘制的尺寸大于其原始尺寸时
    • NearestFilter 从原始纹理中选取最接近的一个像素
    • LinearFilter 从纹理中选择离我们应该选择颜色最近的4个像素,并根据实际点与4个像素的距离,以适当的比例进行混合。
    1. minFilter 在绘制纹理的尺寸小于其原始尺寸时
    • NearestFilter 在纹理中选择最近的像素。
    • LinearFilter 从纹理中选择4个像素,然后混合它们
    • NearestMipmapNearestFilter 选择最近的mip,然后选择一个像素。
    • NearestMipmapLinearFilter选择2个mips,从每个mips中选择一个像素,混合这2个像素。
    • LinearMipmapNearestFilter选择合适的mip,然后选择4个像素并将它们混合。
    • LinearMipmapLinearFilter选择2个mips,从每个mips中选择4个像素,然后将所有8个像素混合成1个像素。
    1. 纹理重复 wrapSwrapT
    • ClampToEdgeWrapping 每条边上的最后一个像素无限重复。
    • RepeatWrapping纹理重复
    • MirroredRepeatWrapping在每次重复时将进行镜像
    1. 纹理偏移 offset
    2. 纹理旋转
    //旋转中心
    someTexture.center.set(.5, .5);
    //旋转弧度
    someTexture.rotation = THREE.MathUtils.degToRad(45);
    
    import * as THREE from "../../three/build/three";
    import * as dat from "dat.gui";
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0xaaaaaa);
    
    const fov = 75;
    const aspect = 2;
    const near = 0.1;
    const far = 5;
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.z = 2;
    
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const loader = new THREE.TextureLoader();
    const texture = loader.load("../assets/images/wall.jpg");
    const material = new THREE.MeshBasicMaterial({
      map: texture,
    });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);
    
    class DegRadHelper {
      constructor(obj, prop) {
        this.obj = obj;
        this.prop = prop;
      }
      get value() {
        return THREE.MathUtils.radToDeg(this.obj[this.prop]);
      }
      set value(v) {
        this.obj[this.prop] = THREE.MathUtils.degToRad(v);
      }
    }
    
    class StringToNumberHelper {
      constructor(obj, prop) {
        this.obj = obj;
        this.prop = prop;
      }
      get value() {
        return this.obj[this.prop];
      }
      set value(v) {
        this.obj[this.prop] = parseFloat(v);
      }
    }
    
    const wrapModes = {
      ClampToEdgeWrapping: THREE.ClampToEdgeWrapping,
      RepeatWrapping: THREE.RepeatWrapping,
      MirroredRepeatWrapping: THREE.MirroredRepeatWrapping,
    };
    
    function updateTexture() {
      texture.needsUpdate = true;
    }
    
    const gui = new dat.GUI();
    gui
      .add(new StringToNumberHelper(texture, "wrapS"), "value", wrapModes)
      .name("texture.wrapS")
      .onChange(updateTexture);
    gui
      .add(new StringToNumberHelper(texture, "wrapT"), "value", wrapModes)
      .name("texture.wrapT")
      .onChange(updateTexture);
    gui.add(texture.repeat, "x", 0, 5, 0.01).name("texture.repeat.x");
    gui.add(texture.repeat, "y", 0, 5, 0.01).name("texture.repeat.y");
    gui.add(texture.offset, "x", -2, 2, 0.01).name("texture.offset.x");
    gui.add(texture.offset, "y", -2, 2, 0.01).name("texture.offset.y");
    gui.add(texture.center, "x", -0.5, 1.5, 0.01).name("texture.center.x");
    gui.add(texture.center, "y", -0.5, 1.5, 0.01).name("texture.center.y");
    gui
      .add(new DegRadHelper(texture, "rotation"), "value", -360, 360)
      .name("texture.rotation");
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render(time) {
      time *= 0.001;
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
    
      cube.rotation.x += 0.005;
      cube.rotation.y += 0.005;
    
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    

    光照

    环境光AmbientLight

    简单地将材质的颜色与光照颜色进行叠加,再乘以光照强度。它没有方向,无法产生阴影,场景内任何一点受到的光照强度都是相同的,除了改变场景内所有物体的颜色以外,不会使物体产生明暗的变化,看起来并不像真正意义上的光照。通常的作用是提亮场景,让暗部不要太暗。

    import * as THREE from "../../three/build/three";
    import { OrbitControls } from "../../three/examples/jsm/controls/OrbitControls";
    import { GUI } from "dat.gui";
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color("black");
    
    const [fov, aspect, near, far] = [45, 2, 0.1, 100];
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 10, 20);
    
    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 5, 0);
    controls.update();
    
    class ColorGUIHelper {
      constructor(object, prop) {
        this.object = object;
        this.prop = prop;
      }
      get value() {
        return `#${this.object[this.prop].getHexString()}`;
      }
    
      set value(hexString) {
        this.object[this.prop].set(hexString);
      }
    }
    
    {
      const color = 0xffffff;
      const intensity = 1;
      const light = new THREE.AmbientLight(color, intensity);
      scene.add(light);
    
      const gui = new GUI();
      gui.addColor(new ColorGUIHelper(light, "color"), "value").name("color");
      gui.add(light, "intensity", 0, 2, 0.01);
    }
    
    {
      const planeSize = 40;
      const loader = new THREE.TextureLoader();
      const texture = loader.load("./assets/images/checker.png");
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.magFilter = THREE.NearestFilter;
      const repeats = planeSize / 2;
      texture.repeat.set(repeats, repeats);
    
      const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
      const planeMat = new THREE.MeshPhongMaterial({
        map: texture,
        side: THREE.DoubleSide,
      });
      const mesh = new THREE.Mesh(planeGeo, planeMat);
      mesh.rotation.x = Math.PI * -0.5;
      scene.add(mesh);
    }
    
    {
      const cubeSize = 4;
      const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
      const cubeMat = new THREE.MeshPhongMaterial({ color: "#8ac" });
      const mesh = new THREE.Mesh(cubeGeo, cubeMat);
      mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
      scene.add(mesh);
    }
    
    {
      const sphereRadius = 3;
      const sphereWidthDivisions = 32;
      const sphereHeightDivisions = 16;
      const sphereGeo = new THREE.SphereGeometry(
        sphereRadius,
        sphereWidthDivisions,
        sphereHeightDivisions
      );
      const sphereMat = new THREE.MeshPhongMaterial({ color: "#CA8" });
      const mesh = new THREE.Mesh(sphereGeo, sphereMat);
      mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
      scene.add(mesh);
    }
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
    
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    

    半球光HemisphereLight

    半球光HemisphereLight的颜色是从天空到地面两个颜色的渐变,与物体材质的颜色叠加后得到最终的颜色。一个点受到的光照颜色是由所在平面的朝向(法向量)决定的 —— 面向正上方就受到天空的光照颜色,面向正下方就受到地面的光照颜色,其他角度则是两个颜色渐变区间的颜色。
    HemisphereLight与其他类型光照结合使用,可以很好地表现天空和地面颜色照射到物体上的效果。所以最好的使用场景就是与其他光照结合使用。

    {
      const skyColor = 0xb1e1ff;
      const groundColor = 0xb97a20;
      const intensity = 1;
      const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
      scene.add(light);
    
      const gui = new GUI();
      gui.addColor(new ColorGUIHelper(light, "color"), "value").name("skyColor");
      gui
        .addColor(new ColorGUIHelper(light, "groundColor"), "value")
        .name("groundColor");
      gui.add(light, "intensity", 0, 2, 0.01);
    }
    

    方向光DirectionalLight

    常常用来表现太阳光照的效果。

    import * as THREE from "../../three/build/three";
    import { OrbitControls } from "../../three/examples/jsm/controls/OrbitControls";
    import { GUI } from "dat.gui";
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color("black");
    
    const [fov, aspect, near, far] = [45, 2, 0.1, 100];
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 10, 20);
    
    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 5, 0);
    controls.update();
    
    class ColorGUIHelper {
      constructor(object, prop) {
        this.object = object;
        this.prop = prop;
      }
      get value() {
        return `#${this.object[this.prop].getHexString()}`;
      }
    
      set value(hexString) {
        this.object[this.prop].set(hexString);
      }
    }
    
    {
      function makeXYZGUI(gui, vector3, name, onChangeFn) {
        const folder = gui.addFolder(name);
        folder.add(vector3, "x", -10, 10).onChange(onChangeFn);
        folder.add(vector3, "y", 0, 10).onChange(onChangeFn);
        folder.add(vector3, "z", -10, 10).onChange(onChangeFn);
        folder.open();
      }
    
      function updateLight() {
        light.target.updateMatrixWorld();
        helper.update();
      }
    
      const color = 0xffffff;
      const intensity = 1;
      const light = new THREE.DirectionalLight(color, intensity);
      light.position.set(0, 10, 0);
      light.target.position.set(-5, 0, 0);
      scene.add(light);
      scene.add(light.target);
      const gui = new GUI();
      gui.addColor(new ColorGUIHelper(light, "color"), "value").name("color");
      gui.add(light, "intensity", 0, 2, 0.01);
      gui.add(light.target.position, "x", -10, 10);
      gui.add(light.target.position, "z", -10, 10);
      gui.add(light.target.position, "y", 0, 10);
      makeXYZGUI(gui, light.position, "position", updateLight);
      makeXYZGUI(gui, light.target.position, "target", updateLight);
    
      const helper = new THREE.DirectionalLightHelper(light);
      scene.add(helper);
    }
    
    {
      const planeSize = 40;
      const loader = new THREE.TextureLoader();
      const texture = loader.load("./assets/images/checker.png");
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.magFilter = THREE.NearestFilter;
      const repeats = planeSize / 2;
      texture.repeat.set(repeats, repeats);
    
      const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
      const planeMat = new THREE.MeshPhongMaterial({
        map: texture,
        side: THREE.DoubleSide,
      });
      const mesh = new THREE.Mesh(planeGeo, planeMat);
      mesh.rotation.x = Math.PI * -0.5;
      scene.add(mesh);
    }
    
    {
      const cubeSize = 4;
      const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
      const cubeMat = new THREE.MeshPhongMaterial({ color: "#8ac" });
      const mesh = new THREE.Mesh(cubeGeo, cubeMat);
      mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
      scene.add(mesh);
    }
    
    {
      const sphereRadius = 3;
      const sphereWidthDivisions = 32;
      const sphereHeightDivisions = 16;
      const sphereGeo = new THREE.SphereGeometry(
        sphereRadius,
        sphereWidthDivisions,
        sphereHeightDivisions
      );
      const sphereMat = new THREE.MeshPhongMaterial({ color: "#CA8" });
      const mesh = new THREE.Mesh(sphereGeo, sphereMat);
      mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
      scene.add(mesh);
    }
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
    
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    

    点光源PointLight

    表示的是从一个点朝各个方向发射出光线的一种光照效果。

    import * as THREE from "../../three/build/three";
    import { OrbitControls } from "../../three/examples/jsm/controls/OrbitControls";
    import { GUI } from "dat.gui";
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color("black");
    
    const [fov, aspect, near, far] = [45, 2, 0.1, 100];
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 10, 20);
    
    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 5, 0);
    controls.update();
    
    class ColorGUIHelper {
      constructor(object, prop) {
        this.object = object;
        this.prop = prop;
      }
      get value() {
        return `#${this.object[this.prop].getHexString()}`;
      }
    
      set value(hexString) {
        this.object[this.prop].set(hexString);
      }
    }
    
    {
      const color = 0xffffff;
      const intensity = 1;
      const light = new THREE.PointLight(color, intensity);
      light.position.set(0, 10, 0);
      scene.add(light);
    
      const helper = new THREE.PointLightHelper(light);
      scene.add(helper);
    
      function updateLight() {
        helper.update();
      }
    
      const gui = new GUI();
      gui.addColor(new ColorGUIHelper(light, "color"), "value").name("color");
      gui.add(light, "intensity", 0, 2, 0.01);
      gui.add(light, "distance", 0, 40).onChange(updateLight);
    
      makeXYZGUI(gui, light.position, "position", updateLight);
    
      function makeXYZGUI(gui, vector3, name, onChangeFn) {
        const folder = gui.addFolder(name);
        folder.add(vector3, "x", -10, 10).onChange(onChangeFn);
        folder.add(vector3, "y", 0, 10).onChange(onChangeFn);
        folder.add(vector3, "z", -10, 10).onChange(onChangeFn);
        folder.open();
      }
    }
    
    {
      const planeSize = 40;
      const loader = new THREE.TextureLoader();
      const texture = loader.load("./assets/images/checker.png");
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.magFilter = THREE.NearestFilter;
      const repeats = planeSize / 2;
      texture.repeat.set(repeats, repeats);
    
      const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
      const planeMat = new THREE.MeshPhongMaterial({
        map: texture,
        side: THREE.DoubleSide,
      });
      const mesh = new THREE.Mesh(planeGeo, planeMat);
      mesh.rotation.x = Math.PI * -0.5;
      scene.add(mesh);
    }
    
    {
      const cubeSize = 4;
      const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
      const cubeMat = new THREE.MeshPhongMaterial({ color: "#8ac" });
      const mesh = new THREE.Mesh(cubeGeo, cubeMat);
      mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
      scene.add(mesh);
    }
    
    {
      const sphereRadius = 3;
      const sphereWidthDivisions = 32;
      const sphereHeightDivisions = 16;
      const sphereGeo = new THREE.SphereGeometry(
        sphereRadius,
        sphereWidthDivisions,
        sphereHeightDivisions
      );
      const sphereMat = new THREE.MeshPhongMaterial({ color: "#CA8" });
      const mesh = new THREE.Mesh(sphereGeo, sphereMat);
      mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
      scene.add(mesh);
    }
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
    
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    

    聚光灯SpotLight

    import * as THREE from "../../three/build/three";
    import { OrbitControls } from "../../three/examples/jsm/controls/OrbitControls";
    import { GUI } from "dat.gui";
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color("black");
    
    const [fov, aspect, near, far] = [45, 2, 0.1, 100];
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 10, 20);
    
    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 5, 0);
    controls.update();
    
    class ColorGUIHelper {
      constructor(object, prop) {
        this.object = object;
        this.prop = prop;
      }
      get value() {
        return `#${this.object[this.prop].getHexString()}`;
      }
    
      set value(hexString) {
        this.object[this.prop].set(hexString);
      }
    }
    
    class DegRadHelper {
      constructor(obj, prop) {
        this.obj = obj;
        this.prop = prop;
      }
      get value() {
        return THREE.MathUtils.radToDeg(this.obj[this.prop]);
      }
      set value(v) {
        this.obj[this.prop] = THREE.MathUtils.degToRad(v);
      }
    }
    
    {
      const color = 0xffffff;
      const intensity = 1;
      const light = new THREE.SpotLight(color, intensity);
      light.position.set(0, 10, 0);
      light.target.position.set(-5, 0, 0);
      scene.add(light);
      scene.add(light.target);
    
      const helper = new THREE.SpotLightHelper(light);
      scene.add(helper);
    
      function updateLight() {
        light.target.updateMatrixWorld();
        helper.update();
      }
      updateLight()
    
      const gui = new GUI();
      gui.addColor(new ColorGUIHelper(light, "color"), "value").name("color");
      gui.add(light, "intensity", 0, 2, 0.01);
      gui.add(light, "distance", 0, 40).onChange(updateLight);
      gui
        .add(new DegRadHelper(light, "angle"), "value", 0, 90)
        .name("angle")
        .onChange(updateLight);
      gui.add(light, "penumbra", 0, 1, 0.01);
      makeXYZGUI(gui, light.position, "position", updateLight);
      makeXYZGUI(gui, light.target.position, "target", updateLight);
    
      function makeXYZGUI(gui, vector3, name, onChangeFn) {
        const folder = gui.addFolder(name);
        folder.add(vector3, "x", -10, 10).onChange(onChangeFn);
        folder.add(vector3, "y", 0, 10).onChange(onChangeFn);
        folder.add(vector3, "z", -10, 10).onChange(onChangeFn);
        folder.open();
      }
    }
    
    {
      const planeSize = 40;
      const loader = new THREE.TextureLoader();
      const texture = loader.load("./assets/images/checker.png");
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.magFilter = THREE.NearestFilter;
      const repeats = planeSize / 2;
      texture.repeat.set(repeats, repeats);
    
      const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
      const planeMat = new THREE.MeshPhongMaterial({
        map: texture,
        side: THREE.DoubleSide,
      });
      const mesh = new THREE.Mesh(planeGeo, planeMat);
      mesh.rotation.x = Math.PI * -0.5;
      scene.add(mesh);
    }
    
    {
      const cubeSize = 4;
      const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
      const cubeMat = new THREE.MeshPhongMaterial({ color: "#8ac" });
      const mesh = new THREE.Mesh(cubeGeo, cubeMat);
      mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
      scene.add(mesh);
    }
    
    {
      const sphereRadius = 3;
      const sphereWidthDivisions = 32;
      const sphereHeightDivisions = 16;
      const sphereGeo = new THREE.SphereGeometry(
        sphereRadius,
        sphereWidthDivisions,
        sphereHeightDivisions
      );
      const sphereMat = new THREE.MeshPhongMaterial({ color: "#CA8" });
      const mesh = new THREE.Mesh(sphereGeo, sphereMat);
      mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
      scene.add(mesh);
    }
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
    
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    

    矩形区域光RectAreaLight

    表示一个矩形区域的发射出来的光照,例如长条的日光灯
    RectAreaLight只能影响MeshStandardMaterial,MeshPhysicalMaterial

    import * as THREE from "../../three/build/three";
    import { OrbitControls } from "../../three/examples/jsm/controls/OrbitControls";
    import { GUI } from "dat.gui";
    import { RectAreaLightUniformsLib } from "../../three/examples/jsm/lights/RectAreaLightUniformsLib";
    import { RectAreaLightHelper } from "../../three/examples/jsm/helpers/RectAreaLightHelper";
    
    RectAreaLightUniformsLib.init();
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color("black");
    
    const [fov, aspect, near, far] = [45, 2, 0.1, 100];
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 10, 20);
    
    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 5, 0);
    controls.update();
    
    class ColorGUIHelper {
      constructor(object, prop) {
        this.object = object;
        this.prop = prop;
      }
      get value() {
        return `#${this.object[this.prop].getHexString()}`;
      }
    
      set value(hexString) {
        this.object[this.prop].set(hexString);
      }
    }
    
    class DegRadHelper {
      constructor(obj, prop) {
        this.obj = obj;
        this.prop = prop;
      }
      get value() {
        return THREE.MathUtils.radToDeg(this.obj[this.prop]);
      }
      set value(v) {
        this.obj[this.prop] = THREE.MathUtils.degToRad(v);
      }
    }
    
    {
      const color = 0xffffff;
      const intensity = 5;
      const width = 12;
      const height = 4;
      const light = new THREE.RectAreaLight(color, intensity, width, height);
      light.position.set(0, 10, 0);
      light.rotation.x = THREE.MathUtils.degToRad(-90);
      scene.add(light);
    
      const helper = new RectAreaLightHelper(light);
      light.add(helper);
    
      function updateLight() {
        helper.update();
      }
    
      const gui = new GUI();
      gui.addColor(new ColorGUIHelper(light, "color"), "value").name("color");
      gui.add(light, "intensity", 0, 10, 0.01);
      gui.add(light, "width", 0, 20);
      gui.add(light, "height", 0, 20);
      gui
        .add(new DegRadHelper(light.rotation, "x"), "value", -180, 180)
        .name("x rotation");
      gui
        .add(new DegRadHelper(light.rotation, "y"), "value", -180, 180)
        .name("y rotation");
      gui
        .add(new DegRadHelper(light.rotation, "z"), "value", -180, 180)
        .name("z rotation");
    
      makeXYZGUI(gui, light.position, "position");
    
      function makeXYZGUI(gui, vector3, name, onChangeFn) {
        const folder = gui.addFolder(name);
        folder.add(vector3, "x", -10, 10).onChange(onChangeFn);
        folder.add(vector3, "y", 0, 10).onChange(onChangeFn);
        folder.add(vector3, "z", -10, 10).onChange(onChangeFn);
        folder.open();
      }
    }
    
    {
      const planeSize = 40;
      const loader = new THREE.TextureLoader();
      const texture = loader.load("./assets/images/checker.png");
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.magFilter = THREE.NearestFilter;
      const repeats = planeSize / 2;
      texture.repeat.set(repeats, repeats);
    
      const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
      const planeMat = new THREE.MeshStandardMaterial({
        map: texture,
        side: THREE.DoubleSide,
      });
      const mesh = new THREE.Mesh(planeGeo, planeMat);
      mesh.rotation.x = Math.PI * -0.5;
      scene.add(mesh);
    }
    
    {
      const cubeSize = 4;
      const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
      const cubeMat = new THREE.MeshStandardMaterial({ color: "#8ac" });
      const mesh = new THREE.Mesh(cubeGeo, cubeMat);
      mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
      scene.add(mesh);
    }
    
    {
      const sphereRadius = 3;
      const sphereWidthDivisions = 32;
      const sphereHeightDivisions = 16;
      const sphereGeo = new THREE.SphereGeometry(
        sphereRadius,
        sphereWidthDivisions,
        sphereHeightDivisions
      );
      const sphereMat = new THREE.MeshStandardMaterial({ color: "#CA8" });
      const mesh = new THREE.Mesh(sphereGeo, sphereMat);
      mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
      scene.add(mesh);
    }
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
    
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    

    WebGLRenderer.physicallyCorrectLights

    这个设置会影响点光源PointLight和聚光灯SpotLight,矩形区域光RectAreaLight会自动应用这个特性。
    在设置光照时,不要设置 distance 来表现光照的衰减,也不要设置 intensity。而是设置光照的 power属性,以流明为单位,three.js 会进行物理计算,从而表现出接近真实的光照效果。在这种情况下 three.js 参与计算的长度单位是米,一个 60瓦 的灯泡大概是 800 流明强度。并且光源有一个 decay 属性,为了模拟真实效果,应该被设置为 2

    import * as THREE from "../../three/build/three";
    import { OrbitControls } from "../../three/examples/jsm/controls/OrbitControls";
    import { GUI } from "dat.gui";
    
    const canvas = document.querySelector("#canvas");
    const renderer = new THREE.WebGLRenderer({ canvas });
    renderer.physicallyCorrectLights = true;
    
    const scene = new THREE.Scene();
    scene.background = new THREE.Color("black");
    
    const [fov, aspect, near, far] = [45, 2, 0.1, 100];
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 10, 20);
    
    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 5, 0);
    controls.update();
    
    class ColorGUIHelper {
      constructor(object, prop) {
        this.object = object;
        this.prop = prop;
      }
      get value() {
        return `#${this.object[this.prop].getHexString()}`;
      }
    
      set value(hexString) {
        this.object[this.prop].set(hexString);
      }
    }
    
    {
      const color = 0xffffff;
      const intensity = 1;
      const light = new THREE.PointLight(color, intensity);
    
      light.power = 800;
      light.decay = 2;
      light.distance = Infinity;
    
      light.position.set(0, 10, 0);
      scene.add(light);
    
      const helper = new THREE.PointLightHelper(light);
      scene.add(helper);
    
      function updateLight() {
        helper.update();
      }
    
      const gui = new GUI();
      gui.addColor(new ColorGUIHelper(light, "color"), "value").name("color");
      gui.add(light, "decay", 0, 4, 0.01);
      gui.add(light, "power", 0, 2000);
    
      makeXYZGUI(gui, light.position, "position", updateLight);
    
      function makeXYZGUI(gui, vector3, name, onChangeFn) {
        const folder = gui.addFolder(name);
        folder.add(vector3, "x", -10, 10).onChange(onChangeFn);
        folder.add(vector3, "y", 0, 10).onChange(onChangeFn);
        folder.add(vector3, "z", -10, 10).onChange(onChangeFn);
        folder.open();
      }
    }
    
    {
      const planeSize = 40;
      const loader = new THREE.TextureLoader();
      const texture = loader.load("./assets/images/checker.png");
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
      texture.magFilter = THREE.NearestFilter;
      const repeats = planeSize / 2;
      texture.repeat.set(repeats, repeats);
    
      const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
      const planeMat = new THREE.MeshPhongMaterial({
        map: texture,
        side: THREE.DoubleSide,
      });
      const mesh = new THREE.Mesh(planeGeo, planeMat);
      mesh.rotation.x = Math.PI * -0.5;
      scene.add(mesh);
    }
    
    {
      const cubeSize = 4;
      const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
      const cubeMat = new THREE.MeshPhongMaterial({ color: "#8ac" });
      const mesh = new THREE.Mesh(cubeGeo, cubeMat);
      mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
      scene.add(mesh);
    }
    
    {
      const sphereRadius = 3;
      const sphereWidthDivisions = 32;
      const sphereHeightDivisions = 16;
      const sphereGeo = new THREE.SphereGeometry(
        sphereRadius,
        sphereWidthDivisions,
        sphereHeightDivisions
      );
      const sphereMat = new THREE.MeshPhongMaterial({ color: "#CA8" });
      const mesh = new THREE.Mesh(sphereGeo, sphereMat);
      mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
      scene.add(mesh);
    }
    
    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) {
        renderer.setSize(width, height, false);
      }
      return needResize;
    }
    
    function render() {
      if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
      }
    
      renderer.render(scene, camera);
      requestAnimationFrame(render);
    }
    
    requestAnimationFrame(render);
    
    

    最后

    每添加一个光源到场景中,都会降低 three.js 渲染场景的速度,所以应该尽量使用最少的资源来实现想要的效果。

    相关文章

      网友评论

          本文标题:threejs-day2(场景/材质/纹理/光照)

          本文链接:https://www.haomeiwen.com/subject/hxygrltx.html