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

  • 相关阅读:
    leetcode 350. Intersection of Two Arrays II
    leetcode 278. First Bad Version
    leetcode 34. Find First and Last Position of Element in Sorted Array
    leetcode 54. Spiral Matrix
    leetcode 59. Spiral Matrix II
    leetcode 44. Wildcard Matching
    leetcode 10. Regular Expression Matching(正则表达式匹配)
    leetcode 174. Dungeon Game (地下城游戏)
    leetcode 36. Valid Sudoku
    Angular Elements
  • 原文地址:https://www.cnblogs.com/liujiekun/p/10736540.html
Copyright © 2011-2022 走看看