# OpenGL ES——导入.stl格式的3D模型

## STL Format

STL是一种文件格式，格式如：

```明码://字符段意义
solidfilenamestl//文件路径及文件名
facetnormalxyz//三角面片法向量的3个分量值
outerloop
vertexxyz//三角面片第一个顶点坐标
vertexxyz//三角面片第二个顶点坐标
vertexxyz//三角面片第三个顶点坐标
endloop
endfacet//完成一个三角面片定义

......//其他facet

endsolidfilenamestl//整个STL文件定义结束```

### 属性位

After these follows a 2-byte ("short") unsigned integer that is the "attribute byte count" – in the standard format, this should be zero because most software does not understand anything else.[6]

### 解析

```public class STLPoint {
public float x;
public float y;
public float z;

public STLPoint(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;

}
}```
```public class STLUtils {

public static FloatBuffer floatToBuffer(float[] a) {
//先初始化buffer，数组的长度*4，因为一个float占4个字节
ByteBuffer bb = ByteBuffer.allocateDirect(a.length * 4);
//数组排序用nativeOrder
bb.order(ByteOrder.nativeOrder());
FloatBuffer buffer = bb.asFloatBuffer();
buffer.put(a);
buffer.position(0);
return buffer;
}

public static int byte4ToInt(byte[] bytes, int offset) {
int b3 = bytes[offset + 3] & 0xFF;
int b2 = bytes[offset + 2] & 0xFF;
int b1 = bytes[offset + 1] & 0xFF;
int b0 = bytes[offset + 0] & 0xFF;
return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
}

public static short byte2ToShort(byte[] bytes, int offset) {
int b1 = bytes[offset + 1] & 0xFF;
int b0 = bytes[offset + 0] & 0xFF;
return (short) ((b1 << 8) | b0);
}

public static float byte4ToFloat(byte[] bytes, int offset) {

return Float.intBitsToFloat(byte4ToInt(bytes, offset));
}

}```
```import java.io.IOException;
import java.io.InputStream;
import java.nio.FloatBuffer;

/**
* Package com.hc.opengl
* Created by HuaChao on 2016/7/28.
*/
public class STLModel {
//三角面个数
private int facetCount;
//顶点坐标数组
private float[] verts;
//每个顶点对应的法向量数组
private float[] vnorms;
//每个三角面的属性信息
private short[] remarks;

//顶点数组转换而来的Buffer
private FloatBuffer vertBuffer;

//每个顶点对应的法向量转换而来的Buffer
private FloatBuffer vnormBuffer;
//以下分别保存所有点在x,y,z方向上的最大值、最小值
float maxX;
float minX;
float maxY;
float minY;
float maxZ;
float minZ;

//返回模型的中心点
public STLPoint getCentrePoint() {
float cx = minX + (maxX - minX) / 2;
float cy = minY + (maxY - minY) / 2;
float cz = minZ + (maxZ - minZ) / 2;
return new STLPoint(cx, cy, cz);
}

//包裹模型的最大半径
public float getR() {
float dx = (maxX - minX);
float dy = (maxY - minY);
float dz = (maxZ - minZ);
float max = dx;
if (dy > max)
max = dy;
if (dz > max)
max = dz;
return max;
}

//设置顶点数组的同时，设置对应的Buffer
public void setVerts(float[] verts) {
this.verts = verts;
vertBuffer = STLUtils.floatToBuffer(verts);
}

//设置顶点数组法向量的同时，设置对应的Buffer
public void setVnorms(float[] vnorms) {
this.vnorms = vnorms;
vnormBuffer = STLUtils.floatToBuffer(vnorms);
}

//···
//其他属性对应的setter、getter函数
//···

public int getFacetCount() {
return facetCount;
}

public void setFacetCount(int facetCount) {
this.facetCount = facetCount;
}

public float[] getVerts() {
return verts;
}

public float[] getVnorms() {
return vnorms;
}

public short[] getRemarks() {
return remarks;
}

public void setRemarks(short[] remarks) {
this.remarks = remarks;
}

public FloatBuffer getVertBuffer() {
return vertBuffer;
}

public void setVertBuffer(FloatBuffer vertBuffer) {
this.vertBuffer = vertBuffer;
}

public FloatBuffer getVnormBuffer() {
return vnormBuffer;
}

public void setVnormBuffer(FloatBuffer vnormBuffer) {
this.vnormBuffer = vnormBuffer;
}

public void parserBinStl(InputStream in) throws IOException{
//前面80字节是文件头，用于存贮文件名；
in.skip(80);

//紧接着用 4 个字节的整数来描述模型的三角面片个数
byte[] bytes = new byte[4];
int facetCount = STLUtils.byte4ToInt(bytes, 0);
setFacetCount(facetCount);
if (facetCount == 0) {
in.close();
return ;
}

// 每个三角面片占用固定的50个字节
byte[] facetBytes = new byte[50 * facetCount];
// 将所有的三角面片读取到字节数组
//数据读取完毕后，可以把输入流关闭
in.close();

parseModel( facetBytes);

}

/**
* 解析模型数据，包括顶点数据、法向量数据、所占空间范围等
*/
private void parseModel(byte[] facetBytes) {
int facetCount = getFacetCount();
/**
*  每个三角面片占用固定的50个字节,50字节当中：
*  三角片的法向量：（1个向量相当于一个点）*（3维/点）*（4字节浮点数/维）=12字节
*  三角片的三个点坐标：（3个点）*（3维/点）*（4字节浮点数/维）=36字节
*  最后2个字节用来描述三角面片的属性信息
* **/
// 保存所有顶点坐标信息,一个三角形3个顶点，一个顶点3个坐标轴
float[] verts = new float[facetCount * 3 * 3];
// 保存所有三角面对应的法向量位置，
// 一个三角面对应一个法向量，一个法向量有3个点
// 而绘制模型时，是针对需要每个顶点对应的法向量，因此存储长度需要*3
// 又同一个三角面的三个顶点的法向量是相同的，
// 因此后面写入法向量数据的时候，只需连续写入3个相同的法向量即可
float[] vnorms = new float[facetCount * 3 * 3];
//保存所有三角面的属性信息
//After these follows a 2-byte ("short") unsigned integer that is the "attribute byte count" – in the standard format, this should be zero because most software does not understand anything else.[6] from wiki
short[] remarks = new short[facetCount];

int stlOffset = 0;
try {
for (int i = 0; i < facetCount; i++) {

for (int j = 0; j < 4; j++) {
float x = STLUtils.byte4ToFloat(facetBytes, stlOffset);
float y = STLUtils.byte4ToFloat(facetBytes, stlOffset + 4);
float z = STLUtils.byte4ToFloat(facetBytes, stlOffset + 8);
stlOffset += 12;

if (j == 0) {//法向量
vnorms[i * 9] = x;
vnorms[i * 9 + 1] = y;
vnorms[i * 9 + 2] = z;
vnorms[i * 9 + 3] = x;
vnorms[i * 9 + 4] = y;
vnorms[i * 9 + 5] = z;
vnorms[i * 9 + 6] = x;
vnorms[i * 9 + 7] = y;
vnorms[i * 9 + 8] = z;
} else {//三个顶点
verts[i * 9 + (j - 1) * 3] = x;
verts[i * 9 + (j - 1) * 3 + 1] = y;
verts[i * 9 + (j - 1) * 3 + 2] = z;

//记录模型中三个坐标轴方向的最大最小值
if (i == 0 && j == 1) {
minX = maxX = x;
minY = maxY = y;
minZ = maxZ = z;
} else {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
minZ = Math.min(minZ, z);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
maxZ = Math.max(maxZ, z);
}
}
}
short r = STLUtils.byte2ToShort(facetBytes, stlOffset);
stlOffset = stlOffset + 2;
remarks[i] = r;
}
} catch (Exception e) {

}
//将读取的数据设置到Model对象中
setVerts(verts);
setVnorms(vnorms);
setRemarks(remarks);

}

}```

## 渲染3D模型

### 创建画布

```    private STLModel model;
private STLPoint mCenterPoint;
private STLPoint eye = new STLPoint(0, 0, -3);
private STLPoint up = new STLPoint(0, 1, 0);
private STLPoint center = new STLPoint(0, 0, 0);
private float mScalef = 1;
private float mDegree = 0;

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glEnable(GL10.GL_DEPTH_TEST); // 启用深度缓存
gl.glClearDepthf(1.0f); // 设置深度缓存值
gl.glDepthFunc(GL10.GL_LEQUAL); // 设置深度缓存比较函数
float r = model.getR();
//r是半径，不是直径，因此用0.5/r可以算出放缩比例
mScalef = 0.5f / r;
mCenterPoint = model.getCentrePoint();
}```

• 开启深度缓存和阴影模式
• 计算缩放比例
• 计算中心点

### 设置投影矩阵

```    @Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

// 设置OpenGL场景的大小,(0,0)表示窗口内部视口的左下角，(width, height)指定了视口的大小
gl.glViewport(0, 0, width, height);

gl.glMatrixMode(GL10.GL_PROJECTION); // 设置投影矩阵
GLU.gluPerspective(gl, 45.0f, ((float) width) / height, 1f, 100f);// 设置透视范围

//以下两句声明，以后所有的变换都是针对模型(即我们绘制的图形)
gl.glMatrixMode(GL10.GL_MODELVIEW);
}```

```    @Override
public void onDrawFrame(GL10 gl) {
// 清除屏幕和深度缓存
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

//眼睛对着原点看
GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
center.y, center.z, up.x, up.y, up.z);

//为了能有立体感觉，通过改变mDegree值，让模型不断旋转
gl.glRotatef(mDegree, 0, 1, 0);

//将模型放缩到View刚好装下
gl.glScalef(mScalef, mScalef, mScalef);
//把模型移动到原点
gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
-mCenterPoint.z);

//===================begin==============================//

//允许给每个顶点设置法向量
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
// 允许设置顶点
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 允许设置颜色

//设置法向量数据源
gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
// 设置三角形顶点数据源
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());

// 绘制三角形
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);

// 取消顶点设置
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
//取消法向量设置
gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);

//=====================end============================//

}```

### gluLookAt

gluLookAt方法非常有趣。它决定了我们看物体的角度。 想象一下，当我们要看一个物体时，我们有三个属于可以改变：

1. 我的眼睛的位置
2. 物体的位置
3. 我目光的角度 eyecenterup这三个量就决定了这三个属性。 其中up = [0,1,0]时，表示我是正着头在看，up=[1,1,0]，表示我是歪着头45度在看。依此类推。

76 篇文章29 人订阅

0 条评论

## 相关文章

### 数据结构 数组和广义表以及树的基本概念

2-1 设有一个10阶的对称矩阵A，采用压缩存储方式，以行序为主存储，a11为第一元素，其存储地址为1，每个元素占一个地址空间，则a85的地址为 (2分) 1...

27380

### 1038 一元三次方程求解

1038 一元三次方程求解 2001年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 白银 Silver ...

30980

84650

39680

### 翻转瓷砖Fliptile（开关反转）- POJ 3279

Farmer John knows that an intellectually satisfied cow is a happy cow who will g...

12020

91600

15010

309100

### Python数据处理从零开始----第四章（可视化）（7）（多图合并）目录正文

=========================================================

9410

40280