工作闲暇之余,偶然翻到了Three.js的官网,立刻被它酷炫的案例给惊艳到了,当即下定决心要试验摸索一番,于是看demo,尝试,踩坑,解决问题,终于搞定了,一个模拟演唱会场景。

主角围绕一个钢管在舞动,两个聚光灯,来回摆动,后面屏幕上显示着照片,顶上一个立方体屏幕水平滚动,垂直滚动交替,后面荧光棒来回摆动,本来想搞一波荧光屏上图片定时切换的,摸索了一番效果不是很理想,就此作罢。先来看一下截图。

需要注意的是,代码需要运行在服务器端,切记。

下面上代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
</style>
<title>测试</title>
<script src="../js/three.js"></script>
<script src="../js/dat.gui.min.js"></script>
<script src="../js/OrbitControls.js"></script>
<script src="../js/util.js"></script>
</head>
<body>
<script>
var scene, camera, renderer, controls;
var target1, target2;
var angle = 0, flag = 1, lightFlag = 1;
var sticks; // 荧光棒
var roles; // 主角
var textPlane, textCube, cubeRotationRange = true;
var cubeArray = [
{ text: "图片1", src: "../images/1.jpg", position: 'top' },
{ text: "图片2", src: "../images/2.jpg", position: 'bottom' },
{ text: "图片3", src: "../images/3.jpg", position: 'left' },
{ text: "图片4", src: "../images/4.jpg", position: 'right' },
{ text: "图片5", src: "../images/5.jpg", position: 'far' },
{ text: "图片6", src: "../images/xusong.jpg", position: 'near' }
]; // textCube的六面
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 500);
camera.position.z = 200;
camera.lookAt(scene.position);
scene.add(camera);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 这块儿踩了一个大坑,如果不设置这个,平行光束为毛是垂直屏幕照射的
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
document.body.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.update();
window.addEventListener('resize', onWindowResize, false);
// var axesHelper = new THREE.AxesHelper(10);
// scene.add(axesHelper);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
}
function run() {
init();
roles = createBones(1, 5, 5, 8, true, false); //创建角色
bang = createBones(1, 0.5, 0.5, 8, false, false, new THREE.MeshStandardMaterial({
skinning: true,
color: 0xccffff,
// emissive: 0xccffff,
side: THREE.DoubleSide,
flatShading: THREE.FlatShading
})); // 创建钢管
bang[0].add(roles[0]); // 让主角绕着钢管绕圈
createAmbientLight(); // 绘制环境光
createPlane(); // 创建舞台平面
createDirectionalLight(); // 平行光束
createTargets(); // 创建点光源跟踪
sticks = createBones(30, 0.1, 0.2, 2, false, true, new THREE.MeshPhongMaterial({
skinning: true,
color: 0xffff66,
emissive: 0xa72534,
side: THREE.DoubleSide,
flatShading: THREE.FlatShading
})); // 创建荧光棒
createSpotlist(new THREE.Vector3(-50, 50, 0), target1);
createSpotlist(new THREE.Vector3(50, 50, 0), target2);
getTextCanvas(createTextPlane, [{ text: "许嵩", src: "../images/xusong.jpg", position: null }]);
getTextCanvas(createTextCube, cubeArray);
render();
}
function render() {
requestAnimationFrame(render);
controls.update();
angle += flag * 1;
angle = angle % 30;
if (angle >= 29 || angle <= -29) {
flag = -flag
}
roles[0].skeleton.bones[3].rotation.z = angle / 180 * Math.PI;
roles[0].skeleton.bones[1].rotation.z = -angle / 180 * Math.PI;
// 点光源跟随target移动
if (target1.position.x >= 0 || target1.position.x <= -40) {
lightFlag = -lightFlag
}
target1.position.x += lightFlag * 0.5;
target2.position.x += -lightFlag * 0.5;
//荧光棒在各自的幅度内移动
for (let i = 0; i < sticks.length; i++) {
let stick = sticks[i];
stick.skeleton.bones[0].rotation.z += 0.01 * stick.swingFlag;
stick.positionNow += 0.01 * stick.swingFlag;
if (Math.abs(stick.positionNow) > Math.abs(stick.swingRange)) {
stick.swingFlag = -stick.swingFlag
}
}
bang[0].rotation.y += 0.02;
if (textCube) {
if (cubeRotationRange) {
textCube.rotation.y += 0.02;
} else {
textCube.rotation.x -= 0.02;
}
if (textCube.rotation.y > Math.PI * 2 || textCube.rotation.x < -Math.PI * 2) {
textCube.rotation.x = 0;
textCube.rotation.y = 0;
cubeRotationRange = !cubeRotationRange;
}
}
renderer.render(scene, camera);
}
run();
function createBones(num, bigRadius, smallRadius, segmentHeight, isRole, isSticks, diyMaterial) {
var meshes = []
//计算参数,这些参数在多处用到
var segmentHeight = segmentHeight;
var segmentCount = 4;
var height = segmentHeight * segmentCount; // 32
var halfHeight = height * 0.5; // 16
var sizing = {
segmentHeight: segmentHeight,
segmentCount: segmentCount,
height: height,
halfHeight: halfHeight
};
for (let j = 0; j < num; j++) {
//创建骨架
var bones = [];
var prevBone = new THREE.Bone();
bones.push(prevBone);
prevBone.position.y = - sizing.halfHeight;
for (var i = 0; i < sizing.segmentCount; i++) {
var bone = new THREE.Bone();
bone.position.y = sizing.segmentHeight;
bones.push(bone);
prevBone.add(bone);
prevBone = bone;
}
var skeleton = new THREE.Skeleton(bones);
//创建形状
// var geometry = new THREE.CylinderBufferGeometry(
// bigRadius, // radiusTop
// smallRadius, // radiusBottom
// sizing.height, // height
// 8, // radiusSegments
// sizing.segmentCount * 3, // heightSegments
// false // openEnded
// );
var geometry = new THREE.CylinderGeometry(
bigRadius, // radiusTop
smallRadius, // radiusBottom
sizing.height, // height
8, // radiusSegments
sizing.segmentCount * 3, // heightSegments
false // openEnded
);
//将形状的每个点和骨骼建立关联,其中skinIndices指定该点由哪些骨骼控制(通过骨骼序号指定),skinWeights指定这些骨骼对该点的控制能力
for (var i = 0; i < geometry.vertices.length; i++) {
var vertex = geometry.vertices[i];
var y = (vertex.y + sizing.halfHeight);
var skinIndex = Math.floor(y / sizing.segmentHeight);
var skinWeight = (y % sizing.segmentHeight) / sizing.segmentHeight;
geometry.skinIndices.push(new THREE.Vector4(skinIndex, skinIndex + 1, 0, 0));
geometry.skinWeights.push(new THREE.Vector4(1 - skinWeight, skinWeight, 0, 0));
}
var bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry);
// 有自定义材料就用自定义材料,否则用默认的
var material = diyMaterial || new THREE.MeshPhongMaterial({
skinning: true,
color: 0x156289,
emissive: 0xa72534,
side: THREE.DoubleSide,
flatShading: THREE.FlatShading,
wireframe: true
});
var mesh = new THREE.SkinnedMesh(bufferGeometry, material);
//绑定骨架和网格,任务完成
mesh.add(bones[0]);
mesh.bind(skeleton);
mesh.scale.multiplyScalar(1);
mesh.castShadow = true;
meshes.push(mesh);
scene.add(mesh);
if (isRole) {
mesh.position.z = 10;
}
if (isSticks) {
let positionX = util.createRandomPos(-100, 100);
let positionY = util.createRandomPos(0, 50);
if (Math.abs(positionX) < 50) {
positionY += 50;
}
mesh.position.set(positionX, positionY, -50);
mesh.swingFlag = 1;
mesh.swingRange = util.createRandomPos(Math.PI / 6, Math.PI / 2);
mesh.positionNow = 0;
}
// //SkeletonHelper可以用线显示出骨架,帮助我们调试骨架,可有可无
// skeletonHelper = new THREE.SkeletonHelper(mesh);
// skeletonHelper.material.linewidth = 2;
// scene.add(skeletonHelper);
}
return meshes;
}
function createAmbientLight() {
var light = new THREE.AmbientLight(0x404040); // soft white light
scene.add(light);
}
function createPlane() {
//Create a plane that receives shadows (but does not cast them)
var planeGeometry = new THREE.PlaneBufferGeometry(100, 100, 32, 32);
var planeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2;
plane.position.y = -20;
plane.receiveShadow = true;
scene.add(plane);
}
function createDirectionalLight() {
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, 100, 0);           //default; directionalLight shining from top
directionalLight.castShadow = true; // default false
//Set up shadow properties for the directionalLight
directionalLight.shadow.mapSize.width = 512; // default
directionalLight.shadow.mapSize.height = 512; // default
directionalLight.shadow.camera.left = -1; // default
directionalLight.shadow.camera.right = 1; // default
directionalLight.shadow.camera.top = 1; // default
directionalLight.shadow.camera.bottom = -1; // default
directionalLight.shadow.camera.near = 0.5; // default
directionalLight.shadow.camera.far = 500; // default
scene.add(directionalLight);
// //Create a helper for the shadow camera (optional)
// var helper = new THREE.CameraHelper(directionalLight.shadow.camera);
// scene.add(helper);
}
function createSpotlist(Vector3, target) {
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(Vector3.x, Vector3.y, Vector3.z);
spotLight.castShadow = true;
spotLight.angle = Math.PI / 18;
spotLight.shadow.mapSize.width = 512;
spotLight.shadow.mapSize.height = 512;
spotLight.shadow.camera.near = 0.5;
spotLight.shadow.camera.far = 500;
spotLight.shadow.camera.fov = 30;
spotLight.target = target;
scene.add(spotLight);
// Create a helper for the spotlight
// var helper = new THREE.SpotLightHelper(spotLight);
// scene.add(helper);
// //Create a helper for the shadow camera (optional)
// var helper = new THREE.CameraHelper(spotLight.shadow.camera);
// scene.add(helper);
}
function createTargets() {
target1 = new THREE.Object3D();
target1.position.set(-20, 0, 0);
scene.add(target1);
target2 = new THREE.Object3D();
target2.position.set(20, 0, 0);
scene.add(target2);
}
function getTextCanvas(callback, srcList) {
var canvasList = [];
var imgList = [];
var totalCount = srcList.length, loadedCount = 0;
for (let i = 0; i < srcList.length; i++) {
let img = new Image();
img.src = srcList[i].src;
img.onload = function () {
loadedCount++;
imgList.push({ img: img, position: srcList[i].position, text: srcList[i].text });
}
}
// 开始处理回调函数
if (typeof callback == "function") {
// 这里的this实际上指的是this对象
function check() {
//
if (loadedCount >= totalCount) { // 如果加载完了
for (let i = 0; i < imgList.length; i++) {
var width = 512, height = 256;
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
// canvas.style.backgroundImage = "url('three/xusong.jpg')";
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#C3C3C3'; // 背景颜色
ctx.fillRect(0, 0, width, height);
ctx.drawImage(imgList[i].img, 0, 0, 512, 256);
// callback(canvas); // 利用该canvas构建要用的物体
ctx.font = 50 + 'px " bold';
ctx.fillStyle = '#2891FF'; // 文字颜色
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(imgList[i].text, width / 2, height / 2);
canvasList.push({ canvas: canvas, position: imgList[i].position });
}
// let createdObj = callback(canvasList);
if (callback.name == 'createTextCube') {
textCube = callback(canvasList);
// console.log(cubeText);
}
else if (callback.name == 'createTextPlane') {
textPlane = callback(canvasList);
}
} else {
// 没有加载完毕
setTimeout(check, 100);
}
}
// 开始反复检查图片有么有加载完毕
check();
}
}
function createTextPlane(canvasList) {
//Create a plane that receives shadows (but does not cast them)
var geometry = new THREE.PlaneGeometry(100, 50, 32);
var texture = new THREE.Texture(canvasList[0].canvas); // canvas做纹理
texture.needsUpdate = true;
var materials = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide }) // top
// var materials = new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(canvas), side: THREE.DoubleSide }) // top
var textPlane = new THREE.Mesh(geometry, materials);
textPlane.position.z = -50;
textPlane.receiveShadow = true;
scene.add(textPlane);
return textPlane;
}
function createTextCube(canvasList) {
//Create a plane that receives shadows (but does not cast them)
var geometry = new THREE.BoxGeometry(25, 25, 25);
var colorList = ['blue', 'yellow', 'green', 'red'];
var positionList = { 'right': 0, 'left': 1, 'top': 2, 'bottom': 3, 'near': 4, 'far': 5 };
var materials = [];
for (let i = 0; i < canvasList.length; i++) {
var texture = new THREE.Texture(canvasList[i].canvas);
texture.needsUpdate = true;
materials[positionList[canvasList[i].position]] = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide });
}
for (let j = 0; j < 6; j++) {
if (materials[j] && !materials[j].isMeshBasicMaterial) {
materials[j] = new THREE.MeshBasicMaterial({ color: colorList[Math.floor(Math.random() * 4)] });
}
}
var textCube = new THREE.Mesh(geometry, materials);
textCube.position.y = 50;
textCube.receiveShadow = true;
scene.add(textCube);
return textCube;
}
</script>
</body>
</html>

总结:本次小尝试学会了three.js的基本模型的画法、光源的用法、骨骼与模型的绑定、纹理等的基本用法。

不足之处:骨骼与模型各节点的绑定权重的计算关系不太理解。

附上github链接:https://github.com/liujiekun/threeJS记得在服务端启动,直接浏览器运行是看不到图片、纹理以及动画原型的。

three.js尝试(一)模拟演唱会效果的更多相关文章

  1. three.js尝试(二)模拟游戏开发:3D人物在地图上行走

    本次尝试,模拟了一个小人物在场景中行走,使用简单模型建立了森林,图片纹理模拟草地,加载3D模型呈现人物,使用按键asdw模拟人物的行走,行走和站立时人物的切换等. 主要用到点:3D模型的加载,模型的动 ...

  2. 利用jquery模拟select效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. [浅学] 1、Node.js尝试_安装&运行第一个helloworld

    官网:https://nodejs.org/ 介绍:Node.js® is a platform built on Chrome's JavaScript runtime for easily bui ...

  4. Rainyday.js – 使用 JavaScript 实现雨滴效果

    Rainyday.js 背后的想法是创建一个 JavaScript 库,利用 HTML5 Canvas 渲染一个雨滴落在玻璃表面的动画.Rainyday.js 有功能可扩展的 API,例如碰撞检测和易 ...

  5. 用js枚举实现简易菜单效果

    用js枚举实现简易菜单效果,左侧显示菜单,右侧显示用户选择的菜单,一图胜千言,还是直接来张效果图吧: 以下是代码: <DOCTYPE html> <html> <head ...

  6. 实用js+css多级树形展开效果导航菜单

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. js+css实现带缓冲效果右键弹出菜单

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. JS实现图片翻书效果示例代码

    js 图片翻书效果.  picture.html  <html xmlns="http://www.w3.org/1999/xhtml">  <head>  ...

  9. js实现图片自动切换效果。

    js实现图片自动切换效果,简单实用,原谅我只是一只小菜鸟还在学大神天天写博文装逼. <script language="javascript"> setInterval ...

随机推荐

  1. 有用的20个Python代码段

    Python是一种非BS编程语言.设计简单和易读性是它广受欢迎的两大原因.正如Python的宗旨:美丽胜于丑陋,显式胜于隐式. 记住一些帮助提高编码设计的常用小诀窍是有用的.在必要时刻,这些小诀窍能够 ...

  2. Java日志框架(一)

    在项目开发过程中,我们可以通过 debug 查找问题.而在线上环境我们查找问题只能通过打印日志的方式查找问题.因此对于一个项目而言,日志记录是一个非常重要的问题.因此,如何选择一个合适的日志记录框架也 ...

  3. SEO(Search Engine Optimization)优化

    SEO(Search Engine Optimization)汉议为搜索引擎优化,是一种利用搜索引擎的规则提高网站在有关搜索引擎内自然排名的方式. SEO的目的是对网站进行深度的优化,从而帮助网站获取 ...

  4. CSS变化、过渡与动画

    CSS变换用于在空间中移动物体,而CSS过渡和CSS关键帧动画用于控制元素随时间推移的变化. 变换.过渡和关键帧动画的规范仍然在制定中.尽管如此,其中大多数特性已经在常用浏览器中实现了. 1.二维变换 ...

  5. GCN 入门

    参考链接: https://www.zhihu.com/question/54504471/answer/611222866 1 拉普拉斯矩阵 参考链接: http://bbs.cvmart.net/ ...

  6. java方法与方法的重载

    一 方法 1.方法的概述 在java中,方法就是用来完成解决某件事情或实现某个功能的办法. 方法实现的过程中,会包含很多条语句用于完成某些有意义的功能——通常是处理文本, 控制输入或计算数值.我们可以 ...

  7. 深度强化学习:Policy-Based methods、Actor-Critic以及DDPG

    Policy-Based methods 在上篇文章中介绍的Deep Q-Learning算法属于基于价值(Value-Based)的方法,即估计最优的action-value function $q ...

  8. JavaScript正则表达式的模式匹配教程,并且附带充足的实战代码

    JavaScript正则表达式的模式匹配 引言 正文 一.正则表达式定义 二.正则表达式的使用 三.RegExp直接量 (1)正则表达式初体验 (2)深入了解正则 字符类 重复 选择 分组与引用 指定 ...

  9. Bootstrap4-思维导图-知识点总结

    Bootstrap4-思维导图-知识点总结 By:Mirror王宇阳 time:2020/4/30 有错误之处 烦请见谅!

  10. QT+VS环境配置中遇到的问题

    大体流程参考的别人的博客流程如下: QT安装: https://blog.csdn.net/qq_42907800/article/details/107370967?> QT+VS环境配置 h ...