0%

1. MeshStandardMaterial

  • 用途:基于物理渲染(PBR),适用于追求真实感的场景,支持金属度、粗糙度、环境光遮蔽(AO)、法线贴图等。
  • 优势:高度逼真,支持复杂光照模型,适合高质量渲染。
  • 缺点:计算开销较大,不适合性能敏感的场景。

    2. MeshLambertMaterial

  • 用途:基于 Lambert 反射模型,是一种简化的漫反射材质,不考虑高光或复杂反射。
  • 优势:计算简单,性能好,适合非金属、低光要求的物体。
  • 缺点:缺乏真实感,不支持法线贴图、金属度等高级特性。

    3. MeshPhongMaterial

  • 用途:基于 Phong 反射模型,支持高光反射,可以模拟塑料、漆面等材质。
  • 优势:比 Lambert 更真实,支持高光和法线贴图。
  • 缺点:不是基于物理的渲染,效果不如 PBR 自然。

    4. MeshDepthMaterial

  • 用途:不显示颜色,只渲染深度信息(相机到物体的距离) QQs按:接近近投影面(camera.near)变明亮反之变暗 适合做雾效 寂静岭阴森视距。
  • 优势:常用于后期处理、阴影生成、深度图分析等。
  • 缺点:不适用于常规物体渲染。

    5. MeshNormalMaterial

  • 用途:显示法线方向,用于调试或特殊视觉效果。
  • 优势:便于检查模型的法线是否正确。
  • 缺点:无法用于真实渲染。

性能与适用场景的权衡

  • 性能优化:在移动端或低性能设备上,使用 MeshLambertMaterialMeshBasicMaterial 可以显著提高渲染性能,而 MeshStandardMaterial 由于其复杂的光照计算,可能会造成帧率下降。

  • 特殊效果:例如,MeshDepthMaterial 可用于实现深度边缘检测、雾效或自定义的后期处理效果,这些是 MeshStandardMaterial 无法直接实现的。

  • 艺术风格:某些风格化渲染(如卡通渲染)更适合使用 MeshToonMaterial,而不是 PBR 材质。

三、兼容性与历史原因

  • Three.js 作为一个成熟的 3D 渲染库,需要兼容多种渲染管线和设备。一些材质(如 MeshLambertMaterialMeshPhongMaterial)在早期版本中就已经存在,它们为不支持 PBR 的设备或项目提供了替代方案。
  • 在 WebGL 1.0 环境下,某些高级特性(如 PBR)可能不被完全支持,此时需要使用更简单的材质。

四、实际开发中的选择建议

材质类型 适用场景 是否支持贴图 性能
MeshStandardMaterial 高质量渲染、PBR 场景 支持所有贴图 较低
MeshLambertMaterial 简单漫反射、低性能设备 仅支持漫反射贴图 较高
MeshPhongMaterial 高光材质、塑料效果 支持高光、法线贴图 中等
MeshDepthMaterial 深度分析、后期处理 不支持颜色贴图
MeshNormalMaterial 法线调试、特殊效果 不支持颜色贴图

roughMap vs displacementMap
前者是PBR即作用于光线反射 后者是作用于顶点位移

@React-three/fiber

开箱即用(out of box) 组件化场景对象如
\ \ \

1
npm i three @react-three/fiber

额外的,transpile in Next.js, set in next.config.js:

1
transpilePackages: ['three'],

React Native中需要expo-gl

1
2
3
4
5
# Automatically install
expo install expo-gl

# Install NPM dependencies
npm install three @react-three/fiber

set Metro boundle tool if use loader, gltf and so on

1
2
3
4
5
6
7
// metro.config.js
module.exports = {
resolver: {
sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx', 'cjs', 'mjs'],
assetExts: ['glb', 'gltf', 'png', 'jpg'],
},
}

简单的栗子

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react'
import { Canvas } from '@react-three/fiber'

const App = () => (
<Canvas>{/* 相当于初始化的Scene */}
<pointLight position={[10, 10, 10]} />
<mesh>
<sphereGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
</Canvas>
)

@React-three/drei

@react-three/drei 是一个工具包(Utility Library),为 @react-three/fiber 提供了一系列高阶组件和实用函数,比如加载模型(useGLTF)、添加环境光(Environment)、设置相机控制(OrbitControls)等。 德语单词“Drei”,意思是“三”
从零探索@react-three/fiber @React-three/drei

Leva

1
npm install leva@latest --save-dev
1
2
3
4
5
6
7
8
9
import { useControls } from 'leva'

const color = useControls({
value: 'green',
})

<Canvas>
<color attach="background" args={[color.value]} />
</Canvas>

#

Renderer

init scene

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const canvas = document.createElement('canvas')
const sizes = {
width: this.$refs.container.clientWidth,
height: this.$refs.container.clientHeight
}
canvas.width = sizes.width;
canvas.height = sizes.height;
this.$refs.container.appendChild(canvas);
// 创建3D场景对象Scene
const scene = new THREE.Scene();
console.log(scene);
// Geometry
const geometry = new THREE.SphereGeometry(5, 32, 32);
const material = new THREE.MeshBasicMaterial({color: '#aaa', wireframe:true})
// Mesh
const mesh = new THREE.Mesh(geometry, material)
mesh.position.set(0, 0, -10);
scene.add(mesh)

// 创建相机对象Camera
// 45-75 是透视投影相机建议的视角范围fov
// 视角比例aspect w/h
// 投影近平面
// 投影远平面
const camera = new THREE.PerspectiveCamera(75, sizes.width/sizes.height, 0.1, 1000);
scene.add(camera);

const renderer = new THREE.WebGLRenderer({canvas: canvas});
renderer.render(scene, camera);

Caution! 虽设置canvas尺寸 但canvas初始化renderer时 其尺寸会受到影响 其结果仍使canvas超出父容器 出现滚动条
应使用renderer.setSize

1
2
3
const renderer = new THREE.WebGLRenderer()
renderer.setSize(container.current.clientWidth, container.current.clientHeight)
container.current.appendChild(renderer.domElement)

另外onResize要加防抖 overflow hidden该加还是得加

Controls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
...
// Add OrbitControls
const controls = new OrbitControls(camera, renderer.domElement)

// controls.update() must be called after any manual changes to the camera's transform
camera.position.set( 0, 20, 100 );
controls.update();

// camera 移动是重复的render过程 需要requestAnimationFrame
function animate() {

requestAnimationFrame( animate );

// required if controls.enableDamping or controls.autoRotate are set to true
controls.update();

renderer.render( scene, camera );
}

Mesh和Geometry

两者都可以rotate, Geometry是”是什么(what)” Mesh是”如何(how)”

Animation

1
2
3
4
5
tick(){
mesh.rotaion.y += 0.05
console.log("tick tack")
requestAnimationFrame(tick)
}

tick 调用频率是帧速率相关的 可以用Date或者THREE.Clock

1
2
3
4
5
6
7
8
9
const clock = new THREE.Clock()
const animate = () => {
const t = clock.getElapsedTime()
group.rotation.y=0.5*Math.PI*t
requestAnimationFrame(animate);
renderer.render(scene, camera);
};

animate();

Issue: 单纯调用gsap无效 需要配合requestAnimationFrame和render方法才能使模型运动生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gsap.to(group.position, {
duration:1,
delay:1,
x:20
})
gsap.to(group.position, {
duration:1,
delay:2,
x:0
})

const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};

animate();

Camera

正交相机

1
2
3
4
5
6
7
8
const camera = new THREE.OrthographicCamera(
-20*container.current.clientWidth/container.current.clientHeight, /*左边界*/
20*container.current.clientWidth/container.current.clientHeight,/*右边界*/
20,/*上边界*/
-20,/*下边界*/
0.1,
1000
)

相机环绕

1
2
3
4
5
6
7
8
9
10
11
12
 // mouse rotate
document.addEventListener('mousemove', (e)=>{
if(!container.current) return
const x = (e.x - container.current.offsetLeft)/container.current.clientWidth - 0.5;
const y = -(e.x - container.current.offsetTop)/container.current.clientHeight + 0.5;
// 圆形轨迹的坐标呈正弦/余弦往复
camera.position.z = Math.cos(x*Math.PI*10)*20
camera.position.x = Math.sin(x*Math.PI*10)*20
// 注意! 相机位置移动时 会从原目标移开 所以重新设置lookAt
camera.lookAt(scene.position)
renderer.render(scene, camera)
})

camera controls

  • OrbitControl
  • DeviceOrientationControls
  • FirstPersionControls
  • FlyControls
  • PointLockControls
  • TrackballControls

debug tool:
dat.gui (out of date) -> lil-gui
control panel
controlkit
Guify
Oui

纹理映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const loader = new THREE.TextureLoader()
const texture = loader.load('./assets/images/cover/cover-6.webp',
(txt)=>{
console.log(txt)
},
()=>{
console.log('loading')
},
(e)=>{
console.error(e)
}
)
// 设置纹理在U轴(水平方向)和V轴(垂直方向)上的重复次数。
texture.repeat.x = 2
texture.repeat.y = 2
// 设置纹理在U轴(S方向)和V轴(T方向)上的包裹模式(wrap mode)。
// THREE.MirroredRepeatWrapping接缝处镜像翻转
texture.wrapS = THREE.MirroredRepeatWrapping
texture.wrapT = THREE.MirroredRepeatWrapping
texture.offset.x = 0.5
// 素材逆时针旋转Π/2
texture.rotation = Math.PI * 0.5
texture.center.x = 1
texture.center.y = 1
// 放大时 线性平滑
texture.magFilter = THREE.LinearFilter
// 缩小时 取相邻两个mip层做线性平滑
texture.minFilter = THREE.LinearMipmapLinearFilter
const cubeMaterial = new THREE.MeshBasicMaterial({
// color: 0xff0000,
// wireframe:true
map: texture
})

const sphereGeometry = new THREE.SphereGeometry(5, 32, 32)
const ball = new THREE.Mesh(sphereGeometry, cubeMaterial)

material properties:

  • transparent true/false
  • opacity 透明度0~1
  • wireframe 显示线框
  • side 材质应用到外侧(THREE.FrontSide)或者内侧(THREE.BackSide)
  • flatshading

matcap

environmentMap and HDRI(High Dynamic Range Imaging 高动态范围成像)

在宏大场景中,计算周围环境在物体上的倒影是巨大的运算负担,于是聪明的图形工作者想到了将环境贴图直接贴在反光物体上的想法

在Three.js中支持将立方体环境的图像映射到物体上 常用HDRI生成立方环境贴图的免费网站:https://polyhaven.com/hdris 原HDRIHeaven

或使用blender工具

3D Text

TextBufferGeometry

facetype.js 转换.ttf字体成json
QQs: 包含中文的字体转换后出现问题,json中以‘字-坐标’作key-value,中文(甚至字母和数字)转换失败在json中显示为方框,于是输入的字无法找到映射。loader导入这样的字体会报 character “xxx” does not exists in font family XXX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';

// text
const fontLoader = new FontLoader()
fontLoader.load('./assets/font/Kristen ITC_Regular.json', (font) => {
const textGeometry = new TextGeometry(
'Hello 3D',
{
font,
size: 2,
depth:0.2, /** 原height属性 */
curveSegments: 12,
bevelEnabled: true, /**斜角 */
bevelThickness: 0.3,
bevelSize: 0.2,
bevelOffset: 0,
bevelSegments: 5
}
)

const textMaterial = new THREE.MeshBasicMaterial({ color: 'blue', wireframe:true })
const textMesh = new THREE.Mesh(textGeometry, textMaterial)
//textMesh.position.set(0,0,0)
scene.add(textMesh)
})

```js

3D Text默认position位于文字起点 移动到中心

```js
textGeometry.translate(
- (textGeometry.boundingBox.x - 0.2)*0.5, /** 减去倒角 */
- (textGeometry.boundingBox.y - 0.2)*0.5,
- (textGeometry.boundingBox.z - 0.3)*0.5
)

阴影

粒子

可视化优化

Three.js常见性能问题和内存泄漏

  • 及时dispose释放资源
  • 减少segment
  • 复用geometry实例
  • 优化requestAnimationFrame 降低帧率和简化回调

webGPU

Three.js 支持webGPU,使用webGPU需要浏览器对该功能的支持 见openGL

对于没有原生支持的浏览器可以通过Polyfill实现

向量点乘

内积 数量积

$\vec{a}$·$\vec{b}$ = $x_1 x_2$ + $y_1y_2$ + $z_1z_2$= |$\vec{a}$||$\vec{b}$|cosθ

标量结果 一个向量在另一个向量方向的投影与被投影向量模的乘积

几何意义 对于单位向量 点乘结果取决于向量方向夹角 反映了方向的一致性

向量叉乘

外积 向量积

名字由来是坐标分量做交叉相乘然后相减的操作

方向垂直与向量所在平面 符合右手法则

几何意义 叉乘结果的正负可以反映方向关系
$\vec{a}$ × $\vec{b}$ > 0 ,$\vec{a}$ 在 $\vec{b}$ 右侧

$\vec{a}$ × $\vec{b}$ < 0 ,$\vec{a}$ 在 $\vec{b}$ 左侧

Godot是C++开发的基于opengl的游戏引擎,MIT开源协议,完全免费。支持一键导出到多个平台,包括主流的桌面平台(Linux、macOS、Windows)、移动平台(Android、iOS)、基于 Web 的平台以及主机平台。

Godot Docs

Token

Token 是大型语言模型处理文本的基本单位。不同模型采用不同的分词方式(分词器),当我们将一段文字输入模型时,模型会先将其拆解成 Token 序列,然后通过这些序列进行预测。

分词器的具体执行原理较为复杂。我们可以简化地将分词器视为一个字典,对于字典中存在的内容,按照字典进行切分并替换为对应的数字
Hello 你好呀 —> Hello | _ | 你好 | 呀 (character: 9, token: 4)

神经网络结构

经典的卷积神经网络(CNN)结构包括以下层:

输入层→卷积层→池化层→全连接层

例如,假设要对0-9十个数字的图片进行分类,则全连接层的输出维度通常要设置为10,与输出分类一一对应。最后通过Softmax层,程序就可以将输出转换为每个类别的概率,概率最高的类别即为预测结果

理解了CNN的输出层维度与分类任务的关系,我们也可以将这个概念迁移到对Transformer语言模型的理解上。尽管二者架构迥异,但是它们在输出层维度及分类任务的原理上仍有共性。

语言模型本质上仍然是一种预测模型,它的核心目标,就是预测给定上下文的下一token。输入层在复杂的网络中传播,最终到达输出层,并且形成 N 个维度的输出。而每个维度正对应着分词表中的一个token。

例如,如果分词表包含50000个token,那么Transformer模型的输出层将输出一个50000维的向量,向量中的每个元素代表对应token的概率。模型会选择概率最高的token作为预测的下一个token。

早期llm的词表(Vocabulary)规模非常小,对应的输出维度也很小,因此相同环境下的理论计算性能更优。但是词表中没有出现的罕见词,有可能被拆分成子词甚至字符。就如ChatGPT 4.0及以前的Vocabulary,大部分中文字符,都会被拆分为2-3个子词token。显然,小规模的Vocabulary有更低的信息密度,相同的输入输出会占据更大的序列长度,这对于输入与输出都有很大的负面影响。

以Llama2-7B模型为例,当词表从32k扩展至64k时,理论的嵌入矩阵参数量从32,000×4096增至65,536×4096,但平均序列长度缩短约30%,实际运行中的整体推理速度提升了15%。

Transformer

Transformer语言模型是基于自注意力机制构建的深度学习架构,已在自然语言处理领域引发革命性变革。
核心架构:

  • 自注意力机制:通过计算序列中每个元素与其他元素的相关性权重(Q/K/V矩阵),捕捉长距离依赖关系。相比RNN/CNN,并行计算效率提升3-8倍(论文7、10)。

  • 多头注意力:将输入切分为多个子空间并行处理,可同时学习语法、语义等不同特征维度(论文3、12)。

  • 位置编码:采用正弦函数生成绝对位置编码(论文1)或相对位置编码(论文18),解决序列顺序建模问题。

它的核心目标,就是预测给定上下文的下一token。

这意味着,Transformer语言模型每输出一个token,输入数据便在神经网络经过了一次完整的传播。最终从词表查询到token string,展示在你眼前的,无论是一个字母,一个单词,甚至一个短语,从模型的角度来看,它们都是“平等的”——从模型角度来看,它们都是一个独立的预测单元,最终对应到词表中的一个 token。

从输入角度上看,正如我们之前提到的,transformer的自注意力模型会将上下文加载到注意力矩阵中,上下文token数直接决定了矩阵的规模,由于O(n²)的时间复杂度,以及O(n²d)的空间复杂度(d为隐藏层维度),小规模词表所带来的低密度信息,可能反而削弱计算性能,增加资源消耗。

从输出角度上看,更小的词表同样意味着单位token包含的信息更少,完成同样的一句输出将会经过更多的推理次数。

机器视觉是人工智能除LLM外的另一个分支

一、传统机器学习模型
支持向量机(SVM)

应用案例:近红外光谱识别(6个特征波长输入,95.3%准确率)

优势:小样本表现优异,适合光谱数据

缺陷:依赖特征工程,需人工提取菌盖颜色、形状等特征

随机森林(RF)

应用案例:15特征(形态+颜色+纹理)分类,全特征建模最优

效果:预测精度超SVM,适合结构化特征数据

二、深度学习模型
基础CNN架构

典型表现:传统CNN在3,100张测试集上首图命中率48.7%

改进方案:降梯度卷积(扩大感受野),命中率提升至81.7%

经典网络改进

ResNet50:迁移学习+图像增强后,测试集平均识别时间0.985秒

EfficientNet:在49,958张自建数据集上,宏F值提升0.75%

MobileNetV2:配合权重衰减策略(YWeight),top-1准确率提升0.87%

细粒度识别专用

双线性CNN:结合Inception-ResNet-v2,在菌盖纹理相似度>85%的数据集上,细粒度分类准确率超95%

Xception模型:迁移学习+多场景数据增强,野外复杂背景识别率达96.67%

轻量化模型

Micro V2:苹果M1 CPU训练142秒达88%准确率,模型体积仅3.5MB

ShuffleNetV2:在8,375张增强数据上,识别速度较VGG16提升3倍

1
npx expo install expo-auth-session expo-crypto # expo-crypto 是 peer dependency 须与 expo-auth-session 一起安装

基于web浏览器的认证

app.json中配置用于重定向携带参数的scheme

1
2
3
4
5
6
7
8
{
"expo": {
"name": "My App",
"slug": "my-app",
"scheme": "myapp", // 这里定义了应用的 scheme
...
}
}

scheme 字段用于定义应用的 URL scheme。URL scheme 是一种协议,允许其他应用程序通过特定的 URL 格式打开你的应用,并传递数据给它。该协议是实现深度链接(deep linking)和通用链接(universal linking)的基础。

深度链接(Deep Linking)和通用链接(Universal Links)是两种用于直接导航到移动应用内特定内容或功能的技术

深度链接是一种允许应用程序内部特定页面或功能被直接访问的链接形式。通过使用自定义URL方案(Custom URL Scheme),开发者可以创建指向应用内任何位置的链接,形如myapp://product/12345

通用链接是苹果公司在iOS 9中引入的一种技术,它使得HTTP/HTTPS链接可以直接打开已安装的应用程序中的相应内容,链接形式与传统网站链接相同,不需要特殊的URL scheme(不依赖是否安装,可避免不同应用程序使用相同的URL scheme的安全问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { useState } from 'react';
import * as WebBrowser from 'expo-web-browser';
import {
exchangeCodeAsync,
makeRedirectUri,
useAuthRequest,
useAutoDiscovery,
} from 'expo-auth-session';
import { Button, Text, SafeAreaView } from 'react-native';


WebBrowser.maybeCompleteAuthSession();


export default function App() {
// Endpoint
const discovery = useAutoDiscovery(
'https://login.microsoftonline.com/<TENANT_ID>/v2.0',
);
const redirectUri = makeRedirectUri({
scheme: 'com.app',
path: 'auth',
});
// 认证成功返回以下uri
// Development Build: my-scheme://redirect
// Expo Go: exp://127.0.0.1:8081/--/redirect
const clientId = '<CLIENT_ID>';

// We store the JWT in here
const [token, setToken] = useState<string | null>(null);

// Request
const [request, , promptAsync] = useAuthRequest(
{
clientId,
scopes: ['openid', 'profile', 'email', 'offline_access'],
redirectUri,
},
discovery,
);

return (
<SafeAreaView>
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync().then((codeResponse) => {
if (request && codeResponse?.type === 'success' && discovery) {
exchangeCodeAsync(
{
clientId,
code: codeResponse.params.code,
extraParams: request.codeVerifier
? { code_verifier: request.codeVerifier }
: undefined,
redirectUri,
},
discovery,
).then((res) => {
setToken(res.accessToken);
});
}
});
}}
/>
<Text>{token}</Text>
</SafeAreaView>
);
}

  • useAutoDiscovery(‘[OpenId issuer URL]’)

objectives orientation:

  • what & what benefits, defect(缺点)
  • 交互interact
  • 隐私和权限
  • 工具链

what is Expo

Expo 是一个基于 React Native 的框架。其服务于简化和便于 React Native 开发方式,Expo框架包括Expo CLI(如 create-expo-app)Expo SDK (其封装了一些导航、设备、通知等功能和api)Expo Go(一个可以在 iOS 和 Android 上安装的应用程序,允许你通过扫描 QR 码或 URL 来即时预览和测试你的 Expo 项目,而不需要构建 APK 或 IPA 文件。)EAS (远程构建 一键推送Google Play和Apple Store)
参考使用Expo开发应用

缺点是对深度定制开发(甚至无法对接支付宝和微信支付功能)和涉及到原生模块的开发缺乏灵活性,需要Expo eject成纯React Native项目,此操作不可逆

安装Expo:

1
2
3
4
5
npx create-expo-app StickerSmash --template blank-typescript
cd StickerSmash

npx expo run:android
npx expo start

使用npx expo install而不是npm i或yarn add,以便提供兼容性提示

Expo路由

file-based router

配置

app.json 存放编译所需静态参数 如app名称、版本信息、OTA配置等
如需动态配置应使用app.config.ts

1
2
3
4
5
6
7
8
import { ExpoConfig, ConfigContext } from 'expo/config';

export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
slug: 'my-app',
name: 'My App',
});

Expo SDK库

EAS

Expo Application Service, 构建安装包需要用eas服务,这是对墙内用户不友好的地方之一

1
2
3
npm i -g eas-cli
eas login
eas build --platform android --profile release

默认打包成abb文件,生成apk需要在eas.json中某个profile中设置好类型如
1
2
3
4
5
"release": {
"android": {
"buildType": "apk"
}
}