工作闲暇之余,偶然翻到了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. win7(64位)使用DEBUG

    win7 64位好像是不能直接打开DOS进行DEUBG的,于是查找相应解决方案 开始看其他人的帖子,写得语焉不详,后来一查,居然是抄百度的.....自己不觉得low吗... 参考百度经验的回答http ...

  2. kubernetes ingress 重定向地址错误

    环境: 有两个 kubernetes 集群: 一个版本为1.11,后面使用A集群代替,ingress 镜像版本为 0.19(quay.io/kubernetes-ingress-controller/ ...

  3. 标星7000+,这个 Python 艺术二维码生成器厉害了!

    微信二维码,相信大家也并不陌生,为了生成美观的二维码,许多用户都会利用一些二维码生成工具. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手 ...

  4. Webpack 定义process.env的时机

    定义 process.env的时机 如果已经提取了公共配置文件 webpack.common.js 分别定义了开发配置webpack.dev.js和生产配置webpack.prod.js 在webpa ...

  5. 【SCOI2013】摩托车交易 - 最大生成树+树链剖分

    题目描述 mzry1992 在打完吊针出院之后,买了辆新摩托车,开始了在周边城市的黄金运送生意.在mzry1992 生活的地方,城市之间是用双向高速公路连接的.另外,每条高速公路有一个载重上限,即在不 ...

  6. Excel清除隐藏的引号或空格

    问题场景 导出到Excel的数据内容有时候被"暗中"添加了[引号]或[空格]等字符. 尤其还"隐藏"了,以至于相同的内容,数据格式都没有问题,不能进行函数操作, ...

  7. SQL获取多个字段中最大小值

    1.语法最大值: GREATEST(expr_1, expr_2, ...expr_n)最小值: LEAST(expr_1, expr_2, ...expr_n) 2.说明GREATEST(expr_ ...

  8. day35:线程队列&进程池和线程池&回调函数&协程

    目录 1.线程队列 2.进程池和线程池 3.回调函数 4.协程:线程的具体实现 5.利用协程爬取数据 线程队列 1.线程队列的基本方法 put 存 get 取 put_nowait 存,超出了队列长度 ...

  9. JavaScript学习系列博客_5_JavaScript中的强制类型转换

    -强制类型转换为String 1.方式1 调用被转换数据的toString()方法 number类型值.布尔类型值.都可以调用toString()方法强制转换.但是null值和undefined值不行 ...

  10. TCL(事务控制语言)

    #TCL/*Transaction Control Language 事务控制语言 事务:一个或一组sql语句组成一个执行单元,这个执行单元要么全部执行,要么全部不执行. 案例:转账 张三丰 1000 ...