马黑黑 发表于 2025-5-30 07:40

ThreeJS入门(十)精灵

本帖最后由 马黑黑 于 2025-5-30 08:04 编辑 <br /><br /><style>
        .artBox { font-size: 18px; }
        .artBox > p { margin: 10px 0; line-height: 30px; }
        .artBox mark { background: lightblue; padding: 4px 8px; }
        #prevBox { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: beige; display: none; padding: 0; overflow: hidden; z-index: 1000; margin: 0; }
        #prevBox::after { position: absolute; content: '关闭预览'; bottom: 10px; left: calc(50% - 40px); padding: 0 4px; width: 80px; height: 30px; line-height: 30px; text-align: center; border: 1px solid #efe; border-radius: 6px; background: #eee; font-size: 14px; box-shadow: 2px 2px 6px rgba(0,0,0,.25); cursor: pointer; }
        iframe { position: relative; width: 100%; height: 100%; border: none; outline: none; box-sizing: border-box; margin: 0; }
</style>

<div id="prevBox"></div>
<div class="artBox">
        <p>精灵 <mark>Sprite</mark> 是 ThreeJS 一个特殊的存在,它的存在理由之一是极低的资源消耗。精灵能够节省资源开销源于其特性:一个形状为矩形、默认颜色为白色的不需要实例化几何体的 2d 图像,总是面向相机,没有阴影,不会旋转。精灵因其此类特性非常适合用于显示图标、指示器、粒子系统中的单个粒子、UI 元素等等。</p>
        <p>在 ThreeJS 中绘制一个精灵图像非常简单:</p>
        <div class="hEdiv"><pre class="hEpre">
// 创建一个默认精灵
const sprite = new THREE.Sprite(new THREE.SpriteMaterial());
// 精灵加入到场景
scene.add(sprite);
        </pre></div>
        <p>精灵由于总是面部朝着相机,因而使用 rotate 或 rotation 旋转 Sprite 精灵是徒劳的,甚至在场景中加入了相机轨道控制器之后自动和手动旋转、翻滚场景,精灵都不为所动。下面的代码分别绘制了一个立方体(BoxGeometry)、一个平面几何体(PlaneGeometry)、一个精灵(Sprite),场景中加入了相机轨道控制器、可以自动、手动翻转场景中的景象:</p>
        <div class="hEdiv"><pre class="hEpre" id="pre1">
&lt;!-- 标准化模块系统 :创建可靠的模块导入结构 --&gt;
&lt;script type="importmap"&gt;
{
"imports": {
    "three": "https://unpkg.ihwx.cn/three@0.176.0/build/three.module.js",
    "three/examples/jsm/": "https://unpkg.ihwx.cn/three@0.176.0/examples/jsm/"
}
}
&lt;/script&gt;

&lt;script type="module"&gt;
        import * as THREE from 'three'; // 导入ThreeJS核心库
        import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; // 导入相机轨道控制库

        const scene = new THREE.Scene;
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 0, 5);
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement); // 轨道控制器
        controls.autoRotate = true; // 启用轨道自转

        // 立方体
        const cube = new THREE.Mesh(
                new THREE.BoxGeometry(),
                new THREE.MeshNormalMaterial()
        );
        cube.position.set(-2, 0, 0); // 定位
        cube.rotateX(Math.PI / 4); // X轴方向侧身

        // 几何体平面
        const plane = new THREE.Mesh(
                new THREE.PlaneGeometry(),
                new THREE.MeshNormalMaterial({ side: THREE.DoubleSide }) // 双面渲染
        );
        plane.position.set(1.5, 0, 0); // 定位
        plane.rotateX(Math.PI / 4);// X轴方向侧身

        // 钢蓝色精灵(可定位但旋转设置无效)
        const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0x4682b4 }));

        scene.add(cube, plane, sprite); // 图像都加入到场景中

        const animate = () =&gt; {
                requestAnimationFrame(animate);
                controls.update(); // 更新轨道控制器令其自转
                renderer.render(scene, camera);
        };

        window.onresize = () =&gt; {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
        }

        animate();
&lt;/script&gt;
        </pre></div>
        <blockquote><button id="btnPrev1">运行代码</button></blockquote>
        <p>查看上面例子的运行效果会发现,平面几何体、立方体绕着精灵旋转,精灵纹丝不动,这证实了如前已述精灵的特性,总是面朝相机。顺便提提:平面几何体是升级版的精灵,它有 <mark>Geometry</mark> 几何体,是名副其实的几何体家族元素(只不过人家是2d的),不像 <mark>Sprite</mark> 精灵那样是个没有骨架的东东。</p>
        <p>精灵天生一副四方脸,不一定符合大众的审美情趣。好在有上一讲讲到的 texture 纹理,我们可以轻轻松松给精灵贴个图,让它长出一张万人迷的漂亮脸蛋应该不是个事,例如下面示例的太阳花脸儿:贴图来源是一张 SVG 图片,贴上这张图片的精灵从Z轴的 -20 个距离单位出发,向观者扑面而来,如此往复。代码如下:</p>
        <div class="hEdiv"><pre class="hEpre" id="pre2">
&lt;div style="margin: 10px; position: absolute;"&gt;点击页面暂停/继续动画&lt;/div&gt;

&lt;script type="module"&gt;
        import * as THREE from 'https://638183.freep.cn/638183/web/ku/three.module.min.js';

        const scene = new THREE.Scene;
        const clock = new THREE.Clock();
        const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 0, 5);
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        // 设置一些动画需要的变量 :zpos - 出发点z坐标值,currentpos - 当前点z坐标值,step 歩幅系数
        let zpos = -20, currentpos = -20, step = 5;

        // 加载纹理
        const texture = new THREE.TextureLoader().load('https://638183.freep.cn/638183/web/svg/sunfl-2.svg', () => {
                renderer.render(scene, camera);
                animate();
        });

        // 创建精灵 :颜色 + 纹理
        const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xff4500, map: texture }));
        sprite.position.z = zpos; // 精灵在Z轴上的初始位置

        scene.add(sprite); // 精灵加入到场景

        // 动画函数 :精灵在Z轴上的位置变化 + 精灵材质旋转
        const animate = () => {
                requestAnimationFrame(animate); // 请求关键帧动画API循环调用函数
                const delta = clock.getDelta(); // 从动画时钟获取上下帧时间间隔
                currentpos += delta * step; // Z轴当前位置变化
                if (currentpos > 5) currentpos = zpos; // Z轴当前位置大于 5 个距离单位时复位到出发点
                sprite.position.z = currentpos; // 精灵在Z轴上的位置
                sprite.material.rotation += delta * 2; // 精灵的材质旋转(精灵不会旋转但材质可以)
                renderer.render(scene, camera); // 渲染效果
        };

        window.onresize = () => {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
        }

        document.onclick = () => clock.running ? clock.stop() : clock.start();
&lt;/script&gt;
        </pre></div>
        <blockquote><button id="btnPrev2">运行代码</button></blockquote>
        <p>texture 纹理彻底改变了精灵的容貌,纹理使用的图片因此至关重要,一般建议使用背景透明的图片,如 png、gif、webp 或 svg,这些格式的图片都允许制作出背景透明、只呈现图像外观的最终效果。除了改变精灵的外观,我们还让不能旋转的精灵在运行的时候旋转了,但必须明白,精灵自己并没有旋转,大家看到的旋转效果实际上是构建成精灵外观的材质 <mark>material</mark> 即精灵的漂亮皮囊在旋转;并且,如果愿意,当加入了相机轨道控制器,还可以通过类似的其它途径驱动精灵也能和其它几何体一样响应轨道的变化而产生相应的变化,不过这个知识点已经超出了入门的范畴,而且,也不符合精灵的存在意义。</p>
        <p>附:1. <a href="http://www.yanhuangxueyuan.com/threejs/docs/index.html?q=sprite#api/zh/objects/Sprite" target="_blank">ThreeJS中文网·精灵(Sprite)</a> 2. <a href="http://www.webgl3d.cn/pages/75d790/" targetBox="_blank">ThreeJS中文网·精灵模型Sprite</a></p>
</div>

<script type="module">
        import hlight from 'https://638183.freep.cn/638183/web/helight/helight1.js';
        const pres = document.querySelectorAll('.hEpre');
        const divs = document.querySelectorAll('.hEdiv');
        divs.forEach( (div, key) => hlight.hl(div, pres));
       
        const preView = (htmlCode, targetBox) => {
                if (targetBox.innerHTML) return;
                const iframe = document.createElement('iframe');
                htmlCode = htmlCode + '<style>body {margin: 0; }</style>';
                iframe.srcdoc = htmlCode;
                targetBox.appendChild(iframe);
                targetBox.style.display = 'block';
                targetBox.onclick = () => {
                        targetBox.innerHTML = '';
                        targetBox.style.display = 'none';
                }
        };

        const value1 = pre1.textContent;
        const value2 = pre2.textContent;
        btnPrev1.onclick = () => preView(value1, prevBox);
        btnPrev2.onclick = () => preView(value2, prevBox);
</script>

花飞飞 发表于 2025-5-30 16:57

刚看了一下,精灵还是个倔强的存在,怎都驱动不了也是好玩,静态的精灵能着色。。
这个着色还会影响动态纹理的颜色{:4_199:}

花飞飞 发表于 2025-5-30 17:02

适用于显示图标、指示器、粒子系统中的单个粒子、UI 元素等,前几种还好,最后的UI元素又是一个新概念。。
给了纹理之后的精灵,设了颜色也看不到,材质去掉直透明,很神秘的存在啊。。。。
扑面而来的有点被惊到了,因为开头小小一个,最后能变到比全屏还大~{:4_173:}

花飞飞 发表于 2025-5-30 17:06

精灵没转,只是漂亮的皮囊在转,这个理解了一会{:4_173:}
精灵由不会动到让人感觉在动,这个过程美妙。
越来越多的新内容出现,ThreeJS 很神奇。。。

马黑黑 发表于 2025-5-30 19:29

花飞飞 发表于 2025-5-30 17:06
精灵没转,只是漂亮的皮囊在转,这个理解了一会
精灵由不会动到让人感觉在动,这个过程美妙。
...

精灵是不能绕开的项目,不然可以结束教程了

马黑黑 发表于 2025-5-30 19:30

花飞飞 发表于 2025-5-30 16:57
刚看了一下,精灵还是个倔强的存在,怎都驱动不了也是好玩,静态的精灵能着色。。
这个着色还会影响动态纹 ...

材质中,Normal材质自带了颜色,其余的基本都可以上色

马黑黑 发表于 2025-5-30 19:30

花飞飞 发表于 2025-5-30 17:02
适用于显示图标、指示器、粒子系统中的单个粒子、UI 元素等,前几种还好,最后的UI元素又是一个新概念。。
...

UI是界面的意思

马黑黑 发表于 2025-5-30 19:31

花飞飞 发表于 2025-5-30 17:02
适用于显示图标、指示器、粒子系统中的单个粒子、UI 元素等,前几种还好,最后的UI元素又是一个新概念。。
...
精灵是第一个ThreeJS音画帖使用的元素,矩形粒子扑面而来的那个帖子

杨帆 发表于 2025-5-30 20:11

谢谢马老师的经典教程!{:4_191:}

哈哈,好看的皮囊千篇一律,有趣的精灵万里挑一

马黑黑 发表于 2025-5-30 20:33

杨帆 发表于 2025-5-30 20:11
谢谢马老师的经典教程!

哈哈,好看的皮囊千篇一律,有趣的精灵万里挑一

说得好

花飞飞 发表于 2025-5-30 20:56

马黑黑 发表于 2025-5-30 19:29
精灵是不能绕开的项目,不然可以结束教程了

别介,这个标题一看就充满诱惑,忍不住要点开看看。。
精灵么,没想到是四方脸一大片{:4_170:}

花飞飞 发表于 2025-5-30 20:57

马黑黑 发表于 2025-5-30 19:30
材质中,Normal材质自带了颜色,其余的基本都可以上色

按道理赋予颜色应该可以看到的,静态的可以理解
动态的时候是因为纹理的存在而颜色不存在了么,我把纹理取消,啥也看不到了

花飞飞 发表于 2025-5-30 20:58

马黑黑 发表于 2025-5-30 19:30
UI是界面的意思

用在哪里的界面{:4_173:}之前没见你提过。这个面生得很

马黑黑 发表于 2025-5-30 20:58

花飞飞 发表于 2025-5-30 20:56
别介,这个标题一看就充满诱惑,忍不住要点开看看。。
精灵么,没想到是四方脸一大片

它就是轻柔、没骨头所以叫精灵。精灵也是很多语言或准语言都存在的,比如HTML图片,以前也有个精灵的,做热点用的

花飞飞 发表于 2025-5-30 20:58

马黑黑 发表于 2025-5-30 19:31
精灵是第一个ThreeJS音画帖使用的元素,矩形粒子扑面而来的那个帖子

天哪,想起来了。。。还说为啥这个粒子是方的。{:4_173:}我回去再瞅瞅它

马黑黑 发表于 2025-5-30 21:02

花飞飞 发表于 2025-5-30 20:58
天哪,想起来了。。。还说为啥这个粒子是方的。我回去再瞅瞅它

那时候刚刚接触ThreeJS,还不会设置精灵的纹理贴图

马黑黑 发表于 2025-5-30 21:04

花飞飞 发表于 2025-5-30 20:57
按道理赋予颜色应该可以看到的,静态的可以理解
动态的时候是因为纹理的存在而颜色不存在了么,我把纹理 ...

纹理和颜色可以叠加在一起

花飞飞 发表于 2025-5-30 21:16

马黑黑 发表于 2025-5-30 21:02
那时候刚刚接触ThreeJS,还不会设置精灵的纹理贴图

那现在那个贴子粒子可以各种样子飞舞了。想想就很漂亮。。
突然发现,你也前不久才开始接触ThreeJS。。这些东东这么晦涩难懂,
现在就把教程出得系统有条理。。。
你这学习能力简直了,膜拜一个,厉害。。

花飞飞 发表于 2025-5-30 21:16

马黑黑 发表于 2025-5-30 21:04
纹理和颜色可以叠加在一起

对的,试的过程发现,纹理图片的颜色是受精灵颜色设置影响的

马黑黑 发表于 2025-5-30 21:36

花飞飞 发表于 2025-5-30 21:16
对的,试的过程发现,纹理图片的颜色是受精灵颜色设置影响的

相互叠加
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: ThreeJS入门(十)精灵