ThreeJS-3D教学四-光源

news2024/12/28 4:46:11

three模拟的真实3D环境,一个非常炫酷的功能便是对光源的操控,之前教学一中已经简单的描述了多种光源,这次咱们就详细的讲下一些最常见的光源:

AmbientLight
该灯光在全局范围内平等地照亮场景中的所有对象。
该灯光不能用于投射阴影,因为它没有方向。

AmbientLight( color : Integer, intensity : Float )
color - (可选)颜色的RGB分量的数值。默认值为0xffffff。
intensity - (可选)灯光强度/强度的数值。默认值为1。

DirectionalLight
向特定方向发射的光。这种光的行为就像它是无限遥远的,并且从它产生的光线都是平行的。这方面的常见用例是模拟日光;太阳离得足够远,它的位置可以被认为是无限的,所有来自它的光线都是平行的。

DirectionalLight( color : Integer, intensity : Float )
color - (可选)灯光的十六进制颜色。默认值为0xffffff(白色)。
intensity - (可选)灯光强度/强度的数值。默认值为1。

PointLight
从一个点向所有方向发射的光。这方面的一个常见用例是复制裸灯泡发出的光。

PointLight( color : Integer, intensity : Float, distance : Number, decay : Float )
color - (可选)灯光的十六进制颜色。默认值为0xffffff(白色)
intensity - (可选)灯光强度/强度的数值。默认值为1.
distance - 光线的最大范围。默认值为0(无限制)
decay - 灯光沿着灯光的距离变暗的量。默认值为2.

HemisphereLight
位于场景正上方的光源,颜色从天空颜色渐变为地面颜色。此灯光不能用于投射阴影。

HemisphereLight( skyColor : Integer, groundColor : Integer, intensity : Float )
skyColor - (可选)天空的十六进制颜色。默认为0xffffff。
groundColor - (可选)地球的十六进制颜色。默认为0xffffff。
intensity - 可选)光的强度/强度的数值。默认为1。

SpotLight
这种光从一个方向的单个点发射,沿着一个圆锥体,该圆锥体的大小随着光的传播而增加。类似车灯的效果,可以做射灯、车灯等

SpotLight( color : Integer, intensity : Float, distance : Float, angle : Radians, penumbra : Float, decay : Float )
color -(可选)灯光的十六进制颜色。默认值为0xffffff(白色)。
intensity -(可选)灯光强度/强度的数值。默认值为1。
distance - 灯光的最大范围。默认值为0(无限制)。
angle - 光从其方向散射的最大角度,其上限为Math。π/2。
penumbra - 由于半影而衰减的聚光灯圆锥体的百分比。取值介于0和1之间。默认值为零。
decay - 光线沿着光线的距离变暗的量。

看了定义后,我们先看下本次案例的效果图
在这里插入图片描述
这个案例中涉及到了以上所有光源效果,其中DirectionalLight和HemisphereLight是注释掉了的,为的是突出PointLight 和 SpotLight的效果,在代码中会发现AmbientLight的灯光强度我也特意变暗了。
案例中首次使用了GUI操作器和物体阴影效果。
GUI主要作用:
1、获取一个对象和该对象上的属性名,并根据属性的类型自动生成一个界面组件来操作该属性。
2、使用它后,我们可以通过界面组件来控制场景中的物体,提高调试效率。

看代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    body {
      width: 100%;
      height: 100%;
    }

    * {
      margin: 0;
      padding: 0;
    }

    .label {
      font-size: 20px;
      color: #000;
      font-weight: 700;
    }
  </style>
</head>
<body>
<div id="container"></div>
<script type="importmap">
  {
    "imports": {
      "three": "../three-155/build/three.module.js",
      "three/addons/": "../three-155/examples/jsm/"
    }
  }
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
import {GPUStatsPanel} from 'three/addons/utils/GPUStatsPanel.js';
import {CSS2DRenderer, CSS2DObject} from 'three/addons/renderers/CSS2DRenderer.js';
import {PLYLoader} from 'three/addons/loaders/PLYLoader.js';
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';

let stats, labelRenderer, gpuPanel, curve, lightHelper, textures;
let camera, scene, renderer, target, controls, lights, spotLight;
const group = new THREE.Group();
let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
const velocity = 0.0005; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
let widthImg = 200;
let heightImg = 200;
let time = 0;
init();
initHelp();
initLight();
axesHelperWord();
animate();
// 添加平面
addPlane();

lights = initPointLight(20, 1);
initSpotLight();
addModel();
addGUI();

function initPointLight(lightNum, lightR) {
  let vector = new THREE.Vector3();
  let geometry = new THREE.SphereGeometry(lightR, 15, 15);
  let lights = [];
  for (let i = 0; i < lightNum; i++) {
    let pointLight = new THREE.PointLight(0xff0000, 150, 30, 2);
    vector.set(Math.random(), Math.random(), Math.random()).normalize();
    pointLight.color.setRGB(vector.x, vector.y, vector.z);
    scene.add(pointLight);
    lights.push(pointLight);
    let material = new THREE.MeshBasicMaterial({color: pointLight.color});
    let emitter = new THREE.Mesh(geometry, material);
    pointLight.add(emitter);
  }
  return lights;
}

function initSpotLight() {
  // 位于场景正上方的光源,颜色从天空颜色渐变为地面颜色。此灯光不能用于投射阴影。
  // const ambient = new THREE.HemisphereLight( 0xffffff, 0x8d8d8d, 0.15 );
  // scene.add( ambient );

  const loader = new THREE.TextureLoader().setPath('../js_three-0.108.0/examples/textures/');
  const filenames = ['disturb.jpg', 'colors.png', 'uv_grid_opengl.jpg'];

  textures = {none: null};

  for (let i = 0; i < filenames.length; i++) {
    const filename = filenames[i];
    const texture = loader.load(filename);
    texture.minFilter = THREE.LinearFilter;
    texture.magFilter = THREE.LinearFilter;
    texture.colorSpace = THREE.SRGBColorSpace;
    textures[filename] = texture;
  }

  const targetObject = new THREE.Object3D();
  targetObject.position.set(-50, 0, -50);
  scene.add(targetObject);

  spotLight = new THREE.SpotLight(0xffffff, 100);
  spotLight.position.set(-50, 50, -50);
  spotLight.target = targetObject;
  spotLight.angle = Math.PI / 4;
  spotLight.intensity = 10000;
  spotLight.penumbra = 1;
  spotLight.decay = 1.5;
  spotLight.distance = 300;
  spotLight.map = textures['uv_grid_opengl.jpg'];

  spotLight.castShadow = true;
  spotLight.shadow.mapSize.width = 1024;
  spotLight.shadow.mapSize.height = 1024;
  spotLight.shadow.camera.near = 1;
  spotLight.shadow.camera.far = 200;
  spotLight.shadow.focus = 1;
  scene.add(spotLight);
  lightHelper = new THREE.SpotLightHelper(spotLight);
  scene.add(lightHelper);
}

function addModel() {
  new PLYLoader().load('../js_three-0.108.0/examples/models/ply/binary/Lucy100k.ply', function (geometry) {
    let scale = 0.02;
    geometry.scale(scale, scale, scale);
    geometry.computeVertexNormals();

    const material = new THREE.MeshLambertMaterial();

    const mesh = new THREE.Mesh(geometry, material);
    mesh.rotation.y = -Math.PI / 2;
    mesh.position.y = 12;
    mesh.position.x = -50;
    mesh.position.z = -50;
    mesh.castShadow = true;
    mesh.receiveShadow = true;
    scene.add(mesh);

  });
}

function addGUI() {
  // GUI
  const gui = new GUI();
  const params = {
    map: textures['disturb.jpg'],
    color: spotLight.color.getHex(),
    intensity: spotLight.intensity,
    distance: spotLight.distance,
    angle: spotLight.angle,
    penumbra: spotLight.penumbra,
    decay: spotLight.decay,
    focus: spotLight.shadow.focus,
    shadows: true
  };
  gui.add(params, 'map', textures).onChange(function (val) {
    spotLight.map = val;
  });
  gui.addColor(params, 'color').onChange(function (val) {
    spotLight.color.setHex(val);
  });
  gui.add(params, 'intensity', 0, 20000).onChange(function (val) {
    spotLight.intensity = val;
  });
  gui.add(params, 'distance', 50, 200).onChange(function (val) {
    spotLight.distance = val;
  });
  gui.add(params, 'angle', 0, Math.PI / 3).onChange(function (val) {
    spotLight.angle = val;
  });
  gui.add(params, 'penumbra', 0, 1).onChange(function (val) {
    spotLight.penumbra = val;
  });
  gui.add(params, 'decay', 1, 2).onChange(function (val) {
    spotLight.decay = val;
  });
  gui.add(params, 'focus', 0, 1).onChange(function (val) {
    spotLight.shadow.focus = val;
  });
  gui.add(params, 'shadows').onChange(function (val) {
    renderer.shadowMap.enabled = val;
    scene.traverse(function (child) {
      if (child.material) {
        child.material.needsUpdate = true;
      }
    });
  });
  gui.open();
}

function addPlane() {
  // 创建一个平面 PlaneGeometry(width, height, widthSegments, heightSegments)
  const planeGeometry = new THREE.PlaneGeometry(widthImg, heightImg, 1, 1);
  // 创建 Lambert 材质:会对场景中的光源作出反应,但表现为暗淡,而不光亮。
  const planeMaterial = new THREE.MeshPhongMaterial({
    color: 0xb2d3e6,
    side: THREE.DoubleSide
  });
  const plane = new THREE.Mesh(planeGeometry, planeMaterial);
  // 以自身中心为旋转轴,绕 x 轴顺时针旋转 45 度
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.set(0, -4, 0);
  plane.castShadow = true;
  plane.receiveShadow = true;
  scene.add(plane);
}

function init() {
  camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 10, 2000);
  camera.up.set(0, 1, 0);
  camera.position.set(60, 40, 60);
  camera.lookAt(0, 0, 0);

  scene = new THREE.Scene();
  scene.background = new THREE.Color('#ccc');

  renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.getElementById('container').appendChild(renderer.domElement);

  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  // 色调映射的曝光级别
  renderer.toneMappingExposure = 1;

  labelRenderer = new CSS2DRenderer();
  labelRenderer.setSize(window.innerWidth, window.innerHeight);
  labelRenderer.domElement.style.position = 'absolute';
  labelRenderer.domElement.style.top = '0px';
  labelRenderer.domElement.style.pointerEvents = 'none';
  document.getElementById('container').appendChild(labelRenderer.domElement);

  controls = new OrbitControls(camera, renderer.domElement);
  controls.mouseButtons = {
    LEFT: THREE.MOUSE.PAN,
    MIDDLE: THREE.MOUSE.DOLLY,
    RIGHT: THREE.MOUSE.ROTATE
  };
  controls.enablePan = true;
  // 设置最大最小视距
  controls.minDistance = 5;
  controls.maxDistance = 1200;

  window.addEventListener('resize', onWindowResize);

  stats = new Stats();
  stats.setMode(1); // 0: fps, 1: ms
  document.body.appendChild(stats.dom);

  gpuPanel = new GPUStatsPanel(renderer.getContext());
  stats.addPanel(gpuPanel);
  stats.showPanel(0);

  scene.add(group);
}

function initLight() {
  // const light = new THREE.DirectionalLight(new THREE.Color('rgb(253,253,253)'));
  // light.position.set(10, 10, 1);
  // light.intensity = 3; // 光线强度

  const AmbientLight = new THREE.AmbientLight(new THREE.Color('rgb(255, 255, 255)'), 0.3);
  // scene.add( light );
  scene.add(AmbientLight);
}

function initHelp() {
  // const size = 100;
  // const divisions = 5;
  // const gridHelper = new THREE.GridHelper( size, divisions );
  // scene.add( gridHelper );

  // The X axis is red. The Y axis is green. The Z axis is blue.
  const axesHelper = new THREE.AxesHelper(100);
  scene.add(axesHelper);
}

function axesHelperWord() {
  let xP = addWord('X轴');
  let yP = addWord('Y轴');
  let zP = addWord('Z轴');
  xP.position.set(50, 0, 0);
  yP.position.set(0, 50, 0);
  zP.position.set(0, 0, 50);
}

function addWord(word) {
  let name = `<span>${word}</span>`;
  let moonDiv = document.createElement('div');
  moonDiv.className = 'label';
  // moonDiv.textContent = 'Moon';
  // moonDiv.style.marginTop = '-1em';
  moonDiv.innerHTML = name;
  const label = new CSS2DObject(moonDiv);
  group.add(label);
  return label;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  requestAnimationFrame(animate);
  time += 0.05;
  if (lights) {
    let swordTime = time * 0.1;
    let dis = 60;
    for (let i = 0, il = lights.length; i < il; i++) {
      let light = lights[i];
      let x = Math.sin(swordTime + i * 7.0) * dis;
      let y = Math.cos(swordTime + i * 5.0) * dis;
      let z = Math.cos(swordTime + i * 3.0) * dis;
      light.position.set(x, y, z);
    }
  }

  if (lightHelper) {
    lightHelper.update();

    const time = performance.now() / 2000;

    spotLight.position.x = Math.cos(time) * 2 - 50;
    spotLight.position.z = Math.sin(time) * 2 - 50;
  }

  stats.update();
  controls.update();
  if (labelRenderer) {
    labelRenderer.render(scene, camera);
  }
  renderer.render(scene, camera);
}
</script>
</body>
</html>

关于shadow效果的设置我们需要特别注意:
1、我们需要首先在renderer中设置
2、需要阴影的物体需要设置
3、这一点是比较容易遗忘的,那就是物体通过光源产生的阴影 投射在哪,相应的物体也需要设置,这个点写这个案例时,我也忘了 折腾半天模型的投影看不到,突然想到我addPlane函数创建的平面没有设置,简直了,耗费了很长时间
当然案例中只是shadow的一种设置方式,后面会讲到别的方式。

案例中还有一个知识点,之所以我将人像模型放在边缘,也是为了这个。那就是spotLight光源默认指向中心点也就是 (0, 0, 0)点,这时我们还需要spotLight实现案例中的效果,必须改变spotLight的指向

  const targetObject = new THREE.Object3D();
  targetObject.position.set(-50, 0, -50);
  scene.add(targetObject);
  // 这样就可以了
  spotLight.target = targetObject;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1044976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CAN信号解析】使用python-can/cantools解析CAN数据

文章目录 1. 如何解析CAN消息1.1 简介1.2 python-can库使用2. python-can库介绍2.1 完整解析流程2.2 简单示例3. 总结与坑4. 代码示例1. 解析一个DBC2. 生成一个DBC3. 解析.asc数据 保存为.csv格式1. 如何解析CAN消息 关于CAN的基础知识,可阅读如下链接: CAN协议详解CAN消息…

作为SiteGPT替代品,HelpLook的优势是什么?

在当今快节奏的数字化世界中&#xff0c;企业不断寻求创新方式来简化运营并增强客户体验。由于聊天机器人能够自动化任务、提供快速响应并提供个性化互动&#xff0c;它们在业务运营中的使用变得非常重要。因此&#xff0c;企业越来越意识到像SiteGPT和HelpLook这样高效的聊天机…

分享5个自动生成PPT的网站

1、Mindshow 之前公众号里分享过这款做PPT的AI工具&#xff0c;这次再拿出来分享一次。 没别的原因&#xff0c;确实好用。 而且也是目前能够和ChatGPT结合使用最好的PPT工具之一。 直接在ChatGPT生成PPT内容&#xff0c;转成Markdown格式复制进来一键排版&#xff0c;一份P…

MySQL ——多表连接查询

一、&#xff08;左、右和全&#xff09;连接概念 内连接&#xff1a; 假设A和B表进行连接&#xff0c;使用内连接的话&#xff0c;凡是A表和B表能够匹配上的记录查询出来。A和B两张表没有主付之分&#xff0c;两张表是平等的。 关键字&#xff1a;inner join on 语句&#xf…

更好用的的MybatisPlus:MybatisFlex(上)

更好用的的MybatisPlus&#xff1a;MybatisFlex&#xff08;上&#xff09; 前言 Mybatis 是我们常用的一个 ORM 框架&#xff0c;而 MybatisPlus &#xff08;以下简称 MP&#xff09; 则是对 Mybatis 进行了一层封装&#xff0c;便捷了我们的开发工作&#xff0c;但是由于其…

进程管理--进程调度基本概念

进程调度 进程调度的核心代码实现参考 kernel/sched/ 目录文件&#xff0c;主要包含以下几个部分&#xff1a; 调度算法&#xff1a;Linux 中实现了多种不同的进程调度算法&#xff0c;如 CFS&#xff08;Completely Fair Scheduler&#xff09;、O(1) 调度算法、实时调度算法…

RocketMQ Dashboard说解

RocketMQ Dashboard 是 RocketMQ 的管控利器&#xff0c;为用户提供客户端和应用程序的各种事件、性能的统计信息&#xff0c;支持以可视化工具代替 Topic 配置、Broker 管理等命令行操作。 介绍​ 功能概览​ 面板功能运维修改nameserver 地址; 选用 VIPChannel驾驶舱查看 …

python编程:加速计算机,优化性能的关键一步——清理临时文件夹Temp

引言&#xff1a; 随着时间的推移&#xff0c;您可能会注意到计算机的性能开始变慢。这可能是由于许多因素导致的&#xff0c;其中一个常见的问题是临时文件的积累。临时文件是由操作系统和应用程序生成的临时性文件&#xff0c;它们在使用后往往被遗忘或忽视。在本篇博客中&am…

[移动通讯]【Carrier Aggregation-4】【LTE-6】

前言&#xff1a; 这里主要介绍一下CA 技术里面&#xff0c;物理层主要工作。 参考&#xff1a; 载波聚合&#xff08;CA&#xff09; 目录&#xff1a; 1&#xff1a; CA 总体分析流程 2&#xff1a; CA 物理层 一 CA 总体分析流程 二 CA物理层 2.1 载波聚合LOG分析的相关包…

基于微信小程序的英语互助小程序设计与实现(亮点:小组制打卡、模拟考试答题、错题本、学习论坛)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

如何看待中小企业实现数字化转型难的问题?_光点科技

随着科技的飞速发展和市场的竞争日益激烈&#xff0c;传统制造型企业不得不迎头赶上数字化时代的步伐&#xff0c;以保持竞争力并实现可持续增长。数字化转型已成为企业生存和成功的必经之路&#xff0c;但对于那些长期依赖传统方法的企业来说&#xff0c;这个过程可能会充满挑…

积加ERP与金蝶云星空对接集成日期范围报告查询打通销售出库新增

积加ERP与金蝶云星空对接集成日期范围报告查询打通销售出库新增 对接源平台:积加ERP 积加创始人及核心产品技术团队&#xff0c;深耕于跨境电商技术领域十余年&#xff0c;深刻领悟卖家在Amazon业务运营各环节的核心诉求&#xff0c;结合多年技术管理经验&#xff0c;全面考虑中…

18年互联网老兵:技术人如何快速成长?!

见字如面&#xff0c;我是军哥。 先问你几个问题。 你是不是每天996&#xff0c;但技术能力还是没有提高&#xff1f; 你是不是明明很努力却进步缓慢&#xff1f; 你是不是在同一家公司干了三年&#xff0c;感觉自己就有三年工作经验了&#xff1f; 你是不是明明知道想要什么&a…

【调度算法】进程调度算法、内存页面置换算法、LRU算法、LFU算法、磁盘调度算法等重点知识汇总

目录 进程调度算法 内存页面置换算法 LRU算法实现 LFU算法实现 磁盘调度算法 进程调度算法 当 CPU 空闲时&#xff0c;操作系统就选择内存中的某个「就绪状态」的进程&#xff0c;并给其分配 CPU。 什么时候会发生 CPU 调度呢&#xff1f;通常有以下情况&#xff1a; 当…

数字孪生燃气可视化系统的九问九答

关键词&#xff1a;数字孪生燃气、智慧燃气、数字孪生燃气系统、智慧燃气平台、智慧燃气场站 谈谈数字孪生燃气系统&#xff1f; 数字孪生燃气是将数字孪生技术应用于燃气运营中&#xff0c;与燃气系统物理实体、数据中心、信息系统等相结合&#xff0c;从而完成辅助管网设计…

想要在手机上查看三维模型、正射影像、激光点云?快来试试这款app

「四维轻云」是一款轻量化的地理空间数据管理云平台&#xff0c;支持地理空间数据的在线管理、编辑及分享。平台有项目管理、场景搭建、发布分享、素材库等功能模块&#xff0c;支持多用户在线协作管理&#xff0c;实现了轻量化、便捷化的空间数据应用。 很多行业用户想要在手…

Observability:使用 OpenTelemetry 自动检测 Java 应用程序

作者&#xff1a;David Hope 在快节奏的软件开发领域&#xff0c;特别是在云原生领域&#xff0c;DevOps 和 SRE 团队日益成为应用程序稳定性和增长的重要合作伙伴。 DevOps 工程师不断优化软件交付&#xff0c;而 SRE 团队则充当应用程序可靠性、可扩展性和顶级性能的管理者。…

linux使用操作[2]

文章目录 版权声明网络传输ping命令wget命令curl命令端口linux端口端口命令和工具 进程管理查看进程关闭进程 主机状态top命令内容详解磁盘信息监控 版权声明 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明&#xff0c;所有版权属于黑马程序员或相…

视频转码软件 EditReady mac中文版介绍

EditReady for Mac是一款专业高效的视频转码器&#xff0c;拥有快速&#xff0c;强大的特点&#xff0c;可以调整视频大小&#xff0c;对视频进行旋转&#xff0c;重新定时等功能&#xff0c;对于需要的朋友&#xff0c;还可以查看和编辑元数据&#xff0c;EditReady Mac中文版…

Ubuntu 安装 Docker 的详细步骤

文章目录 简介1.更新2.安装必要的软件包2.1 基于阿里源 3.验证 Docker 安装是否成功4.安装后的一些常规设置及常用的命令4.1 启动 Docker4.2 Docker 在系统启动时自动运行4.3 运行一个 Hello World 镜像4.4 查看docker运行状态 欢迎来到这篇关于在 Ubuntu 上安装 Docker 的教程…