记录--ThreeJs手搓一个罗盘特效
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
先上效果
前言
最近在学Three.js.,对着文档看了一周多,正好赶上码上掘金的活动,就顺便写了一个小demo,手搓一个罗盘特效。
太极
先来看一下太极的实现方式,这里我们使用CircleGeometry,将其分解开来可以看出是由圆形和半圆形组成 。
CircleGeometry
CircleGeometry | 官网案例 |
---|---|
radius | 半径 |
segments | 分段(三角面)的数量 |
thetaStart | 第一个分段的起始角度 |
thetaLength | 圆形扇区的中心角 |
这里不需要用到segments,但是需要颜色,所以定义一个函数传入半径、颜色、起始角度、中心角。
const createCircle = (r, color, thetaStart, thetaLength) => {
const material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide
});
const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
const circle = new THREE.Mesh(geometry, material);
return circle;
};
我们只需要通过传参生产不同大小的圆或半圆,再进行位移就可以实现其效果。
参考代码/73-96行 还有一些需要注意的地方写在注释里了。
罗盘
接下来看罗盘的实现,罗盘由一个个圆环组成,一个圆环又由内圈、外圈、分隔线、文字、八卦构成。
内外圈
内外圈我们使用两个RingGeometry
RingGeometry | 官网案例 |
---|---|
innerRadius | 内部半径 |
outerRadius | 外部半径 |
thetaSegments | 圆环的分段数 |
phiSegments | 圆环的分段数 |
thetaStart | 起始角度 |
thetaLength | 圆心角 |
通过circle控制内外圆圈的尺寸,circleWidth控制圆圈的线宽
const circleWidth = [0.1, 0.1]
const circle = [0, 1];
circle.forEach((i, j) => {
const RingGeo = new THREE.RingGeometry(
innerRing + i,
innerRing + i + circleWidth[j],
64,
1
);
const Ring = new THREE.Mesh(RingGeo, material);
RingGroup.add(Ring);
});
分隔线
分隔线使用的是PlaneGeometry
PlaneGeometry | 官网案例 |
---|---|
width | 宽度 |
height | 高度 |
widthSegments | 宽度分段数 |
heightSegments | 高度分段数 |
关于分隔线,它的长度就是内外圈的差值,所以这里使用外圈的数值,确定与圆心的距离就要使用内圈的数值加上自身长度除2。除此之外,还需要计算分隔线与圆心的夹角。
for (let i = 0; i < lineNum; i++) {
const r = innerRing + circle[1] / 2;
const rad = ((2 * Math.PI) / lineNum) * i;
const x = Math.cos(rad) * r;
const y = Math.sin(rad) * r;
const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
const line = new THREE.Mesh(planeGeo, material); line.position.set(x, y, 0);
line.rotation.set(0, 0, rad + Math.PI / 2);
RingGroup.add(line);
}
文字
文字使用的是TextGeometry,定位与分隔线一致,只需要交错开来。
for (let i = 0; i < lineNum; i++) {
const r = innerRing + circle[1] / 2;
const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
const x = Math.cos(rad) * r;
const y = Math.sin(rad) * r;
var txtGeo = new THREE.TextGeometry(text[i % text.length], {
font: font,
size: size,
height: 0.001,
curveSegments: 12,
});
txtGeo.translate(offsetX, offsetY, 0);
var txt = new THREE.Mesh(txtGeo, material);
txt.position.set(x, y, 0);
txt.rotation.set(0, 0, rad + -Math.PI / 2);
RingGroup.add(txtMesh);
不过TextGeometry的使用有一个得注意得前提,我们需要引入字体文件。
const fontLoader = new THREE.FontLoader();
const fontUrl =
"https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
let font;
const loadFont = new Promise((resolve, reject) => {
fontLoader.load(
fontUrl,
function (loadedFont) {
font = loadedFont;
resolve();
},
undefined,
function (err) {
reject(err);
}
);
});
八卦
圆环中除了文字之外,还能展示八卦,通过传递baguaData给createBagua生成每一个符号。
const baguaData = [
[1, 1, 1],
[0, 0, 0],
[0, 0, 1],
[0, 1, 0],
[0, 1, 1],
[1, 0, 0],
[1, 0, 1],
[1, 1, 0],
];
for (let i = 0; i < lineNum; i++) {
const r = innerRing + circle[1] / 2;
const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
const x = Math.cos(rad) * r;
const y = Math.sin(rad) * r;
RingGroup.add(
createBagua(baguaData[i % 8], x, y, 0 , rad + Math.PI / 2, text[0]),
);
}
createBagua参考代码/114-146行 ,和分隔线是一样的,使用了PlaneGeometry只是做了一些位置的设置。
视频贴图
在罗盘外,还有一圈视频,这里是用到了VideoTexture,实现也很简单。唯一得注意的是视频的跨域问题,需要配置video.crossOrigin = "anonymous"
const videoSrc = [
"https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
"https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
];
video.src = videoSrc[Math.floor(Math.random() * 2)];
video.crossOrigin = "anonymous";
const texture = new THREE.VideoTexture(video);
... const material = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
map: texture,
});
动画
动画总共分为三个部分,一块是旋转动画,一块是分解动画和入场动画,我们使用gsap实现。
旋转动画
gsap.to(videoGroup.rotation, {
duration: 30,
y: -Math.PI * 2,
repeat: -1,
ease: "none",
});
分解动画
.to(RingGroup.position, {
duration: 1,
ease: "ease.inOut",
y: Math.random() * 10 - 5,
delay: 5,
})
.to(RingGroup.position, {
duration: 1,
ease: "ease.inOut",
delay: 5,
y: 0,
})
}
入场动画
item.scale.set(1.2, 1.2, 1.2);
gsap.to(item.scale, {
duration: 0.8,
x: 1,
y: 1,
repeat: 0,
ease: "easeInOut",
});
旋转动画与分解动画可以写在生成函数内,也可以写在添加scene时,但是入场动画只能写到scene后,因为在生成时,动画就添加上了,当我们点击开始的时候才会将其加入场景中,而这时动画可能已经执行了。
总代码
html
<!--
灵感来源:一人之下里的八奇技————风后奇门,但是剧中和漫画中施展的罗盘有限,所以就参考了罗盘特效随便排布。
从抖音选取了两段剪辑随机播放。 实现方式:Three.js --> <!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head> <body>
<canvas class="webgl"></canvas>
<div class="box">
<div>大道五十,天衍四九,人遁其一</div>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e80a3fa048e84f02bb5ef5b6b04af87f~tplv-k3u1fbpfcp-no-mark:240:240:240:160.awebp?">
<div class="btn">推衍中...</div>
</div>
</body> </html>
style
*{
margin: 0;
padding: 0;
}
body {
background-color: #3d3f42;
} .box{
width: 350px;
height: 250px;
background-color: #000;
position:absolute;
top: calc(50% - 75px);
left: calc(50% - 150px);
border-radius: 10px;
font-size: 16px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
}
.btn {
width: 120px;
height: 35px;
line-height: 35px;
color: #fff;
border: 2px solid #fff;
border-radius: 10px;
font-size: 20px;
transition: 0.5s;
text-align: center;
cursor:default;
opacity: 0.5;
}
img{
width: 200px;
height: 150px;
}
js
import * as THREE from "three@0.125.1";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { gsap } from "gsap@3.5.1";
// Canvas
const canvas = document.querySelector("canvas.webgl");
const box = document.querySelector(".box");
const btn = document.querySelector(".btn");
const video = document.createElement("video"); // Scene
const scene = new THREE.Scene(); //---------------------- const fontLoader = new THREE.FontLoader();
const fontUrl =
"https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
let font;
const loadFont = new Promise((resolve, reject) => {
fontLoader.load(
fontUrl,
function (loadedFont) {
font = loadedFont;
resolve();
},
undefined,
function (err) {
reject(err);
}
);
});
const text = {
五行: ["金", "木", "水", "火", "土"],
八卦: ["乾", "坤", "震", "巽", "坎", "艮", "离", "兑"],
数字: ["壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", "拾"],
天干: ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"],
地支: [
"子",
"丑",
"寅",
"卯",
"辰",
"巳",
"午",
"未",
"申",
"酉",
"戌",
"亥",
],
方位: [
"甲",
"卯",
"乙",
"辰",
"巽",
"巳",
"丙",
"午",
"丁",
"未",
"坤",
"申",
"庚",
"酉",
"辛",
"戍",
"干",
"亥",
"壬",
"子",
"癸",
"丑",
"艮",
"寅",
],
节气: [
"立 春",
"雨 水",
"惊 蛰",
"春 分",
"清 明",
"谷 雨",
"立 夏",
"小 满",
"芒 种",
"夏 至",
"小 暑",
"大 暑",
"立 秋",
"处 暑",
"白 露",
"秋 分",
"寒 露",
"霜 降",
"立 冬",
"小 雪",
"大 雪",
"冬 至",
"小 寒",
"大 寒",
],
天星: [
"天辅",
"天垒",
"天汉",
"天厨",
"天市",
"天掊",
"天苑",
"天衡",
"天官",
"天罡",
"太乙",
"天屏",
"太微",
"天马",
"南极",
"天常",
"天钺",
"天关",
"天潢",
"少微",
"天乙",
"天魁",
"天厩",
"天皇",
],
天干1: [
"甲",
" ",
"乙",
" ",
"丙",
" ",
"丁",
" ",
"戊",
" ",
"己",
" ",
"庚",
" ",
"辛",
" ",
"壬",
" ",
"癸",
" ",
"甲",
" ",
"乙",
" ",
],
地支1: [
"子",
" ",
"丑",
" ",
"寅",
" ",
"卯",
" ",
"辰",
" ",
"巳",
" ",
"午",
" ",
"未",
" ",
"申",
" ",
"酉",
" ",
"戌",
" ",
"亥",
" ",
],
};
const data = [
{
innerRing: 2,
outerRing: 1.5,
lineWidth: 0.1,
circleWidth: [0.1, 0.1],
lineNum: 8,
text: [0xffffff],
offsetX: 0,
offsetY: 0,
size: 0.3,
direction: -1,
duration: 40,
},
{
innerRing: 3.5,
outerRing: 0.7,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 24,
text: text["方位"],
offsetX: -0.2,
offsetY: -0.08,
size: 0.3,
direction: 1,
duration: 10,
},
{
innerRing: 4.2,
outerRing: 0.7,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 24,
text: text["八卦"],
offsetX: -0.2,
offsetY: -0.08,
size: 0.3,
direction: -1,
duration: 20,
},
{
innerRing: 4.9,
outerRing: 1.3,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 24,
text: text["方位"],
offsetX: -0.4,
offsetY: -0.2,
size: 0.6,
direction: 1,
duration: 30,
},
{
innerRing: 6.2,
outerRing: 0.4,
lineWidth: 0.15,
circleWidth: [0, 0],
lineNum: 60,
text: text["地支"],
offsetX: -0.13,
offsetY: 0.01,
size: 0.2,
direction: 1,
duration: 25,
},
{
innerRing: 6.6,
outerRing: 0.4,
lineWidth: 0.15,
circleWidth: [0, 0],
lineNum: 60,
text: text["天干"],
offsetX: -0.13,
offsetY: -0.07,
size: 0.2,
direction: 1,
duration: 25,
},
{
innerRing: 7,
outerRing: 0.5,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 36,
text: text["天星"],
offsetX: -0.27,
offsetY: -0.03,
size: 0.2,
direction: -1,
duration: 20,
},
{
innerRing: 7.5,
outerRing: 0.5,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 24,
text: text["节气"],
offsetX: -0.36,
offsetY: -0.03,
size: 0.2,
direction: 1,
duration: 30,
},
{
innerRing: 8,
outerRing: 0.8,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 48,
text: text["方位"],
offsetX: -0.3,
offsetY: -0.1,
size: 0.4,
direction: 1,
duration: 35,
},
{
innerRing: 8.8,
outerRing: 0.8,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 32,
text: text["八卦"],
offsetX: -0.3,
offsetY: -0.1,
size: 0.4,
direction: -1,
duration: 60,
},
{
innerRing: 9.6,
outerRing: 0.4,
lineWidth: 0.18,
circleWidth: [0, 0],
lineNum: 120,
text: text["地支1"],
offsetX: -0.13,
offsetY: 0.01,
size: 0.2,
direction: 1,
duration: 30,
},
{
innerRing: 10,
outerRing: 0.4,
lineWidth: 0.18,
circleWidth: [0, 0],
lineNum: 120,
text: text["天干1"],
offsetX: -0.13,
offsetY: -0.07,
size: 0.2,
direction: 1,
duration: 30,
},
{
innerRing: 10.4,
outerRing: 0.5,
lineWidth: 0.1,
circleWidth: [0.1, 0.1],
lineNum: 60,
text: text["数字"],
offsetX: -0.13,
offsetY: -0.02,
size: 0.2,
direction: 1,
duration: 25,
},
{
innerRing: 10.9,
outerRing: 0.5,
lineWidth: 0.15,
circleWidth: [0.1, 0.1],
lineNum: 50,
text: text["五行"],
offsetX: -0.13,
offsetY: -0.02,
size: 0.2,
direction: 1,
duration: 35,
},
{
innerRing: 11.7,
outerRing: 1,
lineWidth: 0.1,
circleWidth: [1, 0],
lineNum: 64,
text: [0x000000],
offsetX: 0,
offsetY: 0,
size: 0.3,
direction: 1,
duration: 30,
},
];
const Rings = [];
const duration = [
0, 0.7, 0.7, 0.7, 0.7, 0, 0.7, 0.7, 0.7, 0.7, 0.7, 0, 0.7, 0.7, 0.7,
]; //Ring
const Ring = ({
innerRing,
outerRing,
lineWidth,
circleWidth,
lineNum,
offsetX,
offsetY,
text,
size,
direction,
duration,
}) => {
const RingGroup = new THREE.Group();
const circle = [0, outerRing];
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
}); // create ring
circle.forEach((i, j) => {
const RingGeo = new THREE.RingGeometry(
innerRing + i,
innerRing + circleWidth[j] + i,
64,
1
);
const Ring = new THREE.Mesh(RingGeo, material);
RingGroup.add(Ring);
}); // create line
for (let i = 0; i < lineNum; i++) {
const r = innerRing + circle[1] / 2;
const rad = ((2 * Math.PI) / lineNum) * i;
const x = Math.cos(rad) * r;
const y = Math.sin(rad) * r;
const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
const line = new THREE.Mesh(planeGeo, material); line.position.set(x, y, 0);
line.rotation.set(0, 0, rad + Math.PI / 2);
RingGroup.add(line);
} // create text
if (text.length > 1) {
for (let i = 0; i < lineNum; i++) {
const r = innerRing + circle[1] / 2;
const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
const x = Math.cos(rad) * r;
const y = Math.sin(rad) * r;
var txtGeo = new THREE.TextGeometry(text[i % text.length], {
font: font,
size: size,
height: 0.001,
curveSegments: 12,
});
txtGeo.translate(offsetX, offsetY, 0);
var txtMater = new THREE.MeshStandardMaterial({ color: 0xffffff });
var txtMesh = new THREE.Mesh(txtGeo, txtMater);
txtMesh.position.set(x, y, 0);
txtMesh.rotation.set(0, 0, rad + -Math.PI / 2);
RingGroup.add(txtMesh);
}
} // create bagua
if (text.length == 1) {
const baguaData = [
[1, 1, 1],
[0, 0, 0],
[0, 0, 1],
[0, 1, 0],
[0, 1, 1],
[1, 0, 0],
[1, 0, 1],
[1, 1, 0],
];
for (let i = 0; i < lineNum; i++) {
const r = innerRing + circle[1] / 2;
const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
const x = Math.cos(rad) * r;
const y = Math.sin(rad) * r;
RingGroup.add(
createBagua(baguaData[i % 8], x, y, 0.0001, rad + Math.PI / 2, text[0]),
createBagua(baguaData[i % 8], x, y, -0.0001, rad + Math.PI / 2, text[0])
);
}
} // animation
{
gsap.to(RingGroup.rotation, {
duration: duration,
z: Math.PI * 2 * direction,
repeat: -1,
ease: "none",
}); const amColor = { r: 1, g: 1, b: 1 };
const explode = gsap.timeline({ repeat: -1, delay: 5 });
explode
.to(RingGroup.position, {
duration: 1,
ease: "ease.inOut",
y: Math.random() * 10 - 5,
delay: 5,
})
.to(amColor, {
r: 133 / 255,
g: 193 / 255,
b: 255 / 255,
duration: 2,
onUpdate: () =>
ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
})
.to(RingGroup.position, {
duration: 1,
ease: "ease.inOut",
delay: 5,
y: 0,
})
.to(amColor, {
r: 1,
g: 1,
b: 1,
duration: 3,
onUpdate: () =>
ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
});
} // rotate
RingGroup.rotateX(-Math.PI / 2);
return RingGroup;
}; //taiji
const createTaiji = (position, scale) => {
const taiji = new THREE.Group();
const createCircle = (r, color, thetaStart, thetaLength) => {
const material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide,
});
const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
const circle = new THREE.Mesh(geometry, material);
return circle;
}; const ying = createCircle(1.8, 0x000000, 0, Math.PI);
const yang = createCircle(1.8, 0xffffff, Math.PI, Math.PI);
const Lblack = createCircle(0.9, 0x000000, 0, Math.PI * 2);
const Lwhite = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
const Sblack = createCircle(0.25, 0x000000, 0, Math.PI * 2);
const Swhite = createCircle(0.25, 0xffffff, 0, Math.PI * 2); const Lblack1 = createCircle(0.9, 0x000000, 0, Math.PI * 2);
const Lwhite1 = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
const Sblack1 = createCircle(0.25, 0x000000, 0, Math.PI * 2);
const Swhite1 = createCircle(0.25, 0xffffff, 0, Math.PI * 2); Lblack.position.set(-0.9, 0, 0.001);
Lwhite.position.set(0.9, 0, 0.001);
Swhite.position.set(-0.9, 0, 0.002);
Sblack.position.set(0.9, 0, 0.002);
Lblack1.position.set(-0.9, 0, -0.001);
Lwhite1.position.set(0.9, 0, -0.001);
Swhite1.position.set(-0.9, 0, -0.002);
Sblack1.position.set(0.9, 0, -0.002); taiji.add(
ying,
yang,
Lblack,
Lwhite,
Swhite,
Sblack,
Lblack1,
Lwhite1,
Swhite1,
Sblack1
);
gsap.to(taiji.rotation, {
duration: 30,
z: Math.PI * 2,
repeat: -1,
ease: "none",
});
taiji.rotateX(-Math.PI / 2);
taiji.position.set(...position);
taiji.scale.set(...scale);
return taiji;
};
scene.add(createTaiji([0, 0, 0], [1, 1, 1])); // bagua
const createBagua = (data, x, y, z, deg, color) => {
const idx = [-0.32, 0, 0.32];
const bagua = new THREE.Group();
const material = new THREE.MeshStandardMaterial({
color: color,
side: THREE.DoubleSide,
});
data.forEach((i, j) => {
if (i == 1) {
const yang = new THREE.Mesh(new THREE.PlaneGeometry(1, 0.2), material);
yang.position.set(0, idx[j], 0);
bagua.add(yang);
}
if (i == 0) {
const ying1 = new THREE.Mesh(
new THREE.PlaneGeometry(0.45, 0.2),
material
);
const ying2 = new THREE.Mesh(
new THREE.PlaneGeometry(0.45, 0.2),
material
);
ying1.position.set(-0.275, idx[j], 0);
ying2.position.set(0.275, idx[j], 0);
bagua.add(ying1, ying2);
}
});
bagua.position.set(x, y, z);
bagua.rotation.set(0, 0, deg);
return bagua;
}; const showVideo = () => {
const videoSrc = [
"https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
"https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
];
video.src = videoSrc[Math.floor(Math.random() * 2)];
video.crossOrigin = "anonymous";
const texture = new THREE.VideoTexture(video);
const videoGroup = new THREE.Group();
for (let i = 0; i < 8; i++) {
const r = 25;
const rad = ((2 * Math.PI) / 8) * i;
const x = Math.cos(rad) * r;
const y = Math.sin(rad) * r;
const planeGeo = new THREE.PlaneGeometry(16, 9);
const material = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
map: texture,
});
const plane = new THREE.Mesh(planeGeo, material); plane.position.set(x, 4.5, y);
if (i % 2 == 0) plane.rotation.set(0, rad + Math.PI / 2, 0);
else plane.rotation.set(0, rad, 0);
videoGroup.add(plane);
}
gsap.to(videoGroup.rotation, {
duration: 30,
y: -Math.PI * 2,
repeat: -1,
ease: "none",
});
scene.add(videoGroup);
}; //loadFont, Rings
loadFont.then(() => {
data.forEach((item) => {
Rings.push(Ring(item));
});
btn.innerText = "入 局";
btn.style.opacity = 1;
btn.style.cursor = "pointer";
}); //start
const start = function () {
const showRing = (item) => {
scene.add(item);
item.scale.set(1.2, 1.2, 1.2);
gsap.to(item.scale, {
duration: 0.8,
x: 1,
y: 1,
repeat: 0,
ease: "easeInOut",
});
};
const tl = gsap.timeline();
Rings.forEach((item, idx) => {
tl.to(".webgl", { duration: duration[idx] }).call(() => {
showRing(item);
});
});
}; btn.addEventListener("click", () => {
box.style.display = "none";
start();
showVideo();
video.play();
video.loop = true;
}); //---------------------- //Light
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight); //Sizes
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
}; // Camera
const camera = new THREE.PerspectiveCamera(
75,
sizes.width / sizes.height,
1,
1000
);
camera.position.y = 10;
camera.position.x = 10;
camera.position.z = 10;
camera.lookAt(scene.position);
scene.add(camera); //Renderer
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true,
}); renderer.setSize(sizes.width, sizes.height); //controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
controls.maxDistance = 50;
controls.enablePan = false; const tick = () => {
renderer.render(scene, camera);
controls.update();
window.requestAnimationFrame(tick);
};
tick(); window.addEventListener("resize", () => {
sizes.height = window.innerHeight;
sizes.width = window.innerWidth; camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix(); renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(window.devicePixelRatio);
});
本文转载于:
https://juejin.cn/post/7220629398965108794
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
记录--ThreeJs手搓一个罗盘特效的更多相关文章
- 手搓一个“七夕限定”,用3D Engine 5分钟实现烟花绽放效果
七夕来咯!又到了给重要的人送惊喜的时刻. 今年,除了将心意融入花和礼物,作为程序员,用自己的代码本事手搓一个技术感十足"七夕限定"惊喜,我觉得,这是不亚于车马慢时代手写信的古典主义 ...
- 手搓一个兔子问题(分享一个C语言问题,持续更新...)
大家好,我是小七夜,今天就不分享C语言的基础知识了,分享一个比较好玩的C语言经典例题:兔子问题 题目是这样的:说有一个穷苦人这天捉到了一只公兔子,为了能繁衍后代他又买了一只母兔子,后来兔子开始生小兔子 ...
- 手搓一个C语言简单计算器。
#include <stdio.h> void xing(int shu); void biaoti(int kong,char * title); void zhuyemian(char ...
- 放弃antd table,基于React手写一个虚拟滚动的表格
缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反 ...
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- 手撸一个SpringBoot-Starter
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
- 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理
摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...
- Golang:手撸一个支持六种级别的日志库
Golang标准日志库提供的日志输出方法有Print.Fatal.Panic等,没有常见的Debug.Info.Error等日志级别,用起来不太顺手.这篇文章就来手撸一个自己的日志库,可以记录不同级别 ...
- CozyRSS开发记录9-快速实现一个RSS解析器
CozyRSS开发记录9-快速实现一个RSS解析器 1.再读RSS标准 既然需要自己实现一个RSS解析器,那自然需要仔细的读一读RSS的标准文档.在网上随便找了两份,一份英文一份中文: http:// ...
- 『练手』手写一个独立Json算法 JsonHelper
背景: > 一直使用 Newtonsoft.Json.dll 也算挺稳定的. > 但这个框架也挺闹心的: > 1.影响编译失败:https://www.cnblogs.com/zih ...
随机推荐
- 如何使用graalvm为带有反射功能的java代码生成native image
译自Configure Native Image with the Tracing Agent graal官方文档 , 以下所有命令需要在linux环境下操作,graalvm也支持windows. 要 ...
- ABC 333
ABCDE 赛时 AC. F 列方程:\(f_{i,j}\) 表示有 \(i\) 个人,第 \(j\) 个人最终活下来的概率. \(f_{i,1}=\dfrac{1}{2}f_{i,i}\),因为只有 ...
- 2023年多校联训NOIP层测试4+洛谷 8 月月赛 I & RiOI Round 2
2023年多校联训NOIP层测试4 爆零了 T1 幸运数字 \(0pts\) 首先考虑一个结论: \(4\) 的倍数一定满足最后两位能被 \(4\) 整除. 从 \(1\) 进行输入,方便处理.若枚举 ...
- Argocd学习
argocd官网文档链接 ArgoCD官网文档 在K8S集群使用argocd命令将集群添加到argcd的cluster列表中 argocd cluster add kubernetes-admin@i ...
- 【JS】因两道Promise执行题让我产生自我怀疑,从零手写Promise加深原理理解
壹 ❀ 引 其实在去年七月份,博客所认识的一个朋友问了我一个关于Promise执行先后的问题,具体代码如下: const fn = (s) => ( new Promise((resolve, ...
- Js文件异步加载
Js文件异步加载 浏览器中渲染引擎与Js脚本引擎是互斥的,在浏览器开始渲染页面时,如果遇到<script>标签,会停止渲染当前页面,也就是说在脚本加载与执行的过程中会阻塞页面的渲染,在网速 ...
- Swoole从入门到入土(23)——多进程[进程池Process\Pool]
Swoole提供的进程池为Process\Pool,基于 Swoole\Server 的 Manager 管理进程模块实现.可管理多个工作进程.该模块的核心功能为进程管理,相比 Process 实现多 ...
- 全流程点云机器学习(一)使用CloudCompare自制sharpNet数据集
前言 这不是高支模项目需要嘛,他们用传统算法切那个横杆竖杆流程复杂耗时很长,所以想能不能用机器学习完成这些工作,所以我就来整这个工作了. 工欲善其事,必先利其器,在正式开始之前,我们先要搞懂如何切分数 ...
- std::shared_ptr 线程安全方面的思考
一直惦记着 std::shared_ptr 线程安全的问题,看了些文章后,又怕过段时间忘记了,遂记录下来 std::shared_ptr 的线程安全问题主要有以下两种: 引用计数的加减操作是否线程安全 ...
- 细说Spring框架之核心01-概述
官网:https://spring.io/projects/spring-framework 文档:https://docs.spring.io/spring-framework/docs/curre ...