最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to create a 3d Rugby ball with 2 different images on it's subsequent faces - Stack Overflow

programmeradmin0浏览0评论

I'm trying to create a 3d rugby ball using ThreeJS and RTF and I'm struggling to create 4 faces on the ball where two subsequent faces on the ball will have two different images. Something similar to this.

I have been able to create the geometry and add one image on one face. But the other 3 images don't show up on the geometry.

This is the code that I have so far:

import React, { Suspense } from "react";
import { Canvas, useLoader } from "@react-three/fiber";
import {
  OrbitControls,
  useProgress,
  Html,
} from "@react-three/drei";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import * as THREE from "three";

const RugbyBall: React.FC = () => {
  // Load textures
  const one = useLoader(THREE.TextureLoader, "/images/one.jpg");
  const two = useLoader(THREE.TextureLoader, "/images/two.jpg");
  const texture = useLoader(THREE.TextureLoader, "/images/texture.jpg");

  // Create materials
  const oneMaterial = new THREE.MeshPhysicalMaterial({
    map: one,
    bumpMap: texture,
    bumpScale: 1,
    roughness: 1,
    metalness: 0,
    // side: THREE.FrontSide,
  });

  const twoMaterial = new THREE.MeshPhysicalMaterial({
    map: two,
    bumpMap: texture,
    bumpScale: 1,
    roughness: 1,
    metalness: 0,
    // side: THREE.BackSide,
  });

  // Create the Sphere Geometry (a simple sphere)
  const radius = 50; // Size of the ball
  const widthSegments = 64; // Number of horizontal segments
  const heightSegments = 64; // Number of vertical segments
  const sphereGeometry = new THREE.SphereGeometry(
    radius,
    widthSegments,
    heightSegments
  );

  // Scale the sphere to elongate it into a rugby ball shape
  sphereGeometry.scale(1, 1.8, 1);

  // Clear any pre-existing groups
  sphereGeometry.clearGroups();

  if (sphereGeometry.index && sphereGeometry.index.count) {
    console.log("sphereGeometry.index.count: ", sphereGeometry.index.count);
    // Split the sphere into two hemispheres
    const middle = Math.floor(sphereGeometry.index.count / 2);

    // Add group for the top hemisphere (one)
    for (let i = 0; i < middle; i++) {
      sphereGeometry.addGroup(i * 3, 3, 0); // Apply one material to the first half
    }

    // Add group for the bottom hemisphere (two)
    for (let i = middle; i < sphereGeometry.index.count / 3; i++) {
      sphereGeometry.addGroup(i * 3, 3, 1); // Apply two material to the second half
    }
  }

  // Create the mesh and apply the materials
  const materials = [oneMaterial, twoMaterial]; // Two materials: one and two
  const rugbyBall = new THREE.Mesh(sphereGeometry, materials);

  return <primitive object={rugbyBall} position={[0, 0, 0]} scale={2.5} rotation={[-(Math.PI / 2), 0, 0]} />;
};

const Loader = () => {
  const { progress } = useProgress();
  return <Html center>{Math.round(progress)}% loaded</Html>;
};

// Main Scene Component
const Scene = () => {
  return (
    <Canvas
      camera={{ position: [0, 0, 500], fov: 75 }}
      style={{
        width: "100vw",
        height: "30vh",
        backgroundColor: "red",
      }}
    >
      <Suspense fallback={<Loader />}>
        {/* Lighting */}
        <ambientLight intensity={1} />
        <directionalLight position={[0, 1, 0]} intensity={2} />

        {/* 3D Model */}
        <RugbyBall />

        {/* Controls */}
        <OrbitControls autoRotate autoRotateSpeed={2} enableDamping dampingFactor={0.1} />
      </Suspense>
    </Canvas>
  );
};

export default Scene;

I'm trying to create a 3d rugby ball using ThreeJS and RTF and I'm struggling to create 4 faces on the ball where two subsequent faces on the ball will have two different images. Something similar to this.

I have been able to create the geometry and add one image on one face. But the other 3 images don't show up on the geometry.

This is the code that I have so far:

import React, { Suspense } from "react";
import { Canvas, useLoader } from "@react-three/fiber";
import {
  OrbitControls,
  useProgress,
  Html,
} from "@react-three/drei";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import * as THREE from "three";

const RugbyBall: React.FC = () => {
  // Load textures
  const one = useLoader(THREE.TextureLoader, "/images/one.jpg");
  const two = useLoader(THREE.TextureLoader, "/images/two.jpg");
  const texture = useLoader(THREE.TextureLoader, "/images/texture.jpg");

  // Create materials
  const oneMaterial = new THREE.MeshPhysicalMaterial({
    map: one,
    bumpMap: texture,
    bumpScale: 1,
    roughness: 1,
    metalness: 0,
    // side: THREE.FrontSide,
  });

  const twoMaterial = new THREE.MeshPhysicalMaterial({
    map: two,
    bumpMap: texture,
    bumpScale: 1,
    roughness: 1,
    metalness: 0,
    // side: THREE.BackSide,
  });

  // Create the Sphere Geometry (a simple sphere)
  const radius = 50; // Size of the ball
  const widthSegments = 64; // Number of horizontal segments
  const heightSegments = 64; // Number of vertical segments
  const sphereGeometry = new THREE.SphereGeometry(
    radius,
    widthSegments,
    heightSegments
  );

  // Scale the sphere to elongate it into a rugby ball shape
  sphereGeometry.scale(1, 1.8, 1);

  // Clear any pre-existing groups
  sphereGeometry.clearGroups();

  if (sphereGeometry.index && sphereGeometry.index.count) {
    console.log("sphereGeometry.index.count: ", sphereGeometry.index.count);
    // Split the sphere into two hemispheres
    const middle = Math.floor(sphereGeometry.index.count / 2);

    // Add group for the top hemisphere (one)
    for (let i = 0; i < middle; i++) {
      sphereGeometry.addGroup(i * 3, 3, 0); // Apply one material to the first half
    }

    // Add group for the bottom hemisphere (two)
    for (let i = middle; i < sphereGeometry.index.count / 3; i++) {
      sphereGeometry.addGroup(i * 3, 3, 1); // Apply two material to the second half
    }
  }

  // Create the mesh and apply the materials
  const materials = [oneMaterial, twoMaterial]; // Two materials: one and two
  const rugbyBall = new THREE.Mesh(sphereGeometry, materials);

  return <primitive object={rugbyBall} position={[0, 0, 0]} scale={2.5} rotation={[-(Math.PI / 2), 0, 0]} />;
};

const Loader = () => {
  const { progress } = useProgress();
  return <Html center>{Math.round(progress)}% loaded</Html>;
};

// Main Scene Component
const Scene = () => {
  return (
    <Canvas
      camera={{ position: [0, 0, 500], fov: 75 }}
      style={{
        width: "100vw",
        height: "30vh",
        backgroundColor: "red",
      }}
    >
      <Suspense fallback={<Loader />}>
        {/* Lighting */}
        <ambientLight intensity={1} />
        <directionalLight position={[0, 1, 0]} intensity={2} />

        {/* 3D Model */}
        <RugbyBall />

        {/* Controls */}
        <OrbitControls autoRotate autoRotateSpeed={2} enableDamping dampingFactor={0.1} />
      </Suspense>
    </Canvas>
  );
};

export default Scene;

Share Improve this question edited Feb 22 at 8:30 Łukasz Daniel Mastalerz 2,4482 gold badges7 silver badges28 bronze badges asked Feb 21 at 4:56 SiddAjmeraSiddAjmera 39.5k6 gold badges76 silver badges113 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

It doesn't work like that. You have two options: either modify the native geometry, e.g. SphereGeometry, or create a model, e.g. in Blender, name the appropriate sections and map them (which in my opinion is an easier and faster way). When you apply a texture to a native SphereGeometry, the texture wraps around the entire sphere by default. If you want modify geometry you can assign different materials to each group, allowing for multiple colors or textures on different parts of the sphere, but like I wrote faster is create model in Blender. You need to converting the geometry to non-indexed using the toNonIndexed() method, where each face gets its own set of vertices. This allow assigning unique colors/textures to each face, because vertices are no longer shared between faces, so you can create vertical stripes (for example) on the rugby ball.

Try something like this...

<script type="importmap">
  {
"imports": {
  "three": "https://unpkg/[email protected]/build/three.module.js",
  "three/addons/": "https://unpkg/[email protected]/examples/jsm/"
}
  }
</script>

<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(2);
document.body.appendChild(renderer.domElement);

camera.position.set(0, 0, 15);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

const radius = 3;
const widthSegments = 64;
const heightSegments = 64;
let sphereGeometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
sphereGeometry.scale(1, 1.5, 1);

sphereGeometry = sphereGeometry.toNonIndexed();

const uvA = sphereGeometry.getAttribute("uv");
const posA = sphereGeometry.getAttribute("position");
const normalA = sphereGeometry.getAttribute("normal");
const faceCount = sphereGeometry.attributes.position.count / 3;
const stripeCount = 4;
const stripeGroups = Array.from({ length: stripeCount }, () => []);

for (let face = 0; face < faceCount; face++) {
  const i0 = face * 3;
  const i1 = face * 3 + 1;
  const i2 = face * 3 + 2;
  const u0 = uvA.getX(i0);
  const u1 = uvA.getX(i1);
  const u2 = uvA.getX(i2);
  let avgU = (u0 + u1 + u2) / 3;
  let stripeIndex = Math.floor(avgU * stripeCount);
  if (stripeIndex === stripeCount) stripeIndex = stripeCount - 1;
  stripeGroups[stripeIndex].push(face);
}

const newPositions = [];
const newNormals = [];
const newUVs = [];
let offset = 0;
const groupInfos = [];

for (let stripe = 0; stripe < stripeCount; stripe++) {
  const faces = stripeGroups[stripe];
  const groupStart = offset;
  faces.forEach((face) => {
    const i0 = face * 3;
    const i1 = face * 3 + 1;
    const i2 = face * 3 + 2;
    for (let i of [i0, i1, i2]) {
      newPositions.push(posA.getX(i), posA.getY(i), posA.getZ(i));
      newNormals.push(normalA.getX(i), normalA.getY(i), normalA.getZ(i));
      newUVs.push(uvA.getX(i), uvA.getY(i));
      offset++;
    }
  });
  const count = faces.length * 3;
  if (count > 0) {
    groupInfos.push({ start: groupStart, count: count, materialIndex: stripe });
  }
}

const newGeometryRugby = new THREE.BufferGeometry();
newGeometryRugby.setAttribute(
  "position",
  new THREE.Float32BufferAttribute(newPositions, 3)
);
newGeometryRugby.setAttribute(
  "normal",
  new THREE.Float32BufferAttribute(newNormals, 3)
);
newGeometryRugby.setAttribute("uv", new THREE.Float32BufferAttribute(newUVs, 2));
groupInfos.forEach((info) => {
  newGeometryRugby.addGroup(info.start, info.count, info.materialIndex);
});
const loader = new THREE.TextureLoader();
const t1 = loader.load("https://threejs./examples/textures/brick_diffuse.jpg");
const t2 = loader.load("https://threejs./examples/textures/uv_grid_opengl.jpg");
const t3 = loader.load("https://threejs./examples/textures/brick_diffuse.jpg");
const t4 = loader.load("https://threejs./examples/textures/uv_grid_opengl.jpg");

const m = [
  new THREE.MeshPhongMaterial({ map: t1 }),
  new THREE.MeshPhongMaterial({ map: t2 }),
  new THREE.MeshPhongMaterial({ map: t3 }),
  new THREE.MeshPhongMaterial({ map: t4 }),
];

const rugbyBall = new THREE.Mesh(newGeometryRugby, m);
scene.add(rugbyBall);

const l1 = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(l1);
const l2 = new THREE.DirectionalLight(0xffffff, 0.8);
l2.position.set(5, 10, 7.5);
scene.add(l2);

function a() {
  requestAnimationFrame(a);
  rugbyBall.rotation.y += 0.005;
  renderer.render(scene, camera);
}
a();

window.addEventListener("resize", () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>

发布评论

评论列表(0)

  1. 暂无评论