Android OpenGLES2.0(十六)——3D模型贴图及光照处理(obj+mtl)
Posted 湖广午王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android OpenGLES2.0(十六)——3D模型贴图及光照处理(obj+mtl)相关的知识,希望对你有一定的参考价值。
在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 2012
mtllib pikachu.mtl
# 191 Vertices
v 34.493484 75.31411 -39.308891
v 27.34606 45.516556 -47.155548
#...省略若干行
vt 0.859513 0.676464
vt 0.769048 0.0597
#...省略若干行
vn -0.068504 -0.433852 -0.898376
vn 0.422088 -0.855411 -0.30019
#...省略若干行
usemtl pikagen
o DrawCall_25
g DrawCall_25
f 2/1/1 1/2/2 3/3/3
f 1/4/4 2/5/5 4/6/6
#...省略若干行
usemtl pikagen
o DrawCall_262
g DrawCall_262
f 2/58/58 3/59/59 17/60/60
f 2/61/61 17/62/62 6/63/63
#...省略若干行
mtl文件内容格式如下:
# Material file for pikachu.obj
newmtl 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.png
newmtl 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.png
newmtl 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 z
map_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]
以上是关于Android OpenGLES2.0(十六)——3D模型贴图及光照处理(obj+mtl)的主要内容,如果未能解决你的问题,请参考以下文章
OpenglES2.0 for Android:来画个立方体吧
Android OpenGLES2.0(十八)——轻松搞定Blend颜色混合
Android OpenGLES2.0(十八)——轻松搞定Blend颜色混合
OpenglES2.0 for Android:各种变换来一波