【我的安卓进阶之旅】Opengl Es(8)之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

加载这个模型文件前,我们需要先知道这些数据代表的是什么。针对这个文件,#号开头的,是描述模型文件的相关信息。以v开头的,表示的是顶点坐标。以f开头的,表示一个面,后面跟的四个值是索引。一个v,后面的三个数,代表一个点的xyz,4个点组成了一个四边形。
为什么是4个点?不是说在OpenGLES中基本几何是三角形么?这样问就有点尴尬了,因为模型文件是我在网上随便下的,自己选的模型,跪着也要加载出来。有什么关系,一个四边形不就是两个三角形么。
这个模型文件只有v、f两类数据,但是一个炫酷的模型,往往是包含很多数据的,主要的数据类型如下:

  1. 顶点数据(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

    模型渲染

    模型的渲染,和之前绘制各种形体也差不多了,往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

    片元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

    编译着色器,linkProgram,传入从Obj文件读取的值,然后和渲染一个立方体一样,渲染出模型就OK了。

    源码

    附上github下载地址

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值