Android OpenGLES2.0(十四)——Obj格式3D模型加载
2018-01-15 17:26
357 查看
转自:http://blog.csdn.net/junzia/article/details/54300202
在博主《OpenGLES系列》文章中,最开始的几篇讲的就是OpenGL世界中各种形体的构建,但是那些形体都是规则的简单形体,遇到复杂的形体,比如说一个人、一朵花,怎么办呢?自然是通过其他工具类似于Maya、3DMax等3D建模工具,做好模型导出来,然后用OpenGLES加载导出的模型文件。模型的加载大同小异,本篇博客是以Obj格式的3D模型为例。
格式如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[/code]
加载这个模型文件前,我们需要先知道这些数据代表的是什么。针对这个文件,#号开头的,是描述模型文件的相关信息。以v开头的,表示的是顶点坐标。以f开头的,表示一个面,后面跟的四个值是索引。一个v,后面的三个数,代表一个点的xyz,4个点组成了一个四边形。
为什么是4个点?不是说在OpenGLES中基本几何是三角形么?这样问就有点尴尬了,因为模型文件是我在网上随便下的,自己选的模型,跪着也要加载出来。有什么关系,一个四边形不就是两个三角形么。
这个模型文件只有v、f两类数据,但是一个炫酷的模型,往往是包含很多数据的,主要的数据类型如下:
顶点数据(Vertex data):
v 几何体顶点(Geometric vertices)
vt 贴图坐标点(Texture vertices)
vn 顶点法线(Vertex normals)
vp 参数空格顶点 (Parameter space vertices)
自由形态曲线(Free-form curve)/表面属性(surface attributes):
deg 度(Degree)
bmat 基础矩阵(Basis matrix)
step 步尺寸(Step size)
cstype 曲线或表面类型 (Curve or surface type)
元素(Elements):
p 点(Point)
l 线(Line)
f 面(Face)
curv 曲线(Curve)
curv2 2D曲线(2D curve)
surf 表面(Surface)
自由形态曲线(Free-form curve)/表面主体陈述(surface body statements):
parm 参数值(Parameter values )
trim 外部修剪循环(Outer trimming loop)
hole 内部整修循环(Inner trimming loop)
scrv 特殊曲线(Special curve)
sp 特殊的点(Special point)
end 结束陈述(End statement)
自由形态表面之间的连接(Connectivity between free-form surfaces):
con 连接 (Connect)
成组(Grouping):
g 组名称(Group name)
s 光滑组(Smoothing group)
mg 合并组(Merging group)
o 对象名称(Object name)
显示(Display)/渲染属性(render attributes):
bevel 导角插值(Bevel interpolation)
c_interp 颜色插值(Color interpolation)
d_interp 溶解插值(Dissolve interpolation)
lod 细节层次(Level of detail)
usemtl 材质名称(Material name)
mtllib 材质库(Material library)
shadow_obj 投射阴影(Shadow casting)
trace_obj 光线跟踪(Ray tracing)
ctech 曲线近似技术(Curve approximation technique)
stech 表面近似技术 (Surface approximation technique)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
[/code]
顶点Shader为:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[/code]
片元Shader:
2
3
4
5
6
7
8
9
[/code]
着色器中散射光强度的计算,是根据散射光的公式来的,光照公式在Android OpenGLES2.0(一)——了解OpenGLES2.0光照中有讲到。
编译着色器,linkProgram,传入从Obj文件读取的值,然后和渲染一个立方体一样,渲染出模型就OK了。
欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/54300202]
在博主《OpenGLES系列》文章中,最开始的几篇讲的就是OpenGL世界中各种形体的构建,但是那些形体都是规则的简单形体,遇到复杂的形体,比如说一个人、一朵花,怎么办呢?自然是通过其他工具类似于Maya、3DMax等3D建模工具,做好模型导出来,然后用OpenGLES加载导出的模型文件。模型的加载大同小异,本篇博客是以Obj格式的3D模型为例。
模型文件
本篇博客例子中加载的是一个帽子,资源是在网上随便找的一个。加载出来如图所示:格式如下:
# File exported by ZBrush version 4.2 # www.zbrush.com #Vertex Count 4898 #Face Count 4848 #Auto scale x=0.211538 y=0.211538 z=0.211538 #Auto offset x=-0.000000 y=-0.412507 z=-0.000000 v -0.62500745 3.93329608 0.0000001 v -0.00002446 3.32622414 1.33471741 v 1.47657442 2.55452877 1.37523436 v -1.01934254 3.90772931 0.00000007 ...省略若干行... g default f 990 991 987 986 f 991 874 873 987 f 972 971 991 990 f 971 55 874 991 f 987 992 988 986 ...省略若干行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[/code]
加载这个模型文件前,我们需要先知道这些数据代表的是什么。针对这个文件,#号开头的,是描述模型文件的相关信息。以v开头的,表示的是顶点坐标。以f开头的,表示一个面,后面跟的四个值是索引。一个v,后面的三个数,代表一个点的xyz,4个点组成了一个四边形。
为什么是4个点?不是说在OpenGLES中基本几何是三角形么?这样问就有点尴尬了,因为模型文件是我在网上随便下的,自己选的模型,跪着也要加载出来。有什么关系,一个四边形不就是两个三角形么。
这个模型文件只有v、f两类数据,但是一个炫酷的模型,往往是包含很多数据的,主要的数据类型如下:
顶点数据(Vertex data):
v 几何体顶点(Geometric vertices)
vt 贴图坐标点(Texture vertices)
vn 顶点法线(Vertex normals)
vp 参数空格顶点 (Parameter space vertices)
自由形态曲线(Free-form curve)/表面属性(surface attributes):
deg 度(Degree)
bmat 基础矩阵(Basis matrix)
step 步尺寸(Step size)
cstype 曲线或表面类型 (Curve or surface type)
元素(Elements):
p 点(Point)
l 线(Line)
f 面(Face)
curv 曲线(Curve)
curv2 2D曲线(2D curve)
surf 表面(Surface)
自由形态曲线(Free-form curve)/表面主体陈述(surface body statements):
parm 参数值(Parameter values )
trim 外部修剪循环(Outer trimming loop)
hole 内部整修循环(Inner trimming loop)
scrv 特殊曲线(Special curve)
sp 特殊的点(Special point)
end 结束陈述(End statement)
自由形态表面之间的连接(Connectivity between free-form surfaces):
con 连接 (Connect)
成组(Grouping):
g 组名称(Group name)
s 光滑组(Smoothing group)
mg 合并组(Merging group)
o 对象名称(Object name)
显示(Display)/渲染属性(render attributes):
bevel 导角插值(Bevel interpolation)
c_interp 颜色插值(Color interpolation)
d_interp 溶解插值(Dissolve interpolation)
lod 细节层次(Level of detail)
usemtl 材质名称(Material name)
mtllib 材质库(Material library)
shadow_obj 投射阴影(Shadow casting)
trace_obj 光线跟踪(Ray tracing)
ctech 曲线近似技术(Curve approximation technique)
stech 表面近似技术 (Surface approximation technique)
模型加载
知道了模型文件的内容和格式,加载起来就不是什么问题了:public class ObjReader { public static void read(InputStream stream,Obj3D obj3D){ ArrayList<Float> alv=new ArrayList<Float>();//原始顶点坐标列表 ArrayList<Float> alvResult=new ArrayList<Float>();//结果顶点坐标列表 ArrayList<Float> norlArr=new ArrayList<>(); float[] ab=new float[3],bc=new float[3],norl=new float[3]; try{ InputStreamReader isr=new InputStreamReader(stream); BufferedReader br=new BufferedReader(isr); String temps=null; while((temps=br.readLine())!=null) { String[] tempsa=temps.split("[ ]+"); if(tempsa[0].trim().equals("v")) {//此行为顶点坐标 alv.add(Float.parseFloat(tempsa[1])); alv.add(Float.parseFloat(tempsa[2])); alv.add(Float.parseFloat(tempsa[3])); } else if(tempsa[0].trim().equals("f")) {//此行为三角形面 int a=Integer.parseInt(tempsa[1])-1; int b=Integer.parseInt(tempsa[2])-1; int c=Integer.parseInt(tempsa[3])-1; int d=Integer.parseInt(tempsa[4])-1; //abc和acd两个三角形组成的四边形 alvResult.add(alv.get(a*3)); alvResult.add(alv.get(a*3+1)); alvResult.add(alv.get(a*3+2)); alvResult.add(alv.get(b*3)); alvResult.add(alv.get(b*3+1)); alvResult.add(alv.get(b*3+2)); alvResult.add(alv.get(c*3)); alvResult.add(alv.get(c*3+1)); alvResult.add(alv.get(c*3+2)); alvResult.add(alv.get(a*3)); alvResult.add(alv.get(a*3+1)); alvResult.add(alv.get(a*3+2)); alvResult.add(alv.get(c*3)); alvResult.add(alv.get(c*3+1)); alvResult.add(alv.get(c*3+2)); alvResult.add(alv.get(d*3)); alvResult.add(alv.get(d*3+1)); alvResult.add(alv.get(d*3+2)); //这里也是因为下载模型文件的坑。下了个出了顶点和面啥也没有的模型文件 //为了有3d效果,给它加个光照,自己计算顶点法线 //用面法向量策略。按理说点法向量更适合这种光滑的3D模型,但是计算起来太复杂了,so //既然主要讲3D模型加载,就先用面法向量策略来吧 //通常3D模型里面会包含法向量信息的。 //法向量的计算,ABC三个空间点,他们的法向量为向量AB与向量BC的外积,所以有: for (int i=0;i<3;i++){ ab[i]=alv.get(a*3+i)-alv.get(b*3+i); bc[i]=alv.get(b*3+i)-alv.get(c*3+i); } norl[0]=ab[1]*bc[2]-ab[2]*bc[1]; norl[1]=ab[2]*bc[0]-ab[0]*bc[2]; norl[2]=ab[0]*bc[1]-ab[1]*bc[0]; //上面两个三角形,传入了6个顶点,这里循环6次,简单粗暴 for (int i=0;i<6;i++){ norlArr.add(norl[0]); norlArr.add(norl[1]); norlArr.add(norl[2]); } } } //这些就是比较熟悉的了,一切都为了能够把数据给GPU int size=alvResult.size(); float[] vXYZ=new float[size]; for(int i=0;i<size;i++){ vXYZ[i]=alvResult.get(i); } ByteBuffer byteBuffer=ByteBuffer.allocateDirect(4*size); byteBuffer.order(ByteOrder.nativeOrder()); obj3D.vert=byteBuffer.asFloatBuffer(); obj3D.vert.put(vXYZ); obj3D.vert.position(0); obj3D.vertCount=size/3; int vbSize=norlArr.size(); float[] vbArr=new float[size]; for(int i=0;i<size;i++){ vbArr[i]=norlArr.get(i); } ByteBuffer vb=ByteBuffer.allocateDirect(4*vbSize); vb.order(ByteOrder.nativeOrder()); obj3D.vertNorl=vb.asFloatBuffer(); obj3D.vertNorl.put(vbArr); obj3D.vertNorl.position(0); }catch(Exception e){ e.printStackTrace(); } } public static class Obj3D{ public FloatBuffer vert; public int vertCount; public FloatBuffer vertNorl; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
[/code]
模型渲染
模型的渲染,和之前绘制各种形体也差不多了,往GPU传数据就不用说了,为了让3D模型呈现出立体效果,示例中,增加了简单而不靠谱的光照。所以看得出来,虽然加载出来有立体效果,但是能看到比较明显的网格。当然,光照不是本篇博客的重点,在后续博客里面再详细讨论下光照的问题。顶点Shader为:
attribute vec3 vPosition; attribute vec2 vCoord; uniform mat4 vMatrix; varying vec2 textureCoordinate; attribute vec3 vNormal; //法向量 varying vec4 vDiffuse; //用于传递给片元着色器的散射光最终强度 //返回散射光强度 vec4 pointLight(vec3 normal,vec3 lightLocation,vec4 lightDiffuse){ //变换后的法向量 vec3 newTarget=normalize((vMatrix*vec4(normal+vPosition,1)).xyz-(vMatrix*vec4(vPosition,1)).xyz); //表面点与光源的方向向量 vec3 vp=normalize(lightLocation-(vMatrix*vec4(vPosition,1)).xyz); return lightDiffuse*max(0.0,dot(newTarget,vp)); } void main(){ gl_Position = vMatrix*vec4(vPosition,1); textureCoordinate = vCoord; vec4 at=vec4(1.0,1.0,1.0,1.0); //光照强度 vec3 pos=vec3(50.0,200.0,50.0); //光照位置 vDiffuse=pointLight(vNormal,pos,at); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[/code]
片元Shader:
precision mediump float; varying vec2 textureCoordinate; uniform sampler2D vTexture; varying vec4 vDiffuse;//接收从顶点着色器过来的散射光分量 void main() { vec4 finalColor=vec4(1.0); //给此片元颜色值 gl_FragColor=finalColor*vDiffuse+finalColor*vec4(0.15,0.15,0.15,1.0); }1
2
3
4
5
6
7
8
9
[/code]
着色器中散射光强度的计算,是根据散射光的公式来的,光照公式在Android OpenGLES2.0(一)——了解OpenGLES2.0光照中有讲到。
编译着色器,linkProgram,传入从Obj文件读取的值,然后和渲染一个立方体一样,渲染出模型就OK了。
源码
所有的代码全部在一个项目中,托管在Github上——Android OpenGLES 2.0系列博客的Demo欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/54300202]
相关文章推荐
- Android OpenGLES2.0(十四)——Obj格式3D模型加载
- (转)使用OpenGL显示图像(七)Android OpenGLES2.0——纹理贴图之显示图片
- Android上使用OpenGLES2.0显示YUV数据
- [原]零基础学习SDL开发之在Android使用SDL2.0加载字体
- Android上使用OpenGLES2.0显示YUV数据
- Android OpenGLES2.0 直接导出YUV420数据
- Android OpenGLES2.0(十一)——利用OpenGLES做Camera预览
- Android OpenGLES2.0(十五)——利用EGL后台处理图像
- Android上使用OpenGLES2.0显示YUV数据
- Android Camera2 Opengles2.0 实时滤镜(冷暖色/放大镜/模糊/美颜)
- Android Camera2 Opengles2.0 预览图像实时滤镜 视频编码
- 零基础学习SDL开发之在Android使用SDL2.0加载字体
- Android上使用OpenGLES2.0显示YUV数据
- Android OpenGLES2.0绘制三角形(二)
- 零基础学习SDL开发之在Android使用SDL2.0加载字体
- Android OpenGLES2.0(四)——正方形和圆形
- Android OpenGLES2.0(十)——OpenGL中的平移、旋转、缩放
- OpenglES2.0 for Android:来画个矩形吧
- IOS OpenGLES2.0 入门04 加载纹理
- OpenglES2.0 for Android:来做个地球吧