【问题标题】:How to project a texture/shape to specific part of a mesh in Three.js?如何将纹理/形状投影到 Three.js 中网格的特定部分?
【发布时间】:2021-11-28 09:44:21
【问题描述】:

我想将纹理或某种形状(透明 - 环形、圆形)投射到网格(但投射到特定部分)。例如,在游戏中,我们选择/单击一个敌人或 NPC,然后我们会在角色下方看到一些圆圈,表示选择。该圆会根据网格(高度、坡度)改变其形状,您可以查看以下图片。

我想这样做,但我不知道如何完美地做到这一点 - 到目前为止,我尝试对网格进行光线投射,使顶点正常并应用旋转,但是当涉及到更复杂的部分时网格这效果不够好。我想我需要使用着色器?有什么资源可以查吗?

【问题讨论】:

    标签: javascript three.js mesh


    【解决方案1】:

    并不像看起来那么难。

    .onBeforeCompile修改地面材质,将选中物体的位置统一传递给shader。

    在代码sn-p中,点击一个按钮来选择相应的对象,所以选择标记会跟随在地面上:

    body{
      overflow: hidden;
      margin: 0;
    }
    #selections {
      width: 100px;
      display: flex;
      flex-direction: column;
    }
    button.selected{
      color: #00ff32;
      background: blue;
    }
    <div id="selections" style="position: absolute;border: 1px solid yellow;"></div>
    <script type="module">
    import * as THREE from "https://cdn.skypack.dev/three@0.133";
    import {
      OrbitControls
    } from "https://cdn.skypack.dev/three@0.133/examples/jsm/controls/OrbitControls.js";
    
    let scene = new THREE.Scene();
    let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
    camera.position.set(0, 5, 8);
    camera.lookAt(scene.position);
    let renderer = new THREE.WebGLRenderer({
      antialias: true
    });
    renderer.setSize(innerWidth, innerHeight);
    renderer.setClearColor(0x404040);
    document.body.appendChild(renderer.domElement);
    
    let controls = new OrbitControls(camera, renderer.domElement);
    
    let light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.setScalar(1);
    scene.add(
        light,
      new THREE.AmbientLight(0xffffff, 0.5)
    );
    
    let objects = new Array(5).fill(0).map((p,idx)=>{return setObject(idx)});
    //console.log(objects);
    let selected = objects[0];
    
    let g = new THREE.PlaneGeometry(10, 10, 5, 5);
    g.rotateX(-Math.PI * 0.5);
    for(let i = 0; i < g.attributes.position.count; i++){
        g.attributes.position.setY(i, (Math.random() * 2 - 1) * 0.75);
    }
    g.computeVertexNormals();
    let uniforms = {
        selection: {value: new THREE.Vector3()}
    }
    let m = new THREE.MeshLambertMaterial({
        color: 0x003264,
        map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/water.jpg"),
      onBeforeCompile: shader => {
        shader.uniforms.selection = uniforms.selection;
        shader.vertexShader = `
            varying vec3 vPos;
          ${shader.vertexShader}
        `.replace(
            `#include <begin_vertex>`,
          `#include <begin_vertex>
            vPos = transformed;
          `
        );
        shader.fragmentShader = `
            #define ss(a, b, c) smoothstep(a, b, c)
            uniform vec3 selection;
          varying vec3 vPos;
          ${shader.fragmentShader}
        `.replace(
            `#include <dithering_fragment>`,
          `#include <dithering_fragment>
          
            // shape
            float dist = distance(selection.xz, vPos.xz);
            float r = 0.25;
            
            float shape = (ss(r-0.1, r, dist)*0.75 + 0.25) - ss(r, r + 0.1, dist);
            
            vec3 col = mix(gl_FragColor.rgb, vec3(0, 1, 0.25), shape);
            gl_FragColor = vec4(col, gl_FragColor.a);
          `
        );
        //console.log(shader.fragmentShader)
      }
    });
    
    let o = new THREE.Mesh(g, m);
    scene.add(o);
    
    window.addEventListener("resize", onResize);
    
    let clock = new THREE.Clock();
    
    renderer.setAnimationLoop(_ => {
        
      let t = clock.getElapsedTime() * 0.5;
      
      objects.forEach(obj => {
        let ud = obj.userData;
        obj.position.x = Math.cos(t * ud.scaleX + ud.initPhase) * 4.75;
        obj.position.y = 1;
        obj.position.z = Math.sin(t * ud.scaleZ + ud.initPhase) * 4.75;
      })
      
      o.worldToLocal(uniforms.selection.value.copy(selected.position));
      
      renderer.render(scene, camera);
      
    })
    
    function setObject(idx){
        let g = new THREE.SphereGeometry(0.25);
      let m = new THREE.MeshLambertMaterial({color: 0x7f7f7f * Math.random() + 0x7f7f7f});
      let o = new THREE.Mesh(g, m);
      o.userData = {
        initPhase: Math.PI * 2 * Math.random(),
        scaleX: Math.random() * 0.5 + 0.5,
        scaleZ: Math.random() * 0.5 + 0.5
      }
      scene.add(o);
      
      let btn = document.createElement("button");
      btn.innerText = "Object " + idx;
      selections.appendChild(btn);
      btn.addEventListener("click", event => {
        selections.querySelectorAll("button").forEach(b => {b.classList.remove("selected")});
        btn.classList.add("selected");
        selected = o
      });
      
      return o;
    }
    
    function onResize(event) {
      camera.aspect = innerWidth / innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(innerWidth, innerHeight);
    }
    
    </script>

    【讨论】:

    • 非常感谢!演示看起来很不错。
    • @ErtuğrulÇetin 不客气 :)
    猜你喜欢
    • 2017-09-07
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-31
    • 2021-12-27
    • 2011-11-02
    • 2013-03-26
    相关资源
    最近更新 更多