삼각형 896개2.00 × 0.76 × 2.00 m
코드에서 사용하기
에디터에서 리믹스grass-tile.js · 335줄
독립형 ES 모듈이에요 — three에만 의존해요. 팩토리 함수를 호출하고 그룹을 씬에 추가하세요.
/**
* Grass Tile — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 896 · Size (W × H × D): 2.00 × 0.76 × 2.00 m
*
* Usage:
* import { createGrassTile } from './grass-tile.js';
*
* const model = createGrassTile();
* model.position.set(0, 0, 0);
* scene.add(model);
*/
import * as THREE from 'three';
const deg = THREE.MathUtils.degToRad;
// Deterministic vertex displacement (organic low-poly look).
function jitter(geometry, amount, seed) {
const pos = geometry.attributes.position;
const h = (a, b, c, s) => {
let x = Math.imul(a, 73856093) ^ Math.imul(b, 19349663) ^ Math.imul(c, 83492791) ^ Math.imul(s, 2654435761);
x = Math.imul(x ^ (x >>> 16), 2246822507);
x = Math.imul(x ^ (x >>> 13), 3266489909);
return ((x ^ (x >>> 16)) >>> 0) / 4294967295 - 0.5;
};
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i), y = pos.getY(i), z = pos.getZ(i);
const qx = Math.round(x * 1000), qy = Math.round(y * 1000), qz = Math.round(z * 1000);
pos.setXYZ(i, x + h(qx, qy, qz, seed) * 2 * amount, y + h(qx, qy, qz, seed + 1) * 2 * amount, z + h(qx, qy, qz, seed + 2) * 2 * amount);
}
pos.needsUpdate = true;
geometry.computeVertexNormals();
return geometry;
}
// Extrude a 2D outline along Z (centered). bevel = [thickness, size, segments].
function extrude(outline, depth, holes = [], bevel = null) {
const shape = new THREE.Shape(outline.map(([x, y]) => new THREE.Vector2(x, y)));
for (const hole of holes) shape.holes.push(new THREE.Path(hole.map(([x, y]) => new THREE.Vector2(x, y))));
const options = bevel
? { depth, bevelEnabled: true, bevelThickness: bevel[0], bevelSize: bevel[1], bevelSegments: bevel[2] }
: { depth, bevelEnabled: false };
return new THREE.ExtrudeGeometry(shape, options).translate(0, 0, -depth / 2);
}
export function createGrassTile() {
const root = new THREE.Group();
root.name = 'GrassTile';
// Materials
const grassMat = new THREE.MeshStandardMaterial({ name: 'grass', color: 0x6aae45, roughness: 0.9, flatShading: true });
const grassLightMat = new THREE.MeshStandardMaterial({ name: 'grassLight', color: 0x72ba59, roughness: 0.9, flatShading: true });
const grassDarkMat = new THREE.MeshStandardMaterial({ name: 'grassDark', color: 0x5b8836, roughness: 0.9, flatShading: true });
const dirtMat = new THREE.MeshStandardMaterial({ name: 'dirt', color: 0x8b5d3b, roughness: 0.95, flatShading: true });
const dirtDarkMat = new THREE.MeshStandardMaterial({ name: 'dirtDark', color: 0x6a4630, roughness: 0.95, flatShading: true });
const stoneMat = new THREE.MeshStandardMaterial({ name: 'stone', color: 0x9a958c, roughness: 0.9, flatShading: true });
const petalMat = new THREE.MeshStandardMaterial({ name: 'petal', color: 0xfff3c4, roughness: 0.7, flatShading: true });
const petalPinkMat = new THREE.MeshStandardMaterial({ name: 'petalPink', color: 0xf58fb0, roughness: 0.7, flatShading: true });
// Parts
const grassTop = new THREE.Mesh(new THREE.BoxGeometry(2, 0.12, 2), grassMat);
grassTop.name = 'grassTop';
grassTop.position.set(0, 0.44, 0);
root.add(grassTop);
const dirt = new THREE.Mesh(new THREE.BoxGeometry(1.92, 0.209, 1.92), dirtMat);
dirt.name = 'dirt';
dirt.position.set(0, 0.2755, 0);
root.add(dirt);
const dirtDeep = new THREE.Mesh(new THREE.BoxGeometry(1.92, 0.171, 1.92), dirtDarkMat);
dirtDeep.name = 'dirtDeep';
dirtDeep.position.set(0, 0.0855, 0);
root.add(dirtDeep);
const wallStones = new THREE.Group();
wallStones.name = 'wallStones';
root.add(wallStones);
const stone1 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0997, 0), 0.0149, 40), dirtDarkMat);
stone1.name = 'stone1';
stone1.position.set(-0.5161, 0.2514, 0.952);
stone1.scale.set(1.25, 0.8, 0.42);
wallStones.add(stone1);
const stone2 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0804, 0), 0.0121, 41), stoneMat);
stone2.name = 'stone2';
stone2.position.set(0.3268, 0.1189, 0.9536);
stone2.scale.set(1.25, 0.8, 0.42);
wallStones.add(stone2);
const stone3 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.084, 0), 0.0126, 42), stoneMat);
stone3.name = 'stone3';
stone3.position.set(-0.4879, 0.2323, -0.9533);
stone3.scale.set(1.25, 0.8, 0.42);
wallStones.add(stone3);
const stone4 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.098, 0), 0.0147, 43), dirtDarkMat);
stone4.name = 'stone4';
stone4.position.set(0.3557, 0.2278, -0.9522);
stone4.scale.set(1.25, 0.8, 0.42);
wallStones.add(stone4);
const stone5 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0796, 0), 0.0119, 44), stoneMat);
stone5.name = 'stone5';
stone5.position.set(0.9536, 0.1065, -0.3716);
stone5.scale.set(0.42, 0.8, 1.25);
wallStones.add(stone5);
const stone6 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0747, 0), 0.0112, 45), stoneMat);
stone6.name = 'stone6';
stone6.position.set(0.954, 0.1727, 0.39);
stone6.scale.set(0.42, 0.8, 1.25);
wallStones.add(stone6);
const stone7 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.099, 0), 0.0149, 46), dirtDarkMat);
stone7.name = 'stone7';
stone7.position.set(-0.9521, 0.1584, -0.4864);
stone7.scale.set(0.42, 0.8, 1.25);
wallStones.add(stone7);
const stone8 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0724, 0), 0.0109, 47), stoneMat);
stone8.name = 'stone8';
stone8.position.set(-0.9542, 0.1847, 0.353);
stone8.scale.set(0.42, 0.8, 1.25);
wallStones.add(stone8);
const fringe = new THREE.Group();
fringe.name = 'fringe';
root.add(fringe);
const box14 = new THREE.Mesh(new THREE.BoxGeometry(0.2035, 0.1149, 0.04), grassMat);
box14.position.set(-0.7826, 0.3425, 0.976);
fringe.add(box14);
const box15 = new THREE.Mesh(new THREE.BoxGeometry(0.1765, 0.0855, 0.04), grassMat);
box15.position.set(-0.443, 0.3572, 0.976);
fringe.add(box15);
const box16 = new THREE.Mesh(new THREE.BoxGeometry(0.2104, 0.1628, 0.04), grassMat);
box16.position.set(-0.1877, 0.3186, 0.976);
fringe.add(box16);
const box17 = new THREE.Mesh(new THREE.BoxGeometry(0.231, 0.174, 0.04), grassMat);
box17.position.set(0.1736, 0.313, 0.976);
fringe.add(box17);
const box18 = new THREE.Mesh(new THREE.BoxGeometry(0.202, 0.147, 0.04), grassMat);
box18.position.set(0.4436, 0.3265, 0.976);
fringe.add(box18);
const box19 = new THREE.Mesh(new THREE.BoxGeometry(0.1743, 0.152, 0.04), grassMat);
box19.position.set(0.781, 0.324, 0.976);
fringe.add(box19);
const box20 = new THREE.Mesh(new THREE.BoxGeometry(0.2031, 0.1751, 0.04), grassMat);
box20.position.set(-0.7844, 0.3124, -0.976);
fringe.add(box20);
const box21 = new THREE.Mesh(new THREE.BoxGeometry(0.1774, 0.1158, 0.04), grassMat);
box21.position.set(-0.4439, 0.3421, -0.976);
fringe.add(box21);
const box22 = new THREE.Mesh(new THREE.BoxGeometry(0.2321, 0.1043, 0.04), grassMat);
box22.position.set(-0.1278, 0.3479, -0.976);
fringe.add(box22);
const box23 = new THREE.Mesh(new THREE.BoxGeometry(0.2058, 0.1143, 0.04), grassMat);
box23.position.set(0.1863, 0.3429, -0.976);
fringe.add(box23);
const box24 = new THREE.Mesh(new THREE.BoxGeometry(0.2067, 0.153, 0.04), grassMat);
box24.position.set(0.4558, 0.3235, -0.976);
fringe.add(box24);
const box25 = new THREE.Mesh(new THREE.BoxGeometry(0.1767, 0.1211, 0.04), grassMat);
box25.position.set(0.7831, 0.3394, -0.976);
fringe.add(box25);
const box26 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1332, 0.2277), grassMat);
box26.position.set(0.976, 0.3334, -0.7713);
fringe.add(box26);
const box27 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1603, 0.208), grassMat);
box27.position.set(0.976, 0.3198, -0.1356);
fringe.add(box27);
const box28 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1257, 0.1676), grassMat);
box28.position.set(0.976, 0.3371, 0.1689);
fringe.add(box28);
const box29 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.0836, 0.1534), grassMat);
box29.position.set(0.976, 0.3582, 0.4582);
fringe.add(box29);
const box30 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.1461, 0.2218), grassMat);
box30.position.set(-0.976, 0.327, -0.4982);
fringe.add(box30);
const box31 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.147, 0.2213), grassMat);
box31.position.set(-0.976, 0.3265, 0.1472);
fringe.add(box31);
const box32 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.0868, 0.1733), grassMat);
box32.position.set(-0.976, 0.3566, 0.801);
fringe.add(box32);
const dressing = new THREE.Group();
dressing.name = 'dressing';
root.add(dressing);
const patch1 = new THREE.Mesh(extrude([[0.2699, 0.2022], [0.1197, -0.0441], [-0.0914, -0.0588], [-0.2827, 0.0607], [-0.2908, 0.3137], [-0.1093, 0.4924], [0.2452, 0.4421]], 0.01), grassDarkMat);
patch1.name = 'patch1';
patch1.position.set(0, 0.505, 0);
patch1.rotation.set(deg(-90), 0, 0);
dressing.add(patch1);
const patch2 = new THREE.Mesh(extrude([[-0.0492, 0.1285], [-0.1696, -0.0828], [-0.3953, -0.1221], [-0.5793, -0.0887], [-0.538, 0.2105], [-0.3324, 0.3297], [-0.1519, 0.2815]], 0.01), grassLightMat);
patch2.name = 'patch2';
patch2.position.set(0, 0.505, 0);
patch2.rotation.set(deg(-90), 0, 0);
dressing.add(patch2);
const patch3 = new THREE.Mesh(extrude([[0.891, 0.4463], [0.7064, 0.2439], [0.4918, 0.2463], [0.2658, 0.347], [0.2862, 0.5391], [0.4859, 0.6334], [0.7019, 0.6226]], 0.01), grassDarkMat);
patch3.name = 'patch3';
patch3.position.set(0, 0.505, 0);
patch3.rotation.set(deg(-90), 0, 0);
dressing.add(patch3);
const tuft1 = new THREE.Group();
tuft1.name = 'tuft1';
tuft1.position.set(-0.4418, 0.5, -0.5656);
dressing.add(tuft1);
const cone38 = new THREE.Mesh(new THREE.ConeGeometry(0.0513, 0.2765, 3), grassDarkMat);
cone38.position.set(0.0428, 0.1183, 0.0172);
cone38.rotation.set(deg(156.3576), deg(70.0612), deg(-151.021));
cone38.scale.set(1, 1, 0.45);
tuft1.add(cone38);
const cone39 = new THREE.Mesh(new THREE.ConeGeometry(0.0513, 0.2698, 3), grassMat);
cone39.position.set(-0.0146, 0.1149, 0.0438);
cone39.rotation.set(deg(11.1371), deg(35.9658), deg(5.9415));
cone39.scale.set(1, 1, 0.45);
tuft1.add(cone39);
const cone40 = new THREE.Mesh(new THREE.ConeGeometry(0.0513, 0.24, 3), grassLightMat);
cone40.position.set(-0.0224, 0.1, -0.0404);
cone40.rotation.set(deg(-172.0323), deg(69.8879), deg(155.8518));
cone40.scale.set(1, 1, 0.45);
tuft1.add(cone40);
const tuft2 = new THREE.Group();
tuft2.name = 'tuft2';
tuft2.position.set(0.4093, 0.5, -0.249);
dressing.add(tuft2);
const cone42 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2121, 3), grassDarkMat);
cone42.position.set(0.038, 0.086, 0.0181);
cone42.rotation.set(deg(8.2785), deg(0.8935), deg(-16.3633));
cone42.scale.set(1, 1, 0.45);
tuft2.add(cone42);
const cone43 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2228, 3), grassMat);
cone43.position.set(0.004, 0.0914, 0.0419);
cone43.rotation.set(deg(21.691), deg(14.3474), deg(-2.0401));
cone43.scale.set(1, 1, 0.45);
tuft2.add(cone43);
const cone44 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2533, 3), grassLightMat);
cone44.position.set(-0.042, 0.1066, -0.0015);
cone44.rotation.set(deg(-57.4396), deg(77.0843), deg(57.717));
cone44.scale.set(1, 1, 0.45);
tuft2.add(cone44);
const cone45 = new THREE.Mesh(new THREE.ConeGeometry(0.0467, 0.2007, 3), grassDarkMat);
cone45.position.set(-0.0058, 0.0803, -0.0417);
cone45.rotation.set(deg(-26.1155), deg(57.1997), deg(5.4368));
cone45.scale.set(1, 1, 0.45);
tuft2.add(cone45);
const tuft3 = new THREE.Group();
tuft3.name = 'tuft3';
tuft3.position.set(0.5506, 0.5, 0.2147);
dressing.add(tuft3);
const cone47 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2416, 3), grassDarkMat);
cone47.position.set(0.0478, 0.1008, 0.025);
cone47.rotation.set(deg(25.9645), deg(39.2885), deg(-24.2156));
cone47.scale.set(1, 1, 0.45);
tuft3.add(cone47);
const cone48 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2442, 3), grassMat);
cone48.position.set(0.0224, 0.1021, 0.0491);
cone48.rotation.set(deg(20.6663), deg(32.7584), deg(-8.54));
cone48.scale.set(1, 1, 0.45);
tuft3.add(cone48);
const cone49 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2708, 3), grassLightMat);
cone49.position.set(-0.048, 0.1154, 0.0246);
cone49.rotation.set(deg(5.4579), deg(9.4433), deg(15.5592));
cone49.scale.set(1, 1, 0.45);
tuft3.add(cone49);
const cone50 = new THREE.Mesh(new THREE.ConeGeometry(0.0599, 0.2824, 3), grassDarkMat);
cone50.position.set(0.0204, 0.1212, -0.0499);
cone50.rotation.set(deg(-10.2713), deg(21.9003), deg(-5.3453));
cone50.scale.set(1, 1, 0.45);
tuft3.add(cone50);
const tuft4 = new THREE.Group();
tuft4.name = 'tuft4';
tuft4.position.set(0.3254, 0.5, -0.3063);
dressing.add(tuft4);
const cone52 = new THREE.Mesh(new THREE.ConeGeometry(0.0504, 0.19, 3), grassDarkMat);
cone52.position.set(0.0417, 0.075, 0.0179);
cone52.rotation.set(deg(21.5798), deg(34.6345), deg(-22.654));
cone52.scale.set(1, 1, 0.45);
tuft4.add(cone52);
const cone53 = new THREE.Mesh(new THREE.ConeGeometry(0.0504, 0.2133, 3), grassMat);
cone53.position.set(-0.0253, 0.0867, 0.0377);
cone53.rotation.set(deg(-120.4876), deg(73.0393), deg(136.8698));
cone53.scale.set(1, 1, 0.45);
tuft4.add(cone53);
const cone54 = new THREE.Mesh(new THREE.ConeGeometry(0.0504, 0.2116, 3), grassLightMat);
cone54.position.set(-0.0149, 0.0858, -0.0428);
cone54.rotation.set(deg(175.9501), deg(62.1274), deg(171.3132));
cone54.scale.set(1, 1, 0.45);
tuft4.add(cone54);
const flower1 = new THREE.Group();
flower1.name = 'flower1';
flower1.position.set(-0.053, 0.5, 0.3938);
dressing.add(flower1);
const cylinder56 = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.016, 0.1615, 4), grassDarkMat);
cylinder56.position.set(0, 0.0608, 0);
flower1.add(cylinder56);
const dodecahedron57 = new THREE.Mesh(new THREE.DodecahedronGeometry(0.042, 0), petalMat);
dodecahedron57.position.set(0, 0.1515, 0);
dodecahedron57.scale.set(1, 0.6, 1);
flower1.add(dodecahedron57);
const cone58 = new THREE.Mesh(new THREE.ConeGeometry(0.0258, 0.0646, 3), grassDarkMat);
cone58.position.set(0.0129, 0.0226, 0);
cone58.rotation.set(0, deg(180.0627), deg(-40));
cone58.scale.set(1, 1, 0.4);
flower1.add(cone58);
const flower2 = new THREE.Group();
flower2.name = 'flower2';
flower2.position.set(0.2323, 0.5, 0.729);
dressing.add(flower2);
const cylinder60 = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.016, 0.2186, 4), grassDarkMat);
cylinder60.position.set(0, 0.0893, 0);
flower2.add(cylinder60);
const dodecahedron61 = new THREE.Mesh(new THREE.DodecahedronGeometry(0.0568, 0), petalPinkMat);
dodecahedron61.position.set(0, 0.2086, 0);
dodecahedron61.scale.set(1, 0.6, 1);
flower2.add(dodecahedron61);
const cone62 = new THREE.Mesh(new THREE.ConeGeometry(0.035, 0.0875, 3), grassDarkMat);
cone62.position.set(0.0175, 0.0306, 0);
cone62.rotation.set(0, deg(16.7453), deg(-40));
cone62.scale.set(1, 1, 0.4);
flower2.add(cone62);
const pebble1 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.0549, 0), 0.0099, 165), stoneMat);
pebble1.name = 'pebble1';
pebble1.position.set(-0.2674, 0.5291, 0.6302);
pebble1.rotation.set(deg(-6.4408), deg(281.2683), deg(1.4088));
pebble1.scale.set(1.0367, 0.4965, 1);
dressing.add(pebble1);
const pebble2 = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.077, 0), 0.0139, 200), stoneMat);
pebble2.name = 'pebble2';
pebble2.position.set(-0.781, 0.5342, 0.7674);
pebble2.rotation.set(deg(-2.0315), deg(349.122), deg(-6.6074));
pebble2.scale.set(1.3451, 0.4923, 1);
dressing.add(pebble2);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createGrassTile;
평점 및 댓글
댓글 0개
아직 댓글이 없어요. 이 모델을 어떻게 사용했는지, 어떤 점이 더 나아질 수 있을지 공유해 주세요.
관련 모델
같은 카테고리 또는 비슷한 태그









