本文共 10279 字,大约阅读时间需要 34 分钟。
在Android OpenGLES2.0(十四)——Obj格式3D模型加载中实现了Obj格式的3D模型的加载,加载的是一个没有贴图,没有光照处理的帽子,为了呈现出立体效果,“手动”加了光照,拥有贴图的纹理及光照又该怎么加载呢?
模型文件
本篇博客例子中加载的是一个卡通形象皮卡丘,资源是在网上随便找的一个。加载出来如图所示:# 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实现材质库的解析方法tempVert; private ArrayList tempVertNorl; public ArrayList 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 data){ int size=data.size(); ByteBuffer buffer=ByteBuffer.allocateDirect(size*4); buffer.order(ByteOrder.nativeOrder()); vert=buffer.asFloatBuffer(); for (int i=0;i data){ int size=data.size(); ByteBuffer buffer=ByteBuffer.allocateDirect(size*4); buffer.order(ByteOrder.nativeOrder()); vertNorl=buffer.asFloatBuffer(); for (int i=0;i data){ int size=data.size(); ByteBuffer buffer=ByteBuffer.allocateDirect(size*4); buffer.order(ByteOrder.nativeOrder()); vertTexture=buffer.asFloatBuffer(); for (int i=0;i
public static HashMap实现3D对象拆分解析的方法readMtl(InputStream stream){ HashMap 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 list){ for (int i=1;i
public static List顶点着色器及片元着色器 顶点着色器readMultiObj(Context context,String file){ boolean isAssets; ArrayList data=new ArrayList<>(); ArrayList oVs=new ArrayList ();//原始顶点坐标列表 ArrayList oVNs=new ArrayList<>(); //原始顶点法线列表 ArrayList oVTs=new ArrayList<>(); //原始贴图坐标列表 HashMap mTls=null; HashMap 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 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 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中,进而进行渲染:
Listmodel=ObjReader.readMultiObj(this,"assets/3dres/pikachu.obj");List filters=new ArrayList<>();for (int i=0;i
OK,至此大功告成。