设备: M5 PRO 64G内存
模型 Qwen3.5-27B-Claude-4.6-Opus-Distilled-MLX-4bit
基准测试 速度有点慢
先测试下天气卡片效果
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iOS 18 Weather Cards</title>
<style>
:root {
--ios-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
--card-width: 280px;
--card-height: 400px;
}
body {
margin: 0;
padding: 0;
background-color: #000; /* 深色模式背景 */
font-family: var(--ios-font);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow-x: auto; /* 允许水平滚动 */
}
/* 容器:横版排列 */
.container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 24px;
padding: 40px;
width: 100%;
max-width: 1400px;
}
/* 卡片基础样式 */
.card {
width: var(--card-width);
height: var(--card-height);
border-radius: 40px;
position: relative;
perspective: 1000px; /* 为 3D 翻转做准备 */
cursor: pointer;
transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
user-select: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
overflow: hidden;
}
/* 悬停交互 */
.card:hover {
transform: scale(1.05) translateY(-10px);
box-shadow: 0 20px 40px rgba(0,0,0,0.5);
z-index: 10;
}
/* 翻转逻辑 */
.card.flipped .card-inner {
transform: rotateY(180deg);
}
.card-inner {
width: 100%;
height: 100%;
position: relative;
transition: transform 0.6s cubic-bezier(0.4, 0.2, 0.2, 1);
transform-style: preserve-3d;
border-radius: 40px;
}
/* 卡片正面和背面通用 */
.card-content, .card-back {
position: absolute;
width: 100%;
height: 100%;
border-radius: 40px;
backface-visibility: hidden; /* 隐藏背面 */
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 30px;
box-sizing: border-box;
}
/* 卡片正面背景图 */
.card-content {
background-size: cover;
background-position: center;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
/* 卡片背面样式 */
.card-back {
background: rgba(255, 255, 255, 0.95);
color: #333;
transform: rotateY(180deg);
align-items: center;
justify-content: center;
text-align: center;
}
.card-back h3 {
font-size: 24px;
margin-bottom: 20px;
color: #007aff;
}
.card-back p {
font-size: 16px;
line-height: 1.6;
margin: 5px 0;
}
.card-back .desc {
font-size: 14px;
color: #888;
margin-top: 20px;
font-style: italic;
}
/* =========================================
具体天气卡片样式
========================================= */
/* 1. 晴天 (Sunny) */
.sunny-card .card-content {
background: linear-gradient(160deg, #4facfe 0%, #00f2fe 100%);
}
.sun {
width: 100px;
height: 100px;
margin: 0 auto 20px;
position: relative;
}
.sun-core {
width: 60px;
height: 60px;
background: #ffeb3b;
border-radius: 50%;
position: absolute;
top: 20px; left: 20px;
box-shadow: 0 0 40px #ffeb3b;
}
.sun-ray {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
border-radius: 50%;
border: 2px dashed rgba(255, 235, 59, 0.4);
animation: spin 10s linear infinite;
}
/* 2. 大风 (Windy) */
.windy-card .card-content {
background: linear-gradient(160deg, #4b6cb7 0%, #182848 100%);
}
.cloud-wind {
width: 140px;
height: 80px;
margin: 0 auto 20px;
position: relative;
}
.cloud {
width: 100px;
height: 40px;
background: #ddd;
border-radius: 50px;
position: absolute;
top: 20px;
animation: float 3s ease-in-out infinite;
}
.cloud::after {
content: '';
position: absolute;
width: 50px; height: 50px;
background: #ddd;
border-radius: 50%;
top: -25px; left: 20px;
}
.wind-line {
position: absolute;
height: 4px;
background: rgba(255,255,255,0.6);
border-radius: 2px;
top: 50px;
animation: wind 2s ease-in-out infinite;
}
.w1 { width: 40px; left: 80px; animation-delay: 0s; }
.w2 { width: 60px; left: 90px; animation-delay: 0.5s; top: 60px;}
.w3 { width: 30px; left: 70px; animation-delay: 1s; top: 40px;}
/* 3. 暴雨 (Heavy Rain) */
.rainy-card .card-content {
background: linear-gradient(160deg, #203a43 0%, #2c5364 100%);
}
.heavy-rain {
width: 140px;
height: 100px;
margin: 0 auto 20px;
position: relative;
}
.rain-cloud {
width: 100px; height: 40px;
background: #556;
border-radius: 50px;
position: absolute; top: 10px;
}
.rain-cloud::after { content: ''; position: absolute; width: 50px; height: 50px; background: #556; border-radius: 50%; top: -25px; left: 20px; }
.rain-drop {
width: 2px; height: 10px;
background: #89f;
position: absolute;
opacity: 0.7;
animation: rain 1s linear infinite;
}
.r1 { top: 50px; left: 20px; animation-delay: 0s; }
.r2 { top: 50px; left: 40px; animation-delay: 0.2s; height: 15px;}
.r3 { top: 60px; left: 60px; animation-delay: 0.5s; }
.r4 { top: 50px; left: 80px; animation-delay: 0.8s; height: 12px;}
/* 4. 暴雪 (Blizzard) */
.snowy-card .card-content {
background: linear-gradient(160deg, #83a4d4 0%, #b6fbff 100%);
}
.blizzard {
width: 140px;
height: 100px;
margin: 0 auto 20px;
position: relative;
}
.snow-cloud {
width: 100px; height: 40px;
background: #fff;
border-radius: 50px;
position: absolute; top: 10px;
}
.snow-cloud::after { content: ''; position: absolute; width: 50px; height: 50px; background: #fff; border-radius: 50%; top: -25px; left: 20px; }
.snow-flake {
width: 6px; height: 6px;
background: #333;
border-radius: 50%;
position: absolute;
opacity: 0.6;
animation: snow 2s linear infinite;
}
.s1 { top: 50px; left: 20px; animation-delay: 0s; }
.s2 { top: 60px; left: 50px; animation-delay: 0.5s; width: 8px; height: 8px;}
.s3 { top: 40px; left: 80px; animation-delay: 1s; }
/* =========================================
通用动画 Keyframes
========================================= */
@keyframes spin { 100% { transform: rotate(360deg); } }
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
@keyframes wind {
0% { transform: translateX(0); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateX(-40px); opacity: 0; }
}
@keyframes rain {
0% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(60px); opacity: 0; }
}
@keyframes snow {
0% { transform: translateY(0) rotate(0deg); opacity: 1; }
100% { transform: translateY(60px) rotate(180deg); opacity: 0; }
}
/* 文字排版 */
h2 { font-size: 80px; margin: 0; font-weight: 200; line-height: 1; }
p { font-size: 20px; margin: 5px 0 0; font-weight: 500; }
.location { font-size: 16px; opacity: 0.9; margin-top: auto; padding-top: 20px; display: flex; align-items: center; gap: 5px;}
/* 响应式调整 */
@media (max-width: 768px) {
.card { width: 80vw; height: 300px; }
h2 { font-size: 60px; }
}
</style>
</head>
<body>
<div class="container">
<!-- 1. 晴天 (Sunny) -->
<div class="card sunny-card" onclick="flipCard(this)">
<div class="card-content">
<div class="weather-icon sun">
<div class="sun-core"></div>
<div class="sun-ray"></div>
</div>
<h2>26°</h2>
<p>晴朗</p>
<div class="location">北京</div>
</div>
<div class="card-back">
<h3>详细预报</h3>
<p>紫外线指数:强</p>
<p>空气质量:优</p>
<p class="desc">适合户外活动和晾晒衣物。</p>
</div>
</div>
<!-- 2. 大风 (Windy) -->
<div class="card windy-card" onclick="flipCard(this)">
<div class="card-content">
<div class="weather-icon cloud-wind">
<div class="cloud"></div>
<div class="wind-line w1"></div>
<div class="wind-line w2"></div>
<div class="wind-line w3"></div>
</div>
<h2>18°</h2>
<p>大风</p>
<div class="location">呼和浩特</div>
</div>
<div class="card-back">
<h3>详细预报</h3>
<p>风力:6-7 级</p>
<p>风向:西北风</p>
<p class="desc">请注意防风,远离临时搭建物。</p>
</div>
</div>
<!-- 3. 暴雨 (Heavy Rain) -->
<div class="card rainy-card" onclick="flipCard(this)">
<div class="card-content">
<div class="weather-icon heavy-rain">
<div class="cloud rain-cloud"></div>
<div class="rain-drop r1"></div>
<div class="rain-drop r2"></div>
<div class="rain-drop r3"></div>
<div class="rain-drop r4"></div>
</div>
<h2>15°</h2>
<p>暴雨</p>
<div class="location">广州</div>
</div>
<div class="card-back">
<h3>详细预报</h3>
<p>降雨量:大</p>
<p>湿度:95%</p>
<p class="desc">建议减少外出,注意交通安全。</p>
</div>
</div>
<!-- 4. 暴雪 (Blizzard) -->
<div class="card snowy-card" onclick="flipCard(this)">
<div class="card-content">
<div class="weather-icon blizzard">
<div class="cloud snow-cloud"></div>
<div class="snow-flake s1"></div>
<div class="snow-flake s2"></div>
<div class="snow-flake s3"></div>
<div class="snow-flake s4"></div>
<div class="wind-line snow-wind"></div>
</div>
<h2>-5°</h2>
<p>暴雪</p>
<div class="location">哈尔滨</div>
</div>
<div class="card-back">
<h3>详细预报</h3>
<p>能见度:低</p>
<p>路面:结冰</p>
<p class="desc">极端天气,请尽量留在室内。</p>
</div>
</div>
</div>
<script>
// 交互逻辑
function flipCard(card) {
// 只有当卡片没有被锁定(正在翻转中)或者我们可以简单地切换 class
card.classList.toggle('flipped');
}
</script>
</style>
</body>
</html>
CASE2 莫比乌斯环
一次生成的效果好像不太好
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D 莫比乌斯环</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
font-family: 'Segoe UI', sans-serif;
}
canvas { display: block; }
.controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(255,255,255,0.1);
padding: 15px 25px;
border-radius: 25px;
backdrop-filter: blur(10px);
display: flex;
gap: 20px;
align-items: center;
}
.controls label { color: #fff; font-size: 14px; }
input[type="range"] {
width: 120px;
accent-color: #00d9ff;
}
.info {
position: absolute;
top: 20px;
left: 20px;
color: #fff;
background: rgba(0,0,0,0.3);
padding: 15px;
border-radius: 8px;
}
.info h1 { font-size: 18px; margin-bottom: 5px; color: #00d9ff; }
.info p { font-size: 12px; opacity: 0.8; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div class="info">
<h1>莫比乌斯环 Möbius Strip</h1>
<p>🖱️ 拖动旋转 | 💫 单侧曲面</p>
</div>
<div class="controls">
<label>环半径:<span id="radiusVal">3</span></label>
<input type="range" id="radius" min="1.5" max="5" step="0.1" value="3">
<label>环宽度:<span id="widthVal">1</span></label>
<input type="range" id="width" min="0.3" max="2" step="0.1" value="1">
</div>
<script>
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
if (!gl) {
alert('您的浏览器不支持 WebGL');
throw new Error('WebGL not supported');
}
// 调整画布大小
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();
// 顶点着色器
const vsSource = `
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
uniform mat4 uProjection;
uniform mat4 uView;
uniform mat4 uModel;
varying vec3 vNormal;
varying vec2 vUV;
varying vec3 vWorldPos;
void main() {
vec4 worldPos = uModel * vec4(position, 1.0);
vWorldPos = worldPos.xyz;
vNormal = mat3(uModel) * normal;
vUV = uv;
gl_Position = uProjection * uView * worldPos;
}
`;
// 片元着色器
const fsSource = `
precision mediump float;
varying vec3 vNormal;
varying vec2 vUV;
varying vec3 vWorldPos;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
uniform vec3 uColor4;
void main() {
vec3 normal = normalize(vNormal);
// 基于 UV 的渐变着色
float v = vUV.y;
vec3 color;
if (v < 0.25) {
color = mix(uColor1, uColor2, v * 4.0);
} else if (v < 0.5) {
color = mix(uColor2, uColor3, (v - 0.25) * 4.0);
} else if (v < 0.75) {
color = mix(uColor3, uColor4, (v - 0.5) * 4.0);
} else {
color = mix(uColor4, uColor1, (v - 0.75) * 4.0);
}
// 简单光照
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(dot(normal, lightDir), 0.3);
gl_FragColor = vec4(color * diff, 1.0);
}
`;
// 编译着色器
function compileShader(source, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vs = compileShader(vsSource, gl.VERTEX_SHADER);
const fs = compileShader(fsSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
gl.useProgram(program);
// 获取位置
const positionsLoc = gl.getAttribLocation(program, 'position');
const normalsLoc = gl.getAttribLocation(program, 'normal');
const uvLoc = gl.getAttribLocation(program, 'uv');
const projectionLoc = gl.getUniformLocation(program, 'uProjection');
const viewLoc = gl.getUniformLocation(program, 'uView');
const modelLoc = gl.getUniformLocation(program, 'uModel');
const color1Loc = gl.getUniformLocation(program, 'uColor1');
const color2Loc = gl.getUniformLocation(program, 'uColor2');
const color3Loc = gl.getUniformLocation(program, 'uColor3');
const color4Loc = gl.getUniformLocation(program, 'uColor4');
// 矩阵工具函数
function perspective(fov, aspect, near, far) {
const f = 1.0 / Math.tan(fov / 2);
const nf = 1 / (near - far);
return new Float32Array([
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (far + near) * nf, -1,
0, 0, 2 * far * near * nf, 0
]);
}
function lookAt(eye, center, up) {
const z = normalize(sub(eye, center));
const x = normalize(cross(up, z));
const y = cross(z, x);
return new Float32Array([
x[0], y[0], z[0], 0,
x[1], y[1], z[1], 0,
x[2], y[2], z[2], 0,
-dot(x, eye), -dot(y, eye), -dot(z, eye), 1
]);
}
function multiply(a, b) {
const result = new Float32Array(16);
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
result[i * 4 + j] =
a[i * 4 + 0] * b[0 * 4 + j] +
a[i * 4 + 1] * b[1 * 4 + j] +
a[i * 4 + 2] * b[2 * 4 + j] +
a[i * 4 + 3] * b[3 * 4 + j];
}
}
return result;
}
function rotateX(angle) {
const c = Math.cos(angle), s = Math.sin(angle);
return new Float32Array([1,0,0,0, 0,c,-s,0, 0,s,c,0, 0,0,0,1]);
}
function rotateY(angle) {
const c = Math.cos(angle), s = Math.sin(angle);
return new Float32Array([c,0,s,0, 0,1,0,0, -s,0,c,0, 0,0,0,1]);
}
function normalize(v) {
const len = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
return [v[0]/len, v[1]/len, v[2]/len];
}
function sub(a, b) { return [a[0]-b[0], a[1]-b[1], a[2]-b[2]]; }
function cross(a, b) {
return [
a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0]
];
}
function dot(a, b) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
// 生成莫比乌斯环几何体
function generateMobius(radius, width, segments, res) {
const positions = [];
const normals = [];
const uvs = [];
const indices = [];
for (let i = 0; i <= segments; i++) {
const u = (i / segments) * Math.PI * 2;
const cosU2 = Math.cos(u / 2);
const sinU2 = Math.sin(u / 2);
for (let j = 0; j <= res; j++) {
const v = (j / res) * 2 - 1;
// 莫比乌斯环参数方程
const x = (radius + v * width * cosU2) * Math.cos(u);
const y = (radius + v * width * cosU2) * Math.sin(u);
const z = v * width * sinU2;
positions.push(x, y, z);
uvs.push(u / Math.PI / 2, j / res);
}
}
// 计算法线
const pos = new Float32Array(positions);
for (let i = 0; i <= segments; i++) {
for (let j = 0; j <= res; j++) {
const idx = (i * (res + 1) + j) * 3;
// 计算切线
let tx = 0, ty = 0, tz = 0, bx = 0, by = 0, bz = 0;
if (i > 0 && i < segments) {
const prev = ((i-1) * (res + 1) + j) * 3;
const next = ((i+1) * (res + 1) + j) * 3;
tx = pos[next] - pos[prev];
ty = pos[next+1] - pos[prev+1];
tz = pos[next+2] - pos[prev+2];
} else {
const prev = ((i - 1 + segments) % segments * (res + 1) + j) * 3;
const next = ((i + 1) % segments * (res + 1) + j) * 3;
tx = pos[next] - pos[prev];
ty = pos[next+1] - pos[prev+1];
tz = pos[next+2] - pos[prev+2];
}
if (j > 0 && j < res) {
const prev = (i * (res + 1) + j - 1) * 3;
const next = (i * (res + 1) + j + 1) * 3;
bx = pos[next] - pos[prev];
by = pos[next+1] - pos[prev+1];
bz = pos[next+2] - pos[prev+2];
}
// 叉积得到法线
const nx = ty * bz - tz * by;
const ny = tz * bx - tx * bz;
const nz = tx * by - ty * bx;
const len = Math.sqrt(nx*nx + ny*ny + nz*nz);
normals.push(nx/len, ny/len, nz/len);
}
}
// 生成索引
for (let i = 0; i < segments; i++) {
for (let j = 0; j < res; j++) {
const a = i * (res + 1) + j;
const b = (i + 1) * (res + 1) + j;
const c = (i + 1) * (res + 1) + j + 1;
const d = i * (res + 1) + j + 1;
indices.push(a, b, d);
indices.push(b, c, d);
}
}
return { positions: new Float32Array(positions), normals: new Float32Array(normals),
uvs: new Float32Array(uvs), indices: new Uint16Array(indices) };
}
// 创建缓冲区
let geometry = null;
let positionBuffer, normalBuffer, uvBuffer, indexBuffer;
let numIndices = 0;
function updateGeometry(radius, width) {
const segs = 80, res = 20;
geometry = generateMobius(radius, width, segs, res);
numIndices = geometry.indices.length;
// 位置缓冲
positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, geometry.positions, gl.STATIC_DRAW);
// 法线缓冲
normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, geometry.normals, gl.STATIC_DRAW);
// UV 缓冲
uvBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, geometry.uvs, gl.STATIC_DRAW);
// 索引缓冲
indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.indices, gl.STATIC_DRAW);
}
let radius = 3, width = 1;
updateGeometry(radius, width);
// 设置颜色
gl.uniform3f(color1Loc, 1, 0, 0.2); // 红
gl.uniform3f(color2Loc, 1, 0.5, 0); // 橙
gl.uniform3f(color3Loc, 0, 1, 0.8); // 青
gl.uniform3f(color4Loc, 0, 0.5, 1); // 蓝
// 动画参数
let rotX = 0.3, rotY = 0;
let autoRotate = true;
let targetRotX = rotX, targetRotY = rotY;
// 鼠标交互
let isDragging = false, lastX = 0, lastY = 0;
canvas.addEventListener('mousedown', e => {
isDragging = true;
autoRotate = false;
lastX = e.clientX;
lastY = e.clientY;
});
canvas.addEventListener('mousemove', e => {
if (isDragging) {
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
targetRotY += dx * 0.01;
targetRotX += dy * 0.01;
lastX = e.clientX;
lastY = e.clientY;
}
});
canvas.addEventListener('mouseup', () => isDragging = false);
canvas.addEventListener('mouseleave', () => isDragging = false);
// 触摸支持
canvas.addEventListener('touchstart', e => {
isDragging = true;
autoRotate = false;
lastX = e.touches[0].clientX;
lastY = e.touches[0].clientY;
});
canvas.addEventListener('touchmove', e => {
if (isDragging) {
const dx = e.touches[0].clientX - lastX;
const dy = e.touches[0].clientY - lastY;
targetRotY += dx * 0.01;
targetRotX += dy * 0.01;
lastX = e.touches[0].clientX;
lastY = e.touches[0].clientY;
}
});
// 控件
document.getElementById('radius').addEventListener('input', e => {
radius = parseFloat(e.target.value);
document.getElementById('radiusVal').textContent = radius.toFixed(1);
updateGeometry(radius, width);
});
document.getElementById('width').addEventListener('input', e => {
width = parseFloat(e.target.value);
document.getElementById('widthVal').textContent = width.toFixed(1);
updateGeometry(radius, width);
});
// 渲染循环
function render() {
// 自动旋转
if (autoRotate) {
targetRotY += 0.005;
}
// 平滑插值
rotX += (targetRotX - rotX) * 0.1;
rotY += (targetRotY - rotY) * 0.1;
// 清空画布
gl.clearColor(0.1, 0.1, 0.15, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
// 投影矩阵
const aspect = canvas.width / canvas.height;
const projection = perspective(Math.PI / 4, aspect, 0.1, 100);
// 视图矩阵
const view = lookAt([0, 0, 8], [0, 0, 0], [0, 1, 0]);
// 模型矩阵
const model = multiply(rotateX(rotX), rotateY(rotY));
gl.uniformMatrix4fv(projectionLoc, false, projection);
gl.uniformMatrix4fv(viewLoc, false, view);
gl.uniformMatrix4fv(modelLoc, false, model);
// 绑定属性
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionsLoc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionsLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.vertexAttribPointer(normalsLoc, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(normalsLoc);
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(uvLoc);
// 绘制
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.drawElements(gl.TRIANGLES, numIndices, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
render();
</script>
</body>
</html>
后面再慢慢补充测试内容



