Search code examples
three.jstextures

How to project a texture to curved surface?


Screen capture

I tried to make a 3/4 cylinder surface, and I made it from extruting by a ellipe path, but when I tried to load a texture to the surface, It does not as I exprected: uniformly painted to the surface, it's stretched

I know it's about texture projection, But I dont know how to set options.

class EllipseCurve3 extends THREE.Curve {
  
  ellipse = null
 
  constructor (ellipse) {
    super()
    this.ellipse = ellipse
  }
  
  
  getPoint(t, optionalTarget = new THREE.Vector3()) {
    const point = this.ellipse.getPoint(t, optionalTarget)
    return new THREE.Vector3(
      point.x,
      point.y,
      0
    )
  }
}

// Scene
const scene = new THREE.Scene();

var shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.moveTo(0, 1);
shape.lineTo(50, 1);
shape.moveTo(50, 0);
shape.lineTo(0, 0);

// var curve = new THREE.CatmullRomCurve3([
//   new THREE.Vector3(0, 0, 50),
//   new THREE.Vector3(-50, 0, 0),
//   new THREE.Vector3(0, 0, -50)
// ]);

const arc = new THREE.EllipseCurve(
    0,
    0, // ax, aY
    100,
    100, // xRadius, yRadius
    0,
    1.5 * Math.PI, // aStartAngle, aEndAngle
    false, // aClockwise
    0 // aRotation
  ),
  path = new EllipseCurve3(arc)
  geometry = new THREE.ExtrudeGeometry(shape, {
    bevelEnabled: false,
    extrudePath: path,
    steps: 50,
    depth: 5,
    amount: 20,
    material: 0,
    extrudeMaterial: 1
  });


// Set up lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);

const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);

const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(100, 200, 100); // x, y, z
scene.add(directionalLight);

// Camera
const width = 200;
const height = width * (window.innerHeight / window.innerWidth);
const camera = new THREE.OrthographicCamera(
  width / -2, // left
  width / 2, // right
  height / 2, // top
  height / -2, // bottom
  0.1, // near
  1000 // far
);

camera.position.set(400, 400, 400);
camera.lookAt(0, 0, 0);

// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);

const controls = new THREE.OrbitControls(camera, renderer.domElement);

renderer.render(scene, camera);

// Add it to HTML
document.body.appendChild(renderer.domElement);
var textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = true;

const picture = 'https://threejs.org/examples/textures/uv_grid_opengl.jpg'
textureLoader.load(picture, function(texture) {
    //  repeat pattern
    texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;

    // zoom in on pattern
    texture.repeat.set(.01, .01);

    // assign texture via MeshBasicMaterial
    var material = new THREE.MeshBasicMaterial({
        map: texture,
    needsUpdate: true,
        // transparent: true,
        // premultipliedAlpha: true,
        // side: THREE.DoubleSide,
        // blending: THREE.AdditiveBlending
    });
  
  // var material = new THREE.MeshPhongMaterial({ color: 0x0048ff });
  var mesh = new THREE.Mesh(geometry, material)
  mesh.rotation.x = Math.PI / 2
  // mesh.rotation.z = Math.PI / 2
  scene.add(mesh)
  scene.add(cube)
})

function render() {
  renderer.render(scene, camera);
  // Rotate out group
  // svgGroup.rotation.y -= 0.005
  controls.update();
  requestAnimationFrame(render);
}
render();

Code here https://codepen.io/mike-xu/pen/RwQeEXJ


Solution

  • "I know it's about texture projection" - It's about computing UV, based on vertices coordinates.

    CylinderGeometry also may help to achieve the result you described. With less code, and more convenient and predictable way.

    body{
      overflow: hidden;
      margin: 0;
    }
    <script type="module">
    import * as THREE from "https://cdn.skypack.dev/three@0.136.0";
    import {OrbitControls} from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/controls/OrbitControls.js";
    
    console.clear();
    
    let scene = new THREE.Scene();
    let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
    camera.position.set(0, 10, 10);
    let renderer = new THREE.WebGLRenderer();
    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);
    
    scene.add(new THREE.AxesHelper(10));
    
    let g = new THREE.CylinderGeometry(5, 5, 5, 100, 20, true, 0, Math.PI * 1.5);
    let m = new THREE.MeshBasicMaterial({
      side: THREE.DoubleSide,
      map: new THREE.TextureLoader().load(
        "https://threejs.org/examples/textures/uv_grid_opengl.jpg",
        tex => {
          tex.wrapS = tex.wrapT = THREE.MirroredRepeatWrapping;
          tex.repeat.set(3, 1);
        }
      )
    });
    let c = new THREE.Mesh(g, m);
    scene.add(c);
    
    renderer.setAnimationLoop(() => {
      renderer.render(scene, camera);
    });
    </script>