zoukankan      html  css  js  c++  java
  • three.js尝试(一)模拟演唱会效果

    工作闲暇之余,偶然翻到了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记得在服务端启动,直接浏览器运行是看不到图片、纹理以及动画原型的。

  • 相关阅读:
    类的再理解
    关于网络配置和zmp以及json
    PCL 库安装
    Ros学习注意点
    Ros集成开发环境配置
    《TCP/IP详解卷1:协议》第3章 IP:网际协议(1)-读书笔记
    《Effective C++》第1章 让自己习惯C++-读书笔记
    《TCP/IP详解卷1:协议》第2章 链路层-读书笔记
    《TCP/IP详解卷1:协议》第1章 概述-读书笔记
    C++内存分配与对象构造的分离
  • 原文地址:https://www.cnblogs.com/liujiekun/p/10736540.html
Copyright © 2011-2022 走看看