热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

在AndroidOpenGLES2.0(十四)——Obj格式3D模型加载中实现了Obj格式的3D模型的加载,加载的是一个没有贴图,没有光照处理的帽子,为了呈现出立体效果,“手动”加了

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

模型文件

本篇博客例子中加载的是一个卡通形象皮卡丘,资源是在网上随便找的一个。加载出来如图所示:
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 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 vert.put(data.get(i));
}
vert.position(0);
vertCount=size/3;
}

public void setVertNorl(ArrayList data){
int size=data.size();
ByteBuffer buffer=ByteBuffer.allocateDirect(size*4);
buffer.order(ByteOrder.nativeOrder());
vertNorl=buffer.asFloatBuffer();
for (int i=0;i vertNorl.put(data.get(i));
}
vertNorl.position(0);
}

public void setVertTexture(ArrayList data){
int size=data.size();
ByteBuffer buffer=ByteBuffer.allocateDirect(size*4);
buffer.order(ByteOrder.nativeOrder());
vertTexture=buffer.asFloatBuffer();
for (int i=0;i vertTexture.put(data.get(i));
i++;
vertTexture.put(data.get(i));
i++;
}
vertTexture.position(0);
}

}

实现材质库的解析方法

public static HashMap 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 list.add(Float.parseFloat(value[i]));
}
}

private static void read(String[] value,float[] fv){
for (int i=1;i1;i++){
fv[i-1]=Float.parseFloat(value[i]);
}
}

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

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 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 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 model=ObjReader.readMultiObj(this,"assets/3dres/pikachu.obj");
List filters=new ArrayList<>();
for (int i=0;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]



推荐阅读
author-avatar
knight
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有