使用Three.js爲QQ用戶生成3D頭像陣列

東西其實比較簡單,就是輸出某一範圍內QQ帳號的3D頭像css

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

然後經過騰訊的接口異步併發請求QQ用戶頭像,Canavs做圖生成Texture應用在球體上jquery

須要注意的是,必須修改Chrome啓動參數容許訪問跨域資源纔可正常打開此頁面git

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

先上效果圖web

 

 

而後是主要代碼bootstrap

 

<!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>
相關文章
相關標籤/搜索