使用 three.js 的函数图示器

Function Grapher using three.js

如何以绘制函数的方式实现函数图示器 z=f(x,y) 使用 three.js API。这个程序应该:

  1. 以 .1(十分之一)为增量生成介于 -1 和 1 之间的输入值,并使用它来绘制 x、y 和 z 顶点以作为网格的一部分包括在内然后以图形方式显示。

  2. 实现鼠标控件,以便可以使用鼠标旋转和缩放映射的函数。

  3. 包括一个平面和一个轴助手,为指示原点的函数提供参考点,假设原点为 x=0、y=0 和 z=0。

<script type="module">
import * as THREE from "https://cdn.skypack.dev/three@0.133.1";
import {OrbitControls}


// declare global variables
let scene, camera, cameraControls, renderer, axisHelper;

function init() {
    
    scene = new THREE.Scene;
    

    
    camera = new THREE.PerspectiveCamera(25, window.innerWidth/window.innerHeight, 1, 1000);
    camera.position.set(-30, 50, -7);
   
  

    const canvas = document.querySelector('#canvasElem');

    renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true,
                opacity: 0.5, transparent: true, alpha: true                                    
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
   
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;

    cameraControls = new THREE.OrbitControls(camera, renderer.domElement);
    cameraControls.addEventListener("mousemove", renderer);
    cameraControls.enablePan = false;

    axisHelper = new THREE.AxesHelper;
    scene.add(axisHelper);

    window.addEventListener("resize", ()=>{

        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

        camera.aspect = window.innerWidth/window.innerHeight;
        camera.updateProjectionMatrix()
    });


     //Constants for use with hyperbolic paraboloid. 
     //If these are changed, the shape will change! Don't use zero.
     const a = 1.0;
     const b = 1.0;
   
/**
 * This is the function that will compute z-position from X and Y positions
 * to produce a hyperbolic parabaloid.
 * @param {*} u 
 * @param {*} v 
 * @param {*} w 
 */
function ComputeZ(u,v,w) { 
        x = u * 2 - 1;
        y = v * 2 - 1;
        w.set(x,y,(x*x)/(a*a) - (y*y)/(b*b));
        }


/**
 * These is a getter function used to set vertex colors. 
 * In the version where I code the geometry myself,
 * they were set in the geometry generation function.
 * @param {*} pointX 
 * @param {*} pointY 
 * @param {*} pointZ 
 * @returns 
 */
function getVertexColors(pointX, pointY, pointZ) {
    return new THREE.Color(pointX.x*0.5+0.5, pointY.y*0.5+0.5, pointZ.z*0.5+0.5);
    }


/**
 * The getter function (getVertexColors) one is to make 
 * the setter function (setVertexColors) more compact.
 * @param {*} geometry 
 */
 //for (var i = 0; i < geometry.faces.length; i++)
 //for (i in geometry.faces)
function setVertexColors(geometry) {
    for (var i = 0; i < geometry.faces; i++) {
       var face = geometry.faces[i]; 
       face.vertexColors = [getVertexColors(geometry.vertices[face.a]), 
                            getVertexColors(geometry.vertices[face.b]), 
                            getVertexColors(geometry.vertices[face.c])];
    // face.vertexColors[0] = new THREE.Color("rgb(255,0,0)");
    // face.vertexColors[1] = new THREE.Color("rgb(0,255,0)");
    // face.vertexColors[2] = new THREE.Color("rgb(0, 0,25)");
    // face.vertexColors[3] = new THREE.Color("rgb(0, 0, 0)") 
    }
}

/**
 * This creates a plane grid in the x-y direction, using the specified size and steps.
 * @param {number} size 
 * @param {number} steps 
 */    
function createPlaneGrid(size, steps) { 

    // create a grouping for the lines
    var group = new THREE.Group();

    // create the material for the grid
    var material = new THREE.LineBasicMaterial({color:0x000000, transparent:true, opacity:0.25
                
    });

    // create the x-axis lines for the grid
    for (var i = 0; i <= steps; i+=1) { 
        var f = (i/steps)-0.5;

        // create a base class for geometries and add a customed geometry to it
        var geometry = new THREE.BufferGeometry(); 

        // create the x-axis custom lines of the grid
        const points = [
            new THREE.Vector3( f*size, -size*0.5, 0 ), 
            new THREE.Vector3( f*size, size*0.5, 0 )
        ]
        geometry.setFromPoints(points);
        geometry.computeVertexNormals();

        //create the x-axis line mesh
        var axisX = new THREE.Line( geometry, material );

        //add the line to the group
        group.add( axisX );
        }

    // create the y-axis lines (geometry) for the grid    
    for (var i = 0; i <= steps; i+=1) {
        var f = (i/steps)-0.5;

        // create a base class for geometries and add a customed geometry to it
        var geometry = new THREE.BufferGeometry();

        // create the y-axis custom lines (geometry) of the grid
        const points = [
            new THREE.Vector3( -size*0.5, f*size, 0 ), 
            new THREE.Vector3( size*0.5, f*size, 0 ),
        ]

        geometry.setFromPoints(points);
        geometry.computeVertexNormals();
         //create the y-axis line mesh
        var axisY = new THREE.Line( geometry, material );

        //add the line to the group
        group.add( axisY );
        }
        // return the group when all these calculations are done
        return group;
        }
        
        // create a parametric geometry
        var ParamGeometry = new THREE.ParametricGeometry(ComputeZ, 20, 20);
        setVertexColors(ParamGeometry);

        // create a material
        var ParaMaterial = new THREE.MeshBasicMaterial({color:0xffffff, side:THREE.DoubleSide, 
                        vertexColors: THREE.VertexColors});
          
        //Create models and groups
        var ParaMesh = new THREE.Mesh( ParamGeometry, ParaMaterial ); //Create the actual object
        scene.add( ParaMesh );
          
        var plane = createPlaneGrid(4,12);
        scene.add( plane );    
    
// add directional lighting
var directionalLight = new THREE.DirectionalLight( 0xffffff, 1.0 );
    directionalLight.position.set(100,100,100); 
    directionalLight.castShadow = true;
    directionalLight.shadow.camera.left = -100;
    directionalLight.shadow.camera.bottom = -100;
    directionalLight.shadow.camera.right = 100;
    directionalLight.shadow.camera.top = 100;
    directionalLight.shadow.camera.far = 1000;
    scene.add( directionalLight );

       
    }}

function animate(){
    requestAnimationFrame(animate);
    render();
}
function render(){
   // cameraControls.update();
    renderer.render(scene, camera);
}

init();
animate();
</script>

图形已绘制,但未应用颜色,使形状变成黑色和白色。 我尝试了不同的方法来为面部涂色,但都没有用。 我需要这方面的帮助。

一个非常粗略的“绘图仪”示例 bended/distorted PlaneGeometry:

body{
  overflow: hidden;
  margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three@0.133.1";
import {
  OrbitControls
} from "https://cdn.skypack.dev/three@0.133.1/examples/jsm/controls/OrbitControls.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 10);
camera.position.set(1, 1.5, 1).setLength(2.5);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x161616);
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 grid = new THREE.GridHelper(2, 20, 0xffff00, 0xffff00);
grid.position.y = -0.001;
scene.add(grid, new THREE.AxesHelper(1));

let graphGeom = new THREE.PlaneGeometry(2, 2, 20, 20);
graphGeom.rotateX(Math.PI * -0.5);
let graphMat = new THREE.MeshNormalMaterial({side: THREE.DoubleSide, wireframe: false});
let graph = new THREE.Mesh(graphGeom, graphMat);

// f(x,z)
let pos = graphGeom.attributes.position;
for(let i = 0; i < pos.count; i++){
    let x = pos.getX(i);
  let z = pos.getZ(i);
    pos.setY(i, Math.sin(x * z * Math.PI) * Math.cos(z * z * Math.PI * 0.5) * 0.75);
}
graphGeom.computeVertexNormals();

scene.add(graph);

window.addEventListener("resize", onResize);

renderer.setAnimationLoop(_ => {
  renderer.render(scene, camera);
})

function onResize(event) {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
}

</script>