使用Three.js在浏览器绘制OFF格式的3D文件

本文介绍如何在web端使用Three.js显示3D模型,特别是OFF文件格式。作者详细讲解了OFF文件的结构,并提供了一段JavaScript代码,用于读取和解析OFF文件的内容,包括点和面的信息。通过创建自定义的OFFTEMP对象,实现了点的渲染和面的三角化,从而在Three.js中展示3D模型。此外,还提供了加载OFF文件并应用材质的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近有需求将一系列不同类型的3D文件在web端做出展示,即使从未接触过web端图形编程,three.js会提供很好的帮助,而对应的完成加载模型这些需求显得非常简单,很多格式提供了three的加载器,这些代码在互联网很容易找到,然而部分格式[.off]的加载器暂时没有,笔者在这里简单写了一个


以下javascript代码可以直接复制使用,案例供参考

一、分析OFF文件

OFF
8 9 0
-0.274878 -0.274878 -0.274878
-0.274878 0.274878 -0.274878
0.274878 0.274878 -0.274878
0.274878 -0.274878 -0.274878
-0.274878 -0.274878 0.274878
-0.274878 0.274878 0.274878
0.274878 0.274878 0.274878
0.274878 -0.274878 0.274878
1 0 1 0 0
1 1 1 0 0
1 2 1 0 0
1 3 1 0 0
1 4 1 0 0
1 5 1 0 0
1 6 1 0 0
1 7 1 0 0
1 8 0 1 0

这就是OFF文件的内容。第一行固定为OFF,第二行为3个数字,a,b,c分别表示点的数量,面的数量,边的数量。实际上边的数量比较鸡肋,很多OFF文件边的数量直接填0了。从第二行开始的a行数组表示点的坐标,例如:-0.274878 -0.274878 -0.274878,表示第一个点的坐标为-0.274878 -0.274878 -0.274878。这里有a个点。从第a+1行开始的b行数组表示面的构成信息,第一个数字表示构成面的点的数量,一般为3或者4,后面跟着对应数量的数字,为点的下标。

二、读取OFF文件,将点面信息保存起来

1.创建点面对象

构造一个对象起名OFFTEMP,对象初始化点,面,边数量,同时创建出点数组和边数组,顺便定义一个材质对象,使得该对象在渲染的时候直可以由自己直接传递材质,再定义一个读取OFF源文件的方法,将其点,面读取出来
个人需要,读取文件系统的url,对于文件解析方式不固定,只要能读取出off文件的内容即可

function OFFTEMP(v,f,g)
{
	this.Material = null;
	this.title = "";
	this.Member_V = v;
    this.Member_F =  f;
    this.Member_G = g;
    this.Set=function(x,y,z) {
        this.Member_V = x;
        this.Member_F = y;
        this.Member_G = z;
    };
    this.PointList=[];
    this.Facelist=[];
    this.SetMaterial = function(m)
    {
    	this.Material = m;
    }
  
    this.ReadingOFFFile=function(url){
		   var htmlobj= $.ajax({url:url,async:false});
		   var dataString = htmlobj.responseText;
		   var DataStr = dataString.split('\r\n');
		   let title = DataStr[0];
		   this.Set(parseInt(DataStr[1].split(/\s+/)[0]),parseInt(DataStr[1].split(/\s+/)[1]),parseInt(DataStr[1].split(/\s+/)[2]));
		   var beginline = 2;
		   var endline = 2+this.Member_V;
		   for(let i =2;i<endline;i++)
		   {
		     	let beginindex = 0;
		 	   	var pointitem = {};
			   	var dline = DataStr[i].split(/\s+/);
			   	for(let i =0;i<dline.length;i++)
                {
                	if(dline[i]==""||dline[i]==" "||dline[i]==undefined)
                	{
                		beginindex++;
                	}
                	else
                	{
                		break;
                	}
                }            
			   	pointitem = new THREE.Vector3(dline[0+beginindex],dline[1+beginindex],dline[2+beginindex]);
			   	this.PointList.push(pointitem);
		   }
		   for(let j = endline;j<endline+this.Member_F;j++)
		   {
		     	var faceitem = [];
		     	var dline_vex = DataStr[j].split(/\s+/);
		     	let beginindex = 0;
                for(let i =0;i<dline_vex.length;i++)
                {
                	if(dline_vex[i]==""||dline_vex[i]==" "||dline_vex[i]==undefined)
                	{
                		beginindex++;
                	}
                	else
                	{
                		break;
                	}
                }
		     	var count = parseInt(dline_vex[beginindex]);
		     	for(let x = beginindex+1;x<dline_vex.length;x++)
		     	{
		     		if(dline_vex[x]!='')
		     		{
		     			faceitem.push(parseInt(dline_vex[x]));
		     		}
		     	}
		     	this.Facelist.push(faceitem);
		   }
		   
     }
}

OFF文件使用大量的空格分割,把握好每行的数据,就能很容易的把点面数据读取出来。

2.尝试渲染点

代码如下(示例):

 this.CreatGponit=function() {
        	let geometry = new THREE.Geometry();
        	for(let i =0;i<this.PointList.length;i++)
        	{
        	    geometry.vertices.push(this.PointList[i]);
        	}
        	let  material = new THREE.PointsMaterial( {color: 'red', size:1} );
            let  mesh = new THREE.Points( geometry, material );
            return mesh;
    }
    

在OFFTEMP对象中增加此方法,通过调用此方法来将自己的点形成网格。

            let ot = new OFFTEMP();
			ot.ReadingOFFFile(url1);
			let mesh = ot.CreatGponit();
			mesh.scale.set(12, 12, 12);
			scene.children.push(mesh);
	        mcloud = mesh;

述
根据点的情况,看起来提取数据没有太大的问题,再将面形成,实现三角化。这里可能需要先手写法线计算,3点确定一个平面,通过公式即可正确计算法线

this.GetNomal=function(point1,point2,point3){
     	  let x;
          let y;
          let z;
          x = (point2.y-point1.y)*(point3.z-point1.z) - (point2.z-point1.z)*(point3.y-point1.y);
          y = (point2.z-point1.z)*(point3.x-point1.x) - (point2.x-point1.x)*(point3.z-point1.z);
          z = (point2.x-point1.x)*(point3.y-point1.y) - (point2.y-point1.y)*(point3.x-point1.x);
          return new THREE.Vector3(x,y,z)
     };

然后封装出形成网格的方法

    this.CreatFace=function(){
        	let geometry = new THREE.Geometry();
        	for(let i =0;i<this.PointList.length;i++)
        	{
        	    let thispoint =this.PointList[i];
        	    geometry.vertices.push(thispoint);
        	}
        	var met = []
            let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
        	for(let j =0;j<this.Facelist.length;j++)
        	{
        		if(this.Facelist[j].length==3)
        		{
        	    var v1 = this.Facelist[j][0];
        	    var v2 = this.Facelist[j][1];
        	    var v3 = this.Facelist[j][2];
                let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
        		geometry.faces.push(face);

        		}
        	}
		    let mesh = new THREE.Mesh( geometry, this.Material);
		    
        	return mesh;
       }

再将这些方法集中一下,提供好材质,直接加入场景。没有很大的材质效果需求,用matcap是非常省时省力的。这里再封装一个填入材质生成网格的方法。

   this.CreatMeshWithMaterial = function(m)
     {
     	let geometry = new THREE.Geometry();
        	for(let i =0;i<this.PointList.length;i++)
        	{
        	    let thispoint = this.PointList[i];
        	    geometry.vertices.push(thispoint);
        	}
        	var met = []
            let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
        	for(let j =0;j<this.Facelist.length;j++)
        	{
        		if(this.Facelist[j].length==3)
        		{
        	    var v1 = this.Facelist[j][0];
        	    var v2 = this.Facelist[j][1];
        	    var v3 = this.Facelist[j][2];
                let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
        		geometry.faces.push(face);

        		}
        	}
		    let mesh = new THREE.Mesh( geometry, m);
		    
        	return mesh;
     }
           
     this.LoadOFFobject = function(url,m)
     {
     	this.ReadingOFFFile(url);
        return this.CreatMeshWithMaterial(m);
     }

一般来说材质设置 side: THREE.DoubleSide,,我这里使用的模型是封闭面的,所以看不出什么区别。最终效果如下
二面渲染效果,里面的背面也是蓝的
单面渲染效果,里面的背面是没有的
在这里插入图片描述

完整代码,可以直接使用对象调用。

  function OFFTEMP(v,f,g)
  {
	this.Material = null;
	this.title = "";
	this.Member_V = v;
    this.Member_F =  f;
    this.Member_G = g;
    this.Set=function(x,y,z) {
        this.Member_V = x;
        this.Member_F = y;
        this.Member_G = z;
    };
    this.PointList=[];
    this.Facelist=[];
    
    this.ReadingOFFFile=function(url){
		   var htmlobj= $.ajax({url:url,async:false});
		   var dataString = htmlobj.responseText;
		   var DataStr = dataString.split('\r\n');
		   let title = DataStr[0];
		   this.Set(parseInt(DataStr[1].split(/\s+/)[0]),parseInt(DataStr[1].split(/\s+/)[1]),parseInt(DataStr[1].split(/\s+/)[2]));
		   var beginline = 2;
		   var endline = 2+this.Member_V;
		   for(let i =2;i<endline;i++)
		   {
		     	let beginindex = 0;
		 	   	var pointitem = {};
			   	var dline = DataStr[i].split(/\s+/);
			   	for(let i =0;i<dline.length;i++)
                {
                	if(dline[i]==""||dline[i]==" "||dline[i]==undefined)
                	{
                		beginindex++;
                	}
                	else
                	{
                		break;
                	}
                }            
			   	pointitem = new THREE.Vector3(dline[0+beginindex],dline[1+beginindex],dline[2+beginindex]);
			   	this.PointList.push(pointitem);
		   }
		   for(let j = endline;j<endline+this.Member_F;j++)
		   {
		     	var faceitem = [];
		     	var dline_vex = DataStr[j].split(/\s+/);
		     	let beginindex = 0;
                for(let i =0;i<dline_vex.length;i++)
                {
                	if(dline_vex[i]==""||dline_vex[i]==" "||dline_vex[i]==undefined)
                	{
                		beginindex++;
                	}
                	else
                	{
                		break;
                	}
                }
		     	var count = parseInt(dline_vex[beginindex]);
		     	for(let x = beginindex+1;x<dline_vex.length;x++)
		     	{
		     		if(dline_vex[x]!='')
		     		{
		     			faceitem.push(parseInt(dline_vex[x]));
		     		}
		     	}
		     	this.Facelist.push(faceitem);
		   }
		   
     }
    
    this.CreatGponit=function() {
        	let geometry = new THREE.Geometry();
        	for(let i =0;i<this.PointList.length;i++)
        	{
        	    geometry.vertices.push(this.PointList[i]);
        	}
        	let  material = new THREE.PointsMaterial( {color: 'red', size:1} );
            let  mesh = new THREE.Points( geometry, material );
            return mesh;
    }
    
    this.CreatGponit_c=function() {
        	let geometry = new THREE.Geometry();
        	for(let i =0;i<this.PointList.length;i++)
        	{
        		this.PointList[i].x+=20;
        		this.PointList[i].y+=20;
        		this.PointList[i].z+=20;
        	    geometry.vertices.push(this.PointList[i]);
        	}
        	let  material = new THREE.PointsMaterial( {color: 'red', size:1} );
            let  mesh = new THREE.Points( geometry, material );
            return mesh;
    }
    
    this.SetMaterial = function(m)
    {
    	this.Material = m;
    }
    
    this.CreatFace=function(){
        	let geometry = new THREE.Geometry();
        	for(let i =0;i<this.PointList.length;i++)
        	{
        	    let thispoint =this.PointList[i];
        	    geometry.vertices.push(thispoint);
        	}
        	var met = []
            let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
        	for(let j =0;j<this.Facelist.length;j++)
        	{
        		if(this.Facelist[j].length==3)
        		{
        	    var v1 = this.Facelist[j][0];
        	    var v2 = this.Facelist[j][1];
        	    var v3 = this.Facelist[j][2];
                let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
        		geometry.faces.push(face);

        		}
        	}
		    let mesh = new THREE.Mesh( geometry, this.Material);
		    
        	return mesh;
       }
     
     this.CreatMeshWithMaterial = function(m)
     {
     	let geometry = new THREE.Geometry();
        	for(let i =0;i<this.PointList.length;i++)
        	{
        	    let thispoint = this.PointList[i];
        	    geometry.vertices.push(thispoint);
        	}
        	var met = []
            let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
        	for(let j =0;j<this.Facelist.length;j++)
        	{
        		if(this.Facelist[j].length==3)
        		{
        	    var v1 = this.Facelist[j][0];
        	    var v2 = this.Facelist[j][1];
        	    var v3 = this.Facelist[j][2];
                let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
        		geometry.faces.push(face);

        		}
        	}
		    let mesh = new THREE.Mesh( geometry, m);
		    
        	return mesh;
     }
     
                
     this.GetNomal=function(point1,point2,point3){
     	  let x;
          let y;
          let z;
          x = (point2.y-point1.y)*(point3.z-point1.z) - (point2.z-point1.z)*(point3.y-point1.y);
          y = (point2.z-point1.z)*(point3.x-point1.x) - (point2.x-point1.x)*(point3.z-point1.z);
          z = (point2.x-point1.x)*(point3.y-point1.y) - (point2.y-point1.y)*(point3.x-point1.x);
          return new THREE.Vector3(x,y,z)
     };
     
     this.LoadOFFobject = function(url,m)
     {
     	this.ReadingOFFFile(url);
        return this.CreatMeshWithMaterial(m);
     }
}

可以正常运行,有问题找。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值