ThreeJS-3D教学四-光源
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() {// GUIconst 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: msdocument.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;
相关文章:

ThreeJS-3D教学四-光源
three模拟的真实3D环境,一个非常炫酷的功能便是对光源的操控,之前教学一中已经简单的描述了多种光源,这次咱们就详细的讲下一些最常见的光源: AmbientLight 该灯光在全局范围内平等地照亮场景中的所有对象。 该灯光不能用于投射阴…...

Linux 回收内存到底怎么计算anon/file回收比例,只是swappiness这么简单?
概述 Linux内核为了区分冷热内存,将page以链表的形式保存,主要分为5个链表,除去evictable,我们主要关注另外四个链表:active file/inactive file,active anon和inactive anon链表,可以看到这主要分为两类,file和anon page,内存紧张的时候,内核开始从inactive tail定…...

软件测试中的测试工具和自动化测试
1. 测试工具 测试工具也分为不同人员使用的 开发人员:测试框架,编写测试用例;各类线上dump分析工具如windgb;开发时的集成IDE工具如Visual Studio,idea等等 面向不同测试需求的测试工具 软件测试是软件开发生命周期…...

个人博客系统测试报告
个人博客系统测试报告 一.项目背景二.项目功能三.测试用例3.1 功能测试3.2 自动化测试(部分测试)3.2.1登陆页面3.2.2博客详情页3.2.3博客编辑页3.2.4个人列表页3.2.5测试结果 3.3 性能测试 一.项目背景 当学习完一项技能后,我们总会习惯通过博…...

高效搜索,提升编程效率
一、搜索效率 1.1魔法上网 网址: 一个很变态但可以让你快速学会计算机的方法…………_哔哩哔哩_bilibili 谷歌镜像: https://search.fuyeor.com/zh-cn/Google 谷歌学术: https://link.zhihu.com/?targethttps%3A//scholar.lanfanshu.cn/…...

Java编程技巧:文件上传、下载、预览
目录 1、上传文件1.1、代码1.2、postman测试截图 2、下载resources目录中的模板文件2.1、项目结构2.2、代码2.3、使用场景 3、预览文件3.1、项目结构3.2、代码3.3、使用场景 1、上传文件 1.1、代码 PostMapping("/uploadFile") public String uploadFile(Multipart…...

【蓝桥杯选拔赛真题63】Scratch云朵降雨 少儿编程scratch图形化编程 蓝桥杯选拔赛真题解析
目录 scratch云朵降雨 一、题目要求 编程实现 二、案例分析 1、角色分析...

【新版】系统架构设计师 - 软件架构的演化与维护
个人总结,仅供参考,欢迎加好友一起讨论 文章目录 架构 - 软件架构的演化与维护考点摘要软件架构演化和定义面向对象软件架构演化对象演化消息演化复合片段演化约束演化 软件架构演化方式静态演化动态演化 软件架构演化原则软件架构演化评估方法大型网站架…...
安卓循环遍历计时器
计时器循环遍历 计时器的使用 我习惯两种方式如下: 第一种使用 handler: 1,初始化 声明 public static final int REGULAR_TIME 1000; //1秒 时间间隔private Handler mUiHandler;private int index0;Runnable runnable new Runnable()…...

Docker-基本了解
Docker-基本了解 一、基本概念1、镜像2、容器 二、执行流程三、体系结构 一、基本概念 Docker是容器化平台,提供应用打包,部署与运行应用的容器化平台,应用程序通过docker engine(Docker 引擎获取可用资源)࿰…...

Leetcode383. 赎金信
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以,返回 true ;否则返回 false 。 magazine 中的每…...

overleaf杂谈-Springer文献格式问题
目录 overleaf写作问题记录1.Latex中的%问题(文本变成灰色)2.Springer文献格式问题2.1 新建reference.bib2.2 谷歌学术搜索文章并引用2.3 复制BibTex2.4 复制进reference.bib2.5 在sn-article.tex的\end{document}前添加语句2.6 引用文献2.7 Springer模板…...

No148.精选前端面试题,享受每天的挑战和学习
🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…...

BASH shell脚本篇4——函数
这篇文章介绍下BASH shell中的函数。之前有介绍过shell的其它命令,请参考: BASH shell脚本篇1——基本命令 BASH shell脚本篇2——条件命令 BASH shell脚本篇3——字符串处理 函数是代码重用的最重要方式。Bash函数可以定义为一组命令,在b…...
VisualStudio配置OpenCV环境
VS2022配置OpenCV环境 记录一下Windows上VS配置OpenCV环境的过程。(VS2022 OpenCV4.8) 一、下载OpenCV 从官网或者镜像网站下载Windows版OpenCV。4.8版本的文件为opencv-4.8.0-windows.exe 双击解压到自定义目录,我这边是:E:…...

C++手写NMS
文章目录 前言一、NMS是什么?二、代码展示三、代码实现思路总结 前言 目标检测模型推理后,一般都需要进行NMS操作进行多余框去重,板端部署一般不用opencv自带的NMS,所以记录下手写NMS的代码。 一、NMS是什么? 非极大…...
第9讲:VUE中监听器WATCH使用详解
目录 监听器介绍 监听普通属性 监听对象属性 监听路由属性监听器watch 监听器:它是侦听属性值或者计算属性的变化,一旦发生变化可以在函数中进行相应的操作,从而达到change事件监听的效果!监听器是一个对象,以 key-value 的形式表示。key 是需要监听的表达式,value 是对…...

微信小程序开发基础(一)认识小程序
微信小程序,小程序的一种,英文名Wechat Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。微信小程序是一种不用下载就能使用的应用,也是…...

LeetCode 1049. 最后一块石头的重量 II
1049. 最后一块石头的重量 II - 力扣(LeetCode) 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y&am…...
Golang中的类型转换介绍
Golang中存在4种类型转换,分别是:断言、显式、隐式、强制。下面我将一一介绍每种转换使用场景和方法 一、断言类型转换 主要是判断变量是否可以转换成某一类型。断言主要用于变量是interface{}类型(接口类型)的情况,…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...