• 使用Three.js为QQ用户生成3D头像阵列


    东西其实比较简单,就是输出某一范围内QQ账号的3D头像

    涉及的技术主要是Three.js的基本使用

    而后通过腾讯的接口异步并发请求QQ用户头像,Canavs作图生成Texture应用在球体上

    需要注意的是,必须修改Chrome启动参数允许访问跨域资源才可正常打开此页面

    GitHub地址:https://github.com/gstoken/qq-cube

    先上效果图

    然后是主要代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <link rel="stylesheet" href="./css/bootstrap.css" />
        <style>
            html, body {
                margin: 0px;
                padding: 0px;
                width: 100%;
                height: 100%;
            }
            .page {
                position: fixed;
                width: 100%;
                height: 100%;
                background-color: white;
            }
            .top {
                padding: 10px;
                width: 100%;
                background-color: #222;
                color: white;
            }
            .content {
                width: 100%;
                height: 100%;
            }
        </style>
        <title>QQ号顺序阵列查询</title>
    </head>
    <body>
        <div class="page">
            <div class="top">
                <form class="form-inline">
                    <div class="form-group">
                        <label for="dstQQNum">QQ号:</label>
                        <input type="number" class="form-control" id="dstQQNum" value="1982775886" placeholder="目标QQ号" />
                    </div>
                    <div class="form-group">
                        <label for="querySize">查询数量:</label>
                        <input type="number" class="form-control" id="querySize" value="64" placeholder="查询个数" />
                    </div>
                    <div class="form-group">
                        <label for="taskNum">并发数量:</label>
                        <input type="number" class="form-control" id="taskNum" value="50" placeholder="并发数量" />
                    </div>
                    <div class="form-group">
                        <input id="standardQuery" type="button" class="btn btn-primary" value="查询" />
                        <input id="randomQuery" type="button" class="btn btn-primary" value="随机查查" />
                        <input id="clearQQ" type="button" class="btn btn-primary" value="清空QQ" />
                    </div>
                </form>
            </div>
            <div class="content">
            </div>
        </div>
        <script src="./js/jquery-3.3.1.js"></script>
        <script src="./js/three.js"></script>
        <script src="./js/OrbitControls.js"></script>
        <script src="./js/Detector.js"></script>
        <script src="./js/stats.min.js"></script>
        <script type="x-shader/x-vertex" id="vertexShader">
            varying vec3 vWorldPosition;
            void main() {
                vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
                vWorldPosition = worldPosition.xyz;
                gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
            }
        </script>
        <script type="x-shader/x-fragment" id="fragmentShader">
            uniform vec3 topColor;
            uniform vec3 bottomColor;
            uniform float offset;
            uniform float exponent;
            varying vec3 vWorldPosition;
            void main() {
                float h = normalize( vWorldPosition + offset ).y;
                gl_FragColor = vec4( mix( bottomColor, topColor, max( pow( max( h , 0.0), exponent ), 0.0 ) ), 1.0 );
            }
        </script>
    
        <script>
            if (!Detector.webgl) Detector.addGetWebGLMessage();
    
    
            //球体贴图宽
            const ballTextureWidth = 256;
            //球体贴图高
            const ballTextureHeight = 128;
            //头像尺寸
            const headSize = 128;
            //摄像机,场景,渲染器
            let camera, scene, renderer;
            let stats;
    
            let $area = $(".content");
    
            let meshList = [];
    
            //绘制图像到canvas
            function drawToCanvas (img, canvas) {
                let ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, (ballTextureHeight - headSize) / 2, headSize, headSize);
                ctx.drawImage(img, ballTextureWidth / 2, (ballTextureHeight - headSize) / 2, headSize, headSize);          
            }
    
            //根据QQ以及坐标位置信息生成球体对象
            function createBallMesh (img) {
                let canvas = document.createElement("canvas");
                canvas.width = ballTextureWidth;
                canvas.height = ballTextureHeight;
    
                let ctx = canvas.getContext("2d");
                ctx.fillStyle = "#ffffff";
                ctx.fillRect(0, 0, ballTextureWidth, ballTextureHeight);
                drawToCanvas(img, canvas);
    
                let texture = new THREE.Texture(canvas);
                //新建标准网孔材质
                let ballMat = new THREE.MeshStandardMaterial( {
                    color: "white",
                    roughness: 0.4,
                    metalness: 0.4,
                    map: texture
                });
                texture.needsUpdate = true;
    
                let ballGeometry = new THREE.SphereGeometry(0.5, 32, 32);
                let ballMesh = new THREE.Mesh(ballGeometry, ballMat);
                ballMesh.rotation.y = Math.PI;
    
                texture = undefined;
                canvas = undefined;
    
                return ballMesh;
            }
    
            async function buildQQMeshList (qqNumList, concurrencyNum) {
                let rtvList = [];
                let getList = await getAllQQHead(qqNumList, concurrencyNum);
                for (let i = 0; i < getList.length; ++i) {
                    let curQQInfo = getList[i];
                    let curQQNum = curQQInfo.qqNum;
                    let curMesh = createBallMesh(curQQInfo.headImg);
                    rtvList[i] = {
                        qqNum: curQQNum,
                        qqMesh: curMesh
                    };
                }
                return rtvList;
            }
            
            async function draw (scene, list, concurrencyNum) {
                clear();
                meshList = await buildQQMeshList(list, concurrencyNum);
                meshList.sort((a, b) => {
                    return a.qqNum - b.qqNum;
                });
                len = meshList.length;
                let size = Math.ceil(Math.cbrt(len));
                let i = 0;
                for (let z = 0; z > -size; --z) {
                    for (let y = size - 1; y >= 0; --y) {
                        for (let x = 0; x < size; ++x) {
                            if (i < len) {
                                let ballMesh = meshList[i++].qqMesh;
                                ballMesh.position.set(x * 2, y * 2, z * 2);
                                scene.add(ballMesh);
                            }
                            else {
                                return;
                            }
                        }
                    }
                }
            }
    
            function clear () {
                for (let i = 0; i < meshList.length; ++i) {
                    scene.remove(meshList[i].qqMesh);
                }
            }
    
            async function init(element) {
                let areaWidth = $area.width();
                let areaHeight = $area.height();
    
                //初始化相机
                camera = new THREE.PerspectiveCamera(30, areaWidth / areaHeight, 1, 5000);
                camera.position.set(0, 0, 25);
    
                //初始化场景
                scene = new THREE.Scene();
                scene.background = new THREE.Color().setHSL(0.6, 0, 1);
                scene.fog = new THREE.Fog(scene.background, 1, 5000);
                
                //添加半球光源
                let = hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
                hemiLight.color.setHSL(0.6, 1, 0.6);
                hemiLight.groundColor.setHSL(0.095, 1, 0.75);
                hemiLight.position.set(0, 50, 0);
                scene.add(hemiLight);
                //添加直接光源
                let dirLight = new THREE.DirectionalLight(0xffffff, 1);
                dirLight.color.setHSL(0.1, 1, 0.95);
                dirLight.position.set(-1, 1.75, 1);
                dirLight.position.multiplyScalar(30);
                scene.add(dirLight);
    
                //环境
                let groundGeo = new THREE.PlaneBufferGeometry(10000, 10000);
                let groundMat = new THREE.MeshPhongMaterial({color: 0xffffff, specular: 0x050505});
                groundMat.color.setHSL(0.095, 1, 0.75);
                let ground = new THREE.Mesh(groundGeo, groundMat);
                ground.rotation.x = -Math.PI / 2;
                ground.position.y = -33;
                scene.add(ground);
                ground.receiveShadow = true;
                
                //天幕
                var vertexShader = document.getElementById('vertexShader').textContent;
                var fragmentShader = document.getElementById('fragmentShader').textContent;
                var uniforms = {
                    topColor: {value: new THREE.Color(0x0077ff)},
                    bottomColor: {value: new THREE.Color(0xffffff)},
                    offset: {value: 33},
                    exponent: {value: 0.6}
                };
                uniforms.topColor.value.copy(hemiLight.color);
                scene.fog.color.copy(uniforms.bottomColor.value);
                var skyGeo = new THREE.SphereGeometry(4000, 32, 15);
                var skyMat = new THREE.ShaderMaterial( { vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms: uniforms, side: THREE.BackSide } );
                var sky = new THREE.Mesh( skyGeo, skyMat );
                scene.add(sky);
                
                //渲染器
                renderer = new THREE.WebGLRenderer({antialias: true});
                renderer.setPixelRatio(window.devicePixelRatio);
                renderer.setSize(areaWidth, areaHeight);
                element.appendChild(renderer.domElement);
                renderer.gammaInput = true;
                renderer.gammaOutput = true;
                renderer.shadowMap.enabled = true;
    
                //初始化轨道控制器
                controls = new THREE.OrbitControls(camera, renderer.domElement);
    
                // stats = new Stats();
                // element.appendChild(stats.dom);
    
                window.addEventListener('resize', onWindowResize, false);
            }
    
            function onWindowResize() {
                let width = $area.width();
                let height = $area.height();
                camera.aspect = width / height;
                camera.updateProjectionMatrix();
                renderer.setSize(width, height);
            }
    
            function animate() {
                requestAnimationFrame(animate);
                renderer.render(scene, camera);
                //stats.update();
            }
    
            (async () => {
                let element = $area[0];
                await init(element);
                animate();
            })();
        </script>
    
        <script>
            //根据起始QQ,结束QQ生成QQ列表
            function buildInputQQNumList (opQQNum, edQQNum) {
                let len = edQQNum - opQQNum + 1;
                let list = Array(len).fill(0).map((item, index) => {
                    return opQQNum + index;
                });
                return list;
            }
            //闭区间随机选数
            function randomRange (op, ed) {
                let len = ed - op + 1;
                let offset = Math.floor(Math.random() * len);
                return op + offset;
            }
            //随机生成模拟数据
            function buildTestList (len) {
                let randOpQQNum = randomRange(10000, 100000000);
                let edQQNum = randOpQQNum + len - 1;
                return buildInputQQNumList(randOpQQNum, edQQNum);
            }
            //对列表按索引分组,传入列表和分组个数
            function batch (list, num) {
                let batchList = [];
                for (let i = 0; i < num; ++i) {
                    batchList[i] = list.filter((item, index) => {
                        if (index % num == i) {
                            return true;
                        }
                        else {
                            return false;
                        }
                    });
                }
                return batchList;
            } 
        </script>
    
        <script>
            //根据url获取Image对象,返回一个Promise
            function getImage (url, timeout) {
                timeout = timeout || 60000;
                return new Promise((resolve, reject) => {
                    try {
                        let imgObj = new Image();
                        imgObj.src = url;
                        imgObj.crossOrigin = "";
    
                        let id = setTimeout(() => {
                            reject();
                        }, timeout);
                        
                        imgObj.onload = () => {
                            clearTimeout(id);
                            resolve(imgObj);
                        };
                        
                        imgObj.onerror = () => {
                            reject();
                        };
                    }
                    catch (e) {
                        reject();
                    }
                });   
            }
            //获取访问QQ头像的接口url
            function getQQHeadUrl (qqNum) {
                //return "https://qlogo2.store.qq.com/qzone/" + qqNum + "/" + qqNum + "/100";
                return "http://q2.qlogo.cn/headimg_dl?dst_uin=" + qqNum + "&spec=100";
            }
            //获取QQ头像
            function getQQHead (qqNum) {
                return getImage(getQQHeadUrl(qqNum));
            }
            //根据传入的QQ账号列表生成一个Promise异步任务获取对应QQ头像
            function startGetQQHeadTask (qqNumList) {
                return new Promise(async (resolve, reject) => {
                    let taskArray = [];
                    for (let i = 0; i < qqNumList.length; ++i) {
                        let curQQNum = qqNumList[i];
                        let curQQHeadImg;
                        try {
                            curQQHeadImg = await getQQHead(curQQNum);
                        }
                        catch (e) {
                            curQQHeadImg = null;
                        }
                        taskArray[i] = {
                            qqNum: curQQNum,
                            headImg: curQQHeadImg
                        };
                    }
                    resolve(taskArray);
                });
            }
            //根据QQ账号列表获取所有头像
            function getAllQQHead (qqNumList, concurrencyNum) {
                return new Promise((resolve, reject) => {
                    let rtvList = [];
                    let batchList = batch(qqNumList, concurrencyNum);
                    let count = 0;
                    for (let i = 0; i < batchList.length; ++i) {
                        let dstTaskList = batchList[i];
                        startGetQQHeadTask(dstTaskList).then((list) => {
                            rtvList = rtvList.concat(list);
                            count++;
                            if (count >= concurrencyNum) {
                                resolve(rtvList);
                            }
                        });
                    }   
                });  
            }
        </script>
    
        <script>
            $(function () {
                $("#standardQuery").click(async () => {
                    let dstQQNum = Number(document.getElementById("dstQQNum").value);
                    let querySize = Number(document.getElementById("querySize").value);
                    if (querySize < 1) {
                        alert("查询个数输入错误");
                        return;
                    }
                    let taskNum = Number(document.getElementById("taskNum").value);
                    if (taskNum < 1) {
                        alert("并发数量输入错误");
                        return;
                    }
                    let list = buildInputQQNumList(dstQQNum, dstQQNum + querySize - 1);
                    await draw(scene, list, taskNum);
                });
                $("#randomQuery").click(async () => {
                    let taskNum = Number(document.getElementById("taskNum").value);
                    let list = buildTestList(4 * 4 * 4);
                    await draw(scene, list, taskNum);   
                });
                $("#clearQQ").click(() => {
                    clear();
                });
            });
        </script>
    </body>
    </html>
  • 相关阅读:
    前端-微信公众号开发(jssdk)
    wampserver 配置本地环境局域网内pc移动访问
    客户端缓存之localStorage and sessionStorage
    webpack+vue搭建基础
    高德地图javascriptAPI基本使用心得(下)
    高德地图javascriptAPI基本使用心得(上)
    表单单选多选项清除默认样式小技巧
    (原创)C#零基础学习笔记003-流程控制语句
    (原创)C#零基础学习笔记002-表达式与运算符
    (原创)C#零基础学习笔记000-学习结构
  • 原文地址:https://www.cnblogs.com/jimaojin/p/8533599.html
Copyright © 2020-2023  润新知