Android OpenGLES2.0(十六)——3D模型贴图及光照处理(obj+mtl)

2017-02-28 08:26:52来源:CSDN作者:junzia人点击

在Android OpenGLES2.0(十四)——Obj格式3D模型加载中实现了Obj格式的3D模型的加载,加载的是一个没有贴图,没有光照处理的帽子,为了呈现出立体效果,“手动”加了光照,拥有贴图的纹理及光照又该怎么加载呢?

模型文件

本篇博客例子中加载的是一个卡通形象皮卡丘,资源是在网上随便找的一个。加载出来如图所示:
这里写图片描述

obj内容格式如下:

# Wavefront OBJ file# Exported by Misfit Model 3D 1.3.8# Thu Sep 27 20:02:52 2012mtllib pikachu.mtl# 191 Verticesv 34.493484 75.31411 -39.308891v 27.34606 45.516556 -47.155548#...省略若干行vt 0.859513 0.676464vt 0.769048 0.0597#...省略若干行vn -0.068504 -0.433852 -0.898376vn 0.422088 -0.855411 -0.30019#...省略若干行usemtl pikageno DrawCall_25g DrawCall_25f 2/1/1 1/2/2 3/3/3f 1/4/4 2/5/5 4/6/6#...省略若干行usemtl pikageno DrawCall_262g DrawCall_262f 2/58/58 3/59/59 17/60/60f 2/61/61 17/62/62 6/63/63#...省略若干行

mtl文件内容格式如下:

# Material file for pikachu.objnewmtl eye    Ns 0    d 1    illum 2    Kd 0.8 0.8 0.8    Ks 0.0 0.0 0.0    Ka 0.2 0.2 0.2    map_Kd eye1.pngnewmtl mouth    Ns 0    d 1    illum 2    Kd 0.8 0.8 0.8    Ks 0.0 0.0 0.0    Ka 0.2 0.2 0.2    map_Kd mouth1.pngnewmtl pikagen    Ns 0    d 1    illum 2    Kd 0.8 0.8 0.8    Ks 0.0 0.0 0.0    Ka 0.2 0.2 0.2    map_Kd pikagen.png

关于Obj的内容格式,在上篇博客中已经做了总结,本篇博客中使用的obj,可以看到f后面的不再跟的是4个数字,而是f 2/58/58 3/59/59 17/60/60这种样子的三组数,每一组都表示为顶点坐标索引/贴图坐标点索引/顶点法线索引,三个顶点组成一个三角形。而头部的mtllib pikachu.mtl则指明使用的材质库。
而mtl格式文件中,主要数据类型为:

newmtl name #name为材质名称Ns exponent #exponent指定材质的反射指数,定义了反射高光度Ka r g b    #环境光反射,g和b两参数是可选的,如果只指定了r的值,则g和b的值都等于r的值Kd r g b    #漫反射Ks r g b    #镜面光反射# Ka Kd Ks 都还有其他两种格式,可查阅其他资料:#Kd spectral file.rfl factor#Kd xyz x y zmap_Kd picture.png  #固有纹理贴图map_Ka picture1.png #阴影纹理贴图map_Ks picture2.png #高光纹理贴图illum 2             #光照模型#光照模型属性如下:#0. 色彩开,阴影色关#1. 色彩开,阴影色开#2. 高光开#3. 反射开,光线追踪开#4. 透明: 玻璃开 反射:光线追踪开#5. 反射:菲涅尔衍射开,光线追踪开#6. 透明:折射开 反射:菲涅尔衍射关,光线追踪开#7. 透明:折射开 反射:菲涅尔衍射开,光线追踪开#8. 反射开,光线追踪关#9. 透明: 玻璃开 反射:光线追踪关#10. 投射阴影于不可见表面

模型及贴图加载

模型加载和之前的模型加载大同小异,不同的是,这次我们需要将模型的贴图坐标、顶点法线也一起加载,并传入到shader中。其他参数,有的自然也要取到。
模型加载以obj文件为入口,解析obj文件,从中获取到mtl文件相对路径,然后解析mtl文件。将材质库拆分为诸多的单一材质。obj对象的 加载,根据具使用材质不同来分解为多个3D模型。具体加载过程如下:

建立保存单个材质的类

public class MtlInfo {    //还有其他相关信息,需要的时候一起添加进来    public String newmtl;    public float[] Ka=new float[3];     //阴影色    public float[] Kd=new float[3];     //固有色    public float[] Ks=new float[3];     //高光色    public float[] Ke=new float[3];     //    public float Ns;                    //shininess    public String map_Kd;               //固有纹理贴图    public String map_Ks;               //高光纹理贴图    public String map_Ka;               //阴影纹理贴图    //denotes the illumination model used by the material.    // illum = 1 indicates a flat material with no specular highlights,    // so the value of Ks is not used.    // illum = 2 denotes the presence of specular highlights,    // and so a specification for Ks is required.    public int illum;}

建立保存拥有单一材质的3D对象的类

public class Obj3D {    public FloatBuffer vert;    public int vertCount;    public FloatBuffer vertNorl;    public FloatBuffer vertTexture;    public MtlInfo mtl;    private ArrayList<Float> tempVert;    private ArrayList<Float> tempVertNorl;    public ArrayList<Float> tempVertTexture;    public int textureSMode;    public int textureTMode;    public void addVert(float d){        if(tempVert==null){            tempVert=new ArrayList<>();        }        tempVert.add(d);    }    public void addVertTexture(float d){        if(tempVertTexture==null){            tempVertTexture=new ArrayList<>();        }        tempVertTexture.add(d);    }    public void addVertNorl(float d){        if(tempVertNorl==null){            tempVertNorl=new ArrayList<>();        }        tempVertNorl.add(d);    }    public void dataLock(){        if(tempVert!=null){            setVert(tempVert);            tempVert.clear();            tempVert=null;        }        if(tempVertTexture!=null){            setVertTexture(tempVertTexture);            tempVertTexture.clear();            tempVertTexture=null;        }        if(tempVertNorl!=null){            setVertNorl(tempVertNorl);            tempVertNorl.clear();            tempVertNorl=null;        }    }    public void setVert(ArrayList<Float> data){        int size=data.size();        ByteBuffer buffer=ByteBuffer.allocateDirect(size*4);        buffer.order(ByteOrder.nativeOrder());        vert=buffer.asFloatBuffer();        for (int i=0;i<size;i++){            vert.put(data.get(i));        }        vert.position(0);        vertCount=size/3;    }    public void setVertNorl(ArrayList<Float> data){        int size=data.size();        ByteBuffer buffer=ByteBuffer.allocateDirect(size*4);        buffer.order(ByteOrder.nativeOrder());        vertNorl=buffer.asFloatBuffer();        for (int i=0;i<size;i++){            vertNorl.put(data.get(i));        }        vertNorl.position(0);    }    public void setVertTexture(ArrayList<Float> data){        int size=data.size();        ByteBuffer buffer=ByteBuffer.allocateDirect(size*4);        buffer.order(ByteOrder.nativeOrder());        vertTexture=buffer.asFloatBuffer();        for (int i=0;i<size;){            vertTexture.put(data.get(i));            i++;            vertTexture.put(data.get(i));            i++;        }        vertTexture.position(0);    }}

实现材质库的解析方法

public static HashMap<String,MtlInfo> readMtl(InputStream stream){    HashMap<String,MtlInfo> map=new HashMap<>();    try{        InputStreamReader isr=new InputStreamReader(stream);        BufferedReader br=new BufferedReader(isr);        String temps;        MtlInfo mtlInfo=new MtlInfo();        while((temps=br.readLine())!=null)        {            String[] tempsa=temps.split("[ ]+");            switch (tempsa[0].trim()){                case "newmtl":  //材质                    mtlInfo=new MtlInfo();                    mtlInfo.newmtl=tempsa[1];                    map.put(tempsa[1],mtlInfo);                    break;                case "illum":     //光照模型                    mtlInfo.illum=Integer.parseInt(tempsa[1]);                    break;                case "Kd":                    read(tempsa,mtlInfo.Kd);                    break;                case "Ka":                    read(tempsa,mtlInfo.Ka);                    break;                case "Ke":                    read(tempsa,mtlInfo.Ke);                    break;                case "Ks":                    read(tempsa,mtlInfo.Ks);                    break;                case "Ns":                    mtlInfo.Ns=Float.parseFloat(tempsa[1]);                case "map_Kd":                    mtlInfo.map_Kd=tempsa[1];                    break;            }        }    }catch (Exception e){        e.printStackTrace();    }    return map;}private static void read(String[] value,ArrayList<Float> list){    for (int i=1;i<value.length;i++){        list.add(Float.parseFloat(value[i]));    }}private static void read(String[] value,float[] fv){    for (int i=1;i<value.length&&i<fv.length+1;i++){        fv[i-1]=Float.parseFloat(value[i]);    }}

实现3D对象拆分解析的方法

public static List<Obj3D> readMultiObj(Context context,String file){    boolean isAssets;    ArrayList<Obj3D> data=new ArrayList<>();    ArrayList<Float> oVs=new ArrayList<Float>();//原始顶点坐标列表    ArrayList<Float> oVNs=new ArrayList<>();    //原始顶点法线列表    ArrayList<Float> oVTs=new ArrayList<>();    //原始贴图坐标列表    HashMap<String,MtlInfo> mTls=null;    HashMap<String,Obj3D> mObjs=new HashMap<>();    Obj3D nowObj=null;    MtlInfo nowMtl=null;    try{        String parent;        InputStream inputStream;        if (file.startsWith("assets/")){            isAssets=true;            String path=file.substring(7);            parent=path.substring(0,path.lastIndexOf("/")+1);            inputStream=context.getAssets().open(path);            Log.e("obj",parent);        }else{            isAssets=false;            parent=file.substring(0,file.lastIndexOf("/")+1);            inputStream=new FileInputStream(file);        }        InputStreamReader isr=new InputStreamReader(inputStream);        BufferedReader br=new BufferedReader(isr);        String temps;        while((temps=br.readLine())!=null){            if("".equals(temps)){            }else{                String[] tempsa=temps.split("[ ]+");                switch (tempsa[0].trim()){                    case "mtllib":  //材质                        InputStream stream;                        if (isAssets){                            stream=context.getAssets().open(parent+tempsa[1]);                        }else{                            stream=new FileInputStream(parent+tempsa[1]);                        }                        mTls=readMtl(stream);                        break;                    case "usemtl":  //采用纹理                        if(mTls!=null){                            nowMtl=mTls.get(tempsa[1]);                        }                        if(mObjs.containsKey(tempsa[1])){                            nowObj=mObjs.get(tempsa[1]);                        }else{                            nowObj=new Obj3D();                            nowObj.mtl=nowMtl;                            mObjs.put(tempsa[1],nowObj);                        }                        break;                    case "v":       //原始顶点                        read(tempsa,oVs);                        break;                    case "vn":      //原始顶点法线                        read(tempsa,oVNs);                        break;                    case "vt":                        read(tempsa,oVTs);                        break;                    case "f":                        for (int i=1;i<tempsa.length;i++){                            String[] fs=tempsa[i].split("/");                            int index;                            if(fs.length>0){                                //顶点索引                                index=Integer.parseInt(fs[0])-1;                                nowObj.addVert(oVs.get(index*3));                                nowObj.addVert(oVs.get(index*3+1));                                nowObj.addVert(oVs.get(index*3+2));                            }                            if(fs.length>1){                                //贴图                                index=Integer.parseInt(fs[1])-1;                                nowObj.addVertTexture(oVTs.get(index*2));                                nowObj.addVertTexture(oVTs.get(index*2+1));                            }                            if(fs.length>2){                                //法线索引                                index=Integer.parseInt(fs[2])-1;                                nowObj.addVertNorl(oVNs.get(index*3));                                nowObj.addVertNorl(oVNs.get(index*3+1));                                nowObj.addVertNorl(oVNs.get(index*3+2));                            }                        }                        break;                }            }        }    }catch (Exception e){        e.printStackTrace();    }    for (Map.Entry<String, Obj3D> stringObj3DEntry : mObjs.entrySet()) {        Obj3D obj = stringObj3DEntry.getValue();        data.add(obj);        obj.dataLock();    }    return data;}

顶点着色器及片元着色器

顶点着色器

attribute vec3 vPosition;attribute vec2 vCoord;uniform mat4 vMatrix;uniform vec3 vKa;uniform vec3 vKd;uniform vec3 vKs;varying vec2 textureCoordinate;attribute vec3 vNormal;         //法向量varying vec4 vDiffuse;          //用于传递给片元着色器的散射光最终强度varying vec4 vAmbient;          //用于传递给片元着色器的环境光最终强度varying vec4 vSpecular;          //用于传递给片元着色器的镜面光最终强度void main(){    gl_Position = vMatrix*vec4(vPosition,1);    textureCoordinate = vCoord;    vec3 lightLocation=vec3(0.0,-200.0,-500.0);      //光照位置    vec3 camera=vec3(0,200.0,0);    float shininess=10.0;             //粗糙度,越小越光滑    vec3 newNormal=normalize((vMatrix*vec4(vNormal+vPosition,1)).xyz-(vMatrix*vec4(vPosition,1)).xyz);    vec3 vp=normalize(lightLocation-(vMatrix*vec4(vPosition,1)).xyz);    vDiffuse=vec4(vKd,1.0)*max(0.0,dot(newNormal,vp));                //计算散射光的最终强度    vec3 eye= normalize(camera-(vMatrix*vec4(vPosition,1)).xyz);    vec3 halfVector=normalize(vp+eye);    //求视线与光线的半向量    float nDotViewHalfVector=dot(newNormal,halfVector);   //法线与半向量的点积    float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess));     //镜面反射光强度因子    vSpecular=vec4(vKs,1.0)*powerFactor;               //计算镜面光的最终强度    vAmbient=vec4(vKa,1.0);}

片元着色器

precision mediump float;varying vec2 textureCoordinate;uniform sampler2D vTexture;varying vec4 vDiffuse;          //接收从顶点着色器过来的散射光分量varying vec4 vAmbient;          //接收传递给片元着色器的环境光分量varying vec4 vSpecular;         //接收传递给片元着色器的镜面光分量void main() {    vec4 finalColor=texture2D(vTexture,textureCoordinate);    gl_FragColor=finalColor*vAmbient+finalColor*vSpecular+finalColor*vDiffuse;}

启动加载及渲染

完成了以上准备工作,就可以调用readMultiObj方法,将obj文件读成一个或多个带有各项参数的3D模型类,然后将每一个3D模型的参数传入shader中,进而进行渲染:

List<Obj3D> model=ObjReader.readMultiObj(this,"assets/3dres/pikachu.obj");List<ObjFilter2> filters=new ArrayList<>();for (int i=0;i<model.size();i++){    ObjFilter2 f=new ObjFilter2(getResources());    f.setObj3D(model.get(i));    filters.add(f);}mGLView.setRenderer(new GLSurfaceView.Renderer() {    @Override    public void onSurfaceCreated(GL10 gl, EGLConfig config) {        for (ObjFilter2 f:filters){            f.create();        }    }    @Override    public void onSurfaceChanged(GL10 gl, int width, int height) {        for (ObjFilter2 f:filters){            f.onSizeChanged(width, height);            float[] matrix= Gl2Utils.getOriginalMatrix();            Matrix.translateM(matrix,0,0,-0.3f,0);            Matrix.scaleM(matrix,0,0.008f,0.008f*width/height,0.008f);            f.setMatrix(matrix);        }    }    @Override    public void onDrawFrame(GL10 gl) {        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);        for (ObjFilter2 f:filters){            Matrix.rotateM(f.getMatrix(),0,0.3f,0,1,0);            f.draw();        }    }});mGLView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

OK,至此大功告成。

源码

所有的代码全部在一个项目中,托管在Github上——Android OpenGLES 2.0系列博客的Demo


欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/58272305]


最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台