<!-- 鼠标拖拽查看3D效果 -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.164.1/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.164.1/examples/jsm/"
}
}
</script>
HTML
格式化
支持Emmet,输入 p 后按 Tab键试试吧!
<head> ... </head>
<body>
</body>
CSS
格式化
body {
overflow: hidden;
margin: 0;
}
JS
格式化
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
// console.clear();
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(30, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 15);
let renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", (event) => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
let light = new THREE.DirectionalLight(0xffffff, Math.PI);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, Math.PI * 0.5));
// atlas
let atlasSize = new THREE.Vector2(4, 4);
let atlas = ((dim) => {
let c = document.createElement("canvas");
let tileSize = 256;
c.width = tileSize * dim.x;
c.height = tileSize * dim.y;
let u = (val) => tileSize * 0.01 * val;
let ctx = c.getContext("2d");
// debug
ctx.fillStyle = "rgba(255, 255, 255, 1)";
ctx.fillRect(0, 0, c.width, c.height);
//ctx.clearRect(0, 0, c.width, c.height);
for (let y = 0; y < dim.y; y++) {
for (let x = 0; x < dim.x; x++) {
generateSilly(x, y);
}
}
let tex = new THREE.CanvasTexture(c);
tex.colorSpace = "srgb";
tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
return tex;
function generateSilly(x, y) {
ctx.save();
ctx.translate((x + 0.5) * tileSize, (y + 0.5) * tileSize);
// eyes
ctx.lineWidth = u(5);
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(127, 127, 127, 1)";
drawEyes(25, -25, 15);
drawNose();
drawMouth();
ctx.restore();
}
function drawMouth() {
let p1 = [-25, Math.random() * 25];
let p2 = [-10 + Math.random() * 20, Math.random() * 25];
let p3 = [25, Math.random() * 25];
ctx.beginPath();
let yShift = 20;
ctx.moveTo(u(p1[0]), u(yShift + p1[1]));
ctx.quadraticCurveTo(
u(p2[0]),
u(yShift + p2[1]),
u(p3[0]),
u(yShift + p3[1])
);
ctx.stroke();
}
function drawNose() {
ctx.beginPath();
let arcStart = Math.random() * Math.PI * 2;
let arcEnd = arcStart + (Math.random() * 0.75 + 0.25) * Math.PI * 2;
ctx.arc(0, 0, u(Math.random() * 10 + 5), arcStart, arcEnd);
ctx.stroke();
}
function drawEyes(x, y, radius) {
let eyeSymmX = Math.sign(Math.random() - 0.5);
let eyeSymmY = Math.sign(Math.random() - 0.5);
//left
ctx.fillStyle = "rgba(255, 255, 255, 1)";
ctx.beginPath();
ctx.arc(-u(x), u(y), u(radius), 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// pupil
let dir = [Math.random() - 0.5, Math.random() - 0.5];
let dirL = Math.hypot(dir[0], dir[1]);
let dirN = [dir[0] / dirL, dir[1] / dirL];
let pupilShift = Math.random() * 5;
let finalDir = { x: dirN[0] * pupilShift, y: dirN[1] * pupilShift };
let pupilR = 7;
let pupilColor = `hsla(${Math.random() * 360}, 100%, 25%, 1)`;
//console.log(finalDir);
ctx.fillStyle = pupilColor;
ctx.beginPath();
ctx.arc(-u(x + finalDir.x), u(y + finalDir.y), u(pupilR), 0, Math.PI * 2);
ctx.fill();
// right
ctx.fillStyle = "rgba(255, 255, 255, 1)";
ctx.beginPath();
ctx.arc(u(x), u(y), u(radius), 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// pupil
ctx.fillStyle = pupilColor;
ctx.beginPath();
ctx.arc(
u(x + finalDir.x * eyeSymmX),
u(y + finalDir.y * eyeSymmY),
u(pupilR),
0,
Math.PI * 2
);
ctx.fill();
}
})(atlasSize);
for(let y = 0; y < atlasSize.y; y++){
for(let x = 0; x < atlasSize.x; x++){
let testObj = new THREE.Mesh(
new THREE.SphereGeometry(0.9, 64, 32),
new THREE.MeshLambertMaterial({
color: new THREE.Color().setHSL(Math.random(), 0.75, 0.75),
map: atlas,
onBeforeCompile: shader => {
shader.uniforms.atlasSize = {value: atlasSize};
shader.uniforms.tile = {value: new THREE.Vector2(x, y)}
shader.fragmentShader = `
uniform vec2 atlasSize;
uniform vec2 tile;
${shader.fragmentShader}
`.replace(
`#include <map_fragment>`,
`
vec2 mUV = vMapUv;
vec2 centerUV = ((mUV - 0.5) * vec2(2., 1.) + vec2(0.5, 0.)) * PI;
mUV = centerUV + 0.5;
vec2 atlasTile = 1. / atlasSize;
mUV = clamp((mUV + tile) * atlasTile, vec2(0.), vec2(1.));
#ifdef USE_MAP
vec4 sampledDiffuseColor = texture2D( map, mUV );
#ifdef DECODE_VIDEO_TEXTURE
// use inline sRGB decode until browsers properly support SRGB8_ALPHA8 with video textures (#26516)
sampledDiffuseColor = vec4(
mix(
pow(
sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 )
),
sampledDiffuseColor.rgb * 0.0773993808,
vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) )
)
, sampledDiffuseColor.w
);
#endif
vec2 absUV = abs(centerUV);
diffuseColor *= mix(sampledDiffuseColor, vec4(1), step(0.5, max(absUV.x, absUV.y)));
#endif
`
);
//console.log(shader.fragmentShader)
}
})
);
testObj.position.set(
(-(atlasSize.x - 1) * 0.5 + x) * 2,
(-(atlasSize.y - 1) * 0.5 + y) * 2,
0
);
scene.add(testObj);
}
}
let clock = new THREE.Clock();
let t = 0;
renderer.setAnimationLoop(() => {
let dt = clock.getDelta();
t += dt;
controls.update();
renderer.render(scene, camera);
});