threejs精灵和粒子系统

news2024/9/20 18:44:52

个人博客地址: https://cxx001.gitee.io

前面我们了解到了场景中的网格对象由几何体和材质组成,并且分别系统学习了它们。这节我们将学习一个特殊的网格对象-----粒子(精灵)。

了解粒子

一个粒子(新版叫精灵)是 一个二维平面(小方块) ,它总是面向摄像机。

<!-- chapter-07-01.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Particles</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000000;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        //var canvasRenderer = new THREE.CanvasRenderer();
        var canvasRenderer = new THREE.WebGLRenderer();
        canvasRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
        canvasRenderer.setSize(window.innerWidth, window.innerHeight);

        camera.position.x = 0;
        camera.position.y = 0;
        camera.position.z = 150;
        document.getElementById("WebGL-output").appendChild(canvasRenderer.domElement);

        createSprites();
        render();
		
        function createSprites() {
            // 创建精灵材质
            var material = new THREE.SpriteMaterial();
            for (var x = -5; x < 5; x++) {
                for (var y = -5; y < 5; y++) {
                    // 创建精灵,只需要指定材质
                    var sprite = new THREE.Sprite(material);
                    sprite.position.set(x * 10, y * 10, 0);
                    scene.add(sprite);
                }
            }
        }

        function render() {
            stats.update();
            requestAnimationFrame(render);
            canvasRenderer.render(scene, camera);
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>


大量粒子

如果要创建大量粒子,使用上面THREE.Sprite一个个创建那就太耗性能了。对于这种场景,我们要使用THREE.PointCloudMaterialTHREE.PointCloud来管理。

THREE.PointCloud构造函数接收两个属性:几何体和材质,和创建网格对象类似。材质用来给粒子着色或添加纹理,而几何体则用来指定粒子的位置,几何体每个顶点将会以粒子的形态展示出来。

THREE.PointCloudMaterial材质的属性:

<!-- chapter-07-02.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Particle Basic Material</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000000;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);

        camera.position.x = 20;
        camera.position.y = 0;
        camera.position.z = 150;
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var cloud;
        var controls = new function () {
            this.size = 4;
            this.transparent = true;
            this.opacity = 0.6;
            this.vertexColors = true;
            this.color = 0xffffff;
            this.sizeAttenuation = true;
            this.rotateSystem = true;

            this.redraw = function () {
                if (scene.getObjectByName("particles")) {
                    scene.remove(scene.getObjectByName("particles"));
                }
                createParticles(controls.size, controls.transparent, controls.opacity, controls.vertexColors, controls.sizeAttenuation, controls.color);
            };
        };

        var gui = new dat.GUI();
        gui.add(controls, 'size', 0, 10).onChange(controls.redraw);
        gui.add(controls, 'transparent').onChange(controls.redraw);
        gui.add(controls, 'opacity', 0, 1).onChange(controls.redraw);
        gui.add(controls, 'vertexColors').onChange(controls.redraw);
        gui.addColor(controls, 'color').onChange(controls.redraw);
        gui.add(controls, 'sizeAttenuation').onChange(controls.redraw);
        gui.add(controls, 'rotateSystem');

        controls.redraw();
        render();
		
    	// 使用THREE.PointCloud创建大量粒子
        function createParticles(size, transparent, opacity, vertexColors, sizeAttenuation, color) {
            // 创建点云材质
            var material = new THREE.PointCloudMaterial({
                size: size,
                transparent: transparent,
                opacity: opacity,
                vertexColors: vertexColors,
                sizeAttenuation: sizeAttenuation,
                color: color
            });
			
            // 创建自定义几何体
            var geom = new THREE.Geometry();
            var range = 500;
            for (var i = 0; i < 15000; i++) {
                var particle = new THREE.Vector3(Math.random() * range - range / 2, Math.random() * range - range / 2, Math.random() * range - range / 2);
                geom.vertices.push(particle);
                var color = new THREE.Color(0x00ff00);
                color.setHSL(color.getHSL().h, color.getHSL().s, Math.random() * color.getHSL().l);
                geom.colors.push(color);
            }
			
            // 创建点云对象
            cloud = new THREE.PointCloud(geom, material);
            cloud.name = "particles";
            scene.add(cloud);
        }

        var step = 0;
        function render() {
            stats.update();
            if (controls.rotateSystem) {
                step += 0.01;
                cloud.rotation.x = step;
                cloud.rotation.z = step;
            }
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

在这里插入图片描述


样式化粒子

默认粒子会渲染成小方块,下面来看看怎么改变粒子样式。

1. 使用HTML5画布样式化粒子

前面提到过,THREE.PointCloudMaterial的map属性可以为粒子加载纹理,以此来改变粒子样式(关于纹理后续会详细介绍,这里只需关注使用)。首先我们看看把HTML5的画布转换为纹理来作为粒子的纹理运用。

<!-- chapter-07-03.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Particles - Canvas based texture - WebGL</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000000;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);

        camera.position.x = 20;
        camera.position.y = 0;
        camera.position.z = 150;
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
		
    	// 获取画布的纹理
        var getTexture = function () {
            var canvas = document.createElement('canvas');
            canvas.width = 32;
            canvas.height = 32;

            var ctx = canvas.getContext('2d');
            // the body
            ctx.translate(-81, -84);

            ctx.fillStyle = "orange";
            ctx.beginPath();
            ctx.moveTo(83, 116);
            ctx.lineTo(83, 102);
            ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
            ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
            ctx.lineTo(111, 116);
            ctx.lineTo(106.333, 111.333);
            ctx.lineTo(101.666, 116);
            ctx.lineTo(97, 111.333);
            ctx.lineTo(92.333, 116);
            ctx.lineTo(87.666, 111.333);
            ctx.lineTo(83, 116);
            ctx.fill();

            // the eyes
            ctx.fillStyle = "white";
            ctx.beginPath();
            ctx.moveTo(91, 96);
            ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
            ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
            ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
            ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
            ctx.moveTo(103, 96);
            ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
            ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
            ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
            ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
            ctx.fill();

            // the pupils
            ctx.fillStyle = "blue";
            ctx.beginPath();
            ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
            ctx.fill();
            ctx.beginPath();
            ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
            ctx.fill();

            var texture = new THREE.Texture(canvas);
            texture.needsUpdate = true;
            return texture;
        };

        var cloud;
        var controls = new function () {
            this.size = 15;
            this.transparent = true;
            this.opacity = 0.6;
            this.color = 0xffffff;
            this.rotateSystem = true;
            this.sizeAttenuation = true;

            this.redraw = function () {
                if (scene.getObjectByName("pointcloud")) {
                    scene.remove(scene.getObjectByName("pointcloud"));
                }
                createPointCloud(controls.size, controls.transparent, controls.opacity, controls.sizeAttenuation, controls.color);
            };
        };

        var gui = new dat.GUI();
        gui.add(controls, 'size', 0, 20).onChange(controls.redraw);
        gui.add(controls, 'transparent').onChange(controls.redraw);
        gui.add(controls, 'opacity', 0, 1).onChange(controls.redraw);
        gui.addColor(controls, 'color').onChange(controls.redraw);
        gui.add(controls, 'sizeAttenuation').onChange(controls.redraw);
        gui.add(controls, 'rotateSystem');
        controls.redraw();
        render();

        function createPointCloud(size, transparent, opacity, sizeAttenuation, color) {
            var geom = new THREE.Geometry();
            var material = new THREE.PointCloudMaterial({
                size: size,
                transparent: transparent,
                opacity: opacity,
                map: getTexture(),  // 使用外部纹理
                sizeAttenuation: sizeAttenuation,
                color: color
            });

            var range = 500;
            for (var i = 0; i < 5000; i++) {
                var particle = new THREE.Vector3(Math.random() * range - range / 2, Math.random() * range - range / 2, Math.random() * range - range / 2);
                geom.vertices.push(particle);
            }

            cloud = new THREE.PointCloud(geom, material);
            cloud.name = 'pointcloud';
            // 渲染之前所以粒子按z轴排序,解决部分粒子交叠问题,但是注意会影响性能。
            cloud.sortParticles = true;
            // 开启后摄像机可见范围外的粒子不会渲染,必要时,使用可以提高性能和帧率。
            cloud.FrustumCulled = true;
            scene.add(cloud);
        }

        var step = 0;
        function render() {
            stats.update();
            if (controls.rotateSystem) {
                step += 0.01;
                cloud.rotation.x = step;
                cloud.rotation.z = step;
            }
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

2. 使用纹理样式化粒子

前面使用HTML5画布输出纹理还是显得麻烦,我们还有一种更直接的方式,你可以使用THREE.ImageUtils.loadTexture()函数将图像加载为THREE.Texture,然后赋值给材质的map属性。

注意图片应该是正方形的,并且尺寸最好是2的幂,背景选择黑色(能够更正确地融合)。

<!-- chapter-07-04.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Particles - Rainy scene</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000000;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);

        camera.position.x = 20;
        camera.position.y = 40;
        camera.position.z = 110;
        camera.lookAt(new THREE.Vector3(20, 30, 0));
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var cloud;
        var controls = new function () {
            this.size = 3;
            this.transparent = true;
            this.opacity = 0.6;
            this.color = 0xffffff;
            this.sizeAttenuation = true;
            this.redraw = function () {
                scene.remove(scene.getObjectByName("particles1"));
                scene.remove(scene.getObjectByName("particles2"));
                createPointCloud(controls.size, controls.transparent, controls.opacity, controls.sizeAttenuation, controls.color);
            };
        };

        var gui = new dat.GUI();
        gui.add(controls, 'size', 0, 20).onChange(controls.redraw);
        gui.add(controls, 'transparent').onChange(controls.redraw);
        gui.add(controls, 'opacity', 0, 1).onChange(controls.redraw);
        gui.addColor(controls, 'color').onChange(controls.redraw);
        gui.add(controls, 'sizeAttenuation').onChange(controls.redraw);
        controls.redraw();
        render();
		
        function createPointCloud(size, transparent, opacity, sizeAttenuation, color) {
			// 加载本地图片作为粒子纹理
            var texture = THREE.ImageUtils.loadTexture("../assets/textures/particles/raindrop-3.png");
            
            var geom = new THREE.Geometry();
            var material = new THREE.ParticleBasicMaterial({
                size: size,
                transparent: transparent,
                opacity: opacity,
                map: texture,
                blending: THREE.AdditiveBlending,
                sizeAttenuation: sizeAttenuation,
                color: color
            });

            var range = 40;
            for (var i = 0; i < 1500; i++) {
                var particle = new THREE.Vector3(
                        Math.random() * range - range / 2,
                        Math.random() * range * 1.5,
                        Math.random() * range - range / 2);
                particle.velocityY = 0.1 + Math.random() / 5;
                particle.velocityX = (Math.random() - 0.5) / 3;
                geom.vertices.push(particle);
            }

            cloud = new THREE.ParticleSystem(geom, material);
            cloud.sortParticles = true;
            scene.add(cloud);
        }

        function render() {
            stats.update();
            
            // 模拟下雨效果
            var vertices = cloud.geometry.vertices;
            vertices.forEach(function (v) {
                v.y = v.y - (v.velocityY);
                v.x = v.x - (v.velocityX);
                if (v.y <= 0) v.y = 60;
                if (v.x <= -20 || v.x >= 20) v.velocityX = v.velocityX * -1;
            });

            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

在这里插入图片描述


使用精灵贴图

精灵贴图是很多小精灵图片打包在一张图片上,一次加载这张大图后,使用时通过纹理的偏移来选取需要的小图片。

这个示例展示了创建两个场景使用不同摄像机(游戏里场景和UI界面就可以这样使用)

<!-- chapter-07-05.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Particles - Sprites</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #000000;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
    
    	// 透视相机场景和正交相机场景
        var scene = new THREE.Scene();
        var sceneOrtho = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 250);
        var cameraOrtho = new THREE.OrthographicCamera(0, window.innerWidth, window.innerHeight, 0, -10, 10);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);

        camera.position.x = 0;
        camera.position.y = 0;
        camera.position.z = 50;
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
		
    	// 球体
        var material = new THREE.MeshNormalMaterial();
        var geom = new THREE.SphereGeometry(15, 20, 20);
        var mesh = new THREE.Mesh(geom, material);
        scene.add(mesh);
		
    	// 获取精灵贴图纹理
        var getTexture = function () {
            var texture = new THREE.ImageUtils.loadTexture("../assets/textures/particles/sprite-sheet.png");
            return texture;
        };

        var controls = new function () {
            this.size = 150;
            this.sprite = 0;
            this.transparent = true;
            this.opacity = 0.6;
            this.color = 0xffffff;
            this.rotateSystem = true;

            this.redraw = function () {
                sceneOrtho.children.forEach(function (child) {
                    if (child instanceof THREE.Sprite) sceneOrtho.remove(child);
                });
                createSprite(controls.size, controls.transparent, controls.opacity, controls.color, controls.sprite);
            };
        };

        var gui = new dat.GUI();
        gui.add(controls, 'sprite', 0, 4).step(1).onChange(controls.redraw);
        gui.add(controls, 'size', 0, 120).onChange(controls.redraw);
        gui.add(controls, 'transparent').onChange(controls.redraw);
        gui.add(controls, 'opacity', 0, 1).onChange(controls.redraw);
        gui.addColor(controls, 'color').onChange(controls.redraw);
        controls.redraw();
        render();

        function createSprite(size, transparent, opacity, color, spriteNumber) {
            var spriteMaterial = new THREE.SpriteMaterial({
                        opacity: opacity,
                        color: color,
                        transparent: transparent,
                        map: getTexture()
                    }
            );

            // 通过map的offset/repeat选择精灵贴图里的精灵图片,范围0~1
            spriteMaterial.map.offset = new THREE.Vector2(0.2 * spriteNumber, 0);
            spriteMaterial.map.repeat = new THREE.Vector2(1 / 5, 1);

            var sprite = new THREE.Sprite(spriteMaterial);
            sprite.scale.set(size, size, size);
            sprite.position.set(100, 50, -10);
            sprite.velocityX = 5;   // 精灵x轴移动速度因子
            sceneOrtho.add(sprite);  // 小人精灵添加到正交场景中
        }


        var step = 0;
        function render() {
            stats.update();
            
            // 透视相机y轴运动
            camera.position.y = Math.sin(step += 0.01) * 20;
			
            // 正交相机里的精灵x轴左右移动
            sceneOrtho.children.forEach(function (e) {
                if (e instanceof THREE.Sprite) {
                    // move the sprite along the bottom
                    e.position.x = e.position.x + e.velocityX;
                    if (e.position.x > window.innerWidth) {
                        e.velocityX = -5;
                        e.material.map.offset.set(1 / 5 * (controls.sprite % 4), 0);
                    }
                    if (e.position.x < 0) {
                        e.velocityX = 5;
                    }
                }
            });


            requestAnimationFrame(render);

            webGLRenderer.render(scene, camera);
            webGLRenderer.autoClear = false; // 关闭自动清理,不然在sceneOrtho render时会清理scene的,那么就会导致看不到scene里的球体了
            webGLRenderer.render(sceneOrtho, cameraOrtho);
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>


从高级几何体创建THREE.PointCloud

前面我们知道了THREE.PointCloud是基于几何体的顶点来渲染每个粒子的。前面我们都是使用的自定义顶点,其实也可以直接用高级几何体创建点云。

示例:使用上节的环状扭结几何体创建粒子

<!-- chapter-07-06.html -->
<!DOCTYPE html>
<html>
<head>
    <title>3D Torusknot</title>
    <script type="text/javascript" src="../libs/three.js"></script>
    <script type="text/javascript" src="../libs/stats.js"></script>
    <script type="text/javascript" src="../libs/dat.gui.js"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
    function init() {
        var stats = initStats();
        var scene = new THREE.Scene();
        var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

        var webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 50;
        camera.lookAt(new THREE.Vector3(10, 0, 0));
        document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

        var knot;
        var controls = new function () {
            this.radius = 13;
            this.tube = 1.7;
            this.radialSegments = 156;
            this.tubularSegments = 12;
            this.p = 5;
            this.q = 4;
            this.heightScale = 3.5;
            this.asParticles = false;
            this.rotate = false;

            this.redraw = function () {
                if (knot) scene.remove(knot);
                // 创建环状扭结几何体
                var geom = new THREE.TorusKnotGeometry(controls.radius, controls.tube, Math.round(controls.radialSegments), Math.round(controls.tubularSegments), Math.round(controls.p), Math.round(controls.q), controls.heightScale);

                if (controls.asParticles) {
                    // 创建点云对象
                    knot = createPointCloud(geom);
                } else {
                    // 创建网格对象
                    knot = createMesh(geom);
                }

                scene.add(knot);
            };
        };

        var gui = new dat.GUI();
        gui.add(controls, 'radius', 0, 40).onChange(controls.redraw);
        gui.add(controls, 'tube', 0, 40).onChange(controls.redraw);
        gui.add(controls, 'radialSegments', 0, 400).step(1).onChange(controls.redraw);
        gui.add(controls, 'tubularSegments', 1, 20).step(1).onChange(controls.redraw);
        gui.add(controls, 'p', 1, 10).step(1).onChange(controls.redraw);
        gui.add(controls, 'q', 1, 15).step(1).onChange(controls.redraw);
        gui.add(controls, 'heightScale', 0, 5).onChange(controls.redraw);
        gui.add(controls, 'asParticles').onChange(controls.redraw);
        gui.add(controls, 'rotate').onChange(controls.redraw);
        controls.redraw();
        render();
		
    	// 获取当前画布的纹理信息
        function generateSprite() {
            var canvas = document.createElement('canvas');
            canvas.width = 16;
            canvas.height = 16;
            var context = canvas.getContext('2d');
            var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
            gradient.addColorStop(0, 'rgba(255,255,255,1)');
            gradient.addColorStop(0.2, 'rgba(0,255,255,1)');
            gradient.addColorStop(0.4, 'rgba(0,0,64,1)');
            gradient.addColorStop(1, 'rgba(0,0,0,1)');
            context.fillStyle = gradient;
            context.fillRect(0, 0, canvas.width, canvas.height);
            var texture = new THREE.Texture(canvas);
            texture.needsUpdate = true;
            return texture;
        }

        function createPointCloud(geom) {
            var material = new THREE.PointCloudMaterial({
                color: 0xffffff,
                size: 3,
                transparent: true,
                blending: THREE.AdditiveBlending,
                map: generateSprite()  // 使用HTML5画布的纹理
            });

            var cloud = new THREE.PointCloud(geom, material);
            cloud.sortParticles = true;
            return cloud;
        }

        function createMesh(geom) {
            var meshMaterial = new THREE.MeshNormalMaterial({});
            meshMaterial.side = THREE.DoubleSide;
            var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial]);
            return mesh;
        }

        function render() {
            stats.update();
            if (controls.rotate) {
                knot.rotation.y += 0.01;
            }
            requestAnimationFrame(render);
            webGLRenderer.render(scene, camera);
        }

        function initStats() {
            var stats = new Stats();
            stats.setMode(0); // 0: fps, 1: ms
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            document.getElementById("Stats-output").appendChild(stats.domElement);
            return stats;
        }
    }
    window.onload = init;
</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/709077.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux--阅读文本指令:more、less

生成10000行数字并将其写入普通文件的指令&#xff1a; count0; while [ $count -le 10000 ]; do echo "hello bit ${count}"; let count; done > file.txt 直接cat < file.txt会刷屏的&#xff0c;故引入more 注&#xff1a;enter键控制下翻&#xff0c;q直…

拷贝对象,拷贝快乐:揭开JavaScript中拷贝的神奇面纱

文章目录 浅拷贝&#xff08;Shallow Copy&#xff09;1. 使用 Object.assign() 方法2. 使用展开运算符&#xff08;Spread Operator&#xff09;3. 使用数组的 slice() 或 concat() 方法 深拷贝&#xff08;Deep Copy&#xff09;1. 使用 JSON 对象的方法2. 使用递归方法自行实…

@validated的自定义注解校验编程式校验

自定义注解校验 前面的文章中&#xff0c;我们都是采用validate机制自带的条件注解来进行参数校验&#xff0c; 比如Min、NotNull…等等&#xff0c; 这些的确可以帮我们省去一部分的参数校验&#xff0c;可惜还有一部分的业务校验规则并不是如这般简单的&#xff0c; 比如前端…

C++ 程序设计入门

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

mybatis web使用01

CREATE TABLE t_act ( id bigint auto_increment, actno varchar(255) NOT NULL, balance int(11) default 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 创建 maven web 工程 在 IntelliJ IDEA 上使用 Maven 创建 web 工程的步骤如下&#xff1a; 在…

机器学习-方差和偏差理论

机器学习-方差和偏差理论 关于机器学习方差和偏差的内容其实很重要&#xff0c;这个方差和偏差可以帮助我们去分析&#xff0c;模型的泛化能力和过拟合的程度。 下面我们先给存储方差和偏差的公式&#xff1a; 注意&#xff0c;下式当中&#xff0c; f ( x ; D ) 表示在数据集…

jmeter测并发

文章目录 jmeter是什么安装使用1、测试计划2、线程组3、添加请求4、添加报告开始测试 jmeter是什么 Apache JMeter是一个开源的负载测试工具&#xff0c;用于评估和分析系统的性能和功能。它可以模拟多种类型的负载&#xff0c;包括 Web 应用程序、数据库服务器、FTP 服务器等…

密码找回安全总结-业务安全测试实操(29)

密码重置凭证与用户账户关联不严 有些信息系统在密码找回功能的校验逻辑上存在缺陷,只校验了密码重置凭证是否在 数据库中存在,但未严格校验该重置凭证和用户账号之间的绑定关系。这种密码重置凭证 与用户账户关联不严的逻辑漏洞就让攻击者可以通过在数据包中修改用户账号达…

全志V3S嵌入式驱动开发(解决kernel 5.2.y 网卡驱动问题)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 关于v3s网卡驱动&#xff0c;之前我们已经实现了。而且&#xff0c;它还是我们移植的第一个v3s驱动&#xff0c;之前的内容地址在这个地方&#xf…

ATTCK(五)之ATTCK子项目invoke-atomicredteam的安装与使用

ATT&CK子项目invoke-atomicredteam的安装与使用 Invoke_atomicredteam介绍 Invoke-AtomicRedTeam 是一个 PowerShell 模块&#xff0c;用于执行在Red Canary 的 Atomic Red Team 项目的atomics 文件夹中定义的测试。“atomics”包含由MITRE ATT&CK™ 框架定义的每个技…

jupyter notebook优化

一&#xff0e;这个是jupyter notebook主题设置的相关教程&#xff0c;如果经常看着高亮的屏幕&#xff0c;对于眼睛会是一种损伤&#xff01; https://blog.csdn.net/qq_41566627/article/details/104984796?utm_mediumdistribute.pc_relevant.none-task-blog-2%7Edefault%7…

[RocketMQ] Broker接收消息入口源码 (九)

文章目录 1.Broker处理请求入口1.1 registerProcessor注册消息处理器1.2 NettyServerHandler处理请求1.3 processRequestCommand分发处理请求1.3.1 rejectRequest是否拒绝请求 2.asyncProcessRequest异步处理请求2.1 asyncProcessRequest异步处理请求 3.总结 1.Broker处理请求入…

海康威视网络摄像头通过浏览器网页的配置流程

一、登录 输入网络摄像机IP地址&#xff0c;显示登录窗口&#xff0c;输入用户名和密码后&#xff0c;点击登录。如果是新设备&#xff0c;需要先激活设备&#xff0c;设置登录密码。&#xff08;Edge浏览器如何访问海康设备&#xff1f;&#xff09; 二、界面介绍 登入界面后…

logstash收集日志到elasticsearch

1.前言 logstash是一个相对较重的日志收集器&#xff0c;可以通过多种方式获取到日志数据&#xff0c;如tcp、日志文件、kafka、redis、rabbitmq等方式&#xff0c;还可以使用filter去过滤日志、转换日志为json格式&#xff0c;所以logstash是一个功能强大的日志收集器&#x…

六、神经网络完整训练流程(MNIST数据集为例)

一、下载数据集 MNIST数据集 将下载好的数据集解压放入同级项目路径下 二、导包 import torch import torch.nn as nnn import torch.optim as optim import torch.nn.functional as F import matplotlib.pyplot as plt import numpy as np from torchvision import datase…

华芯微特SWM34-IO速度优化之模拟SPI写速度提速

本文以在SWM34S&#xff08;M33内核,150Mhz&#xff0c;编译器Keil MDK 5.36&#xff09;上优化为例&#xff0c;说明优化方法和需要注意的地方&#xff0c;其他MCU可以参考。 在编写模拟SPI通信驱动LCD的例子的时候&#xff0c;会用到一个发送字节的核心函数&#xff0c;其基本…

【JavaSE】初步认识

目录 【1】Java语言概述 【1.1】Java是什么 【1.2】Java语言重要性 【1.3】Java语言发展简史 【1.4】Java语言特性 【1.5】 Java开发环境安装 【2】初识Java的main方法 【2.1】main方法示例 【2.2】运行Java程序 【3】注释 【3.1】基本规则 【3.2】注释规范 【4】…

ESP32-S2启动异常分析

客户反馈最近一批50块基于ESP32-S2的LoRaWAN gateway&#xff0c;有5块偶尔网络灯能亮&#xff0c;经常不能亮。 反复分析&#xff0c;定位&#xff0c;一个共同现象是用示波器看&#xff0c;串口输出一串信息后再没输出了。因为用了 ESP-ROM:esp32s2-rc4-20191025 Build:Oct …

企业构建知识库方案

AI模型理解误区&#xff1a;百万成本微调垂直行业达模型VS低成本建立企业专属知识库或ai助理_哔哩哔哩_bilibili

vscode关闭调试工具栏

问题描述 项目启动的时候老是蹦出这玩意 很碍眼 解决方案&#xff1a; 设置里搜索 选项改为hidden即可