热门标签 | 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]



推荐阅读
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • PyCharm下载与安装指南
    本文详细介绍如何从官方渠道下载并安装PyCharm集成开发环境(IDE),涵盖Windows、macOS和Linux系统,同时提供详细的安装步骤及配置建议。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 本文介绍了如何使用 Spring Boot DevTools 实现应用程序在开发过程中自动重启。这一特性显著提高了开发效率,特别是在集成开发环境(IDE)中工作时,能够提供快速的反馈循环。默认情况下,DevTools 会监控类路径上的文件变化,并根据需要触发应用重启。 ... [详细]
  • 三星W799在2011年的表现堪称经典,以其独特的双屏设计和强大的功能引领了双模手机的潮流。本文详细介绍其配置、功能及锁屏设置。 ... [详细]
  • 在API测试中,我们常常需要通过大量不同的数据集(包括正常和异常情况)来验证同一个接口。如果为每种场景单独编写测试用例,不仅繁琐而且效率低下。采用数据驱动的方式可以有效简化这一过程。本文将详细介绍如何利用CSV文件进行数据驱动的API测试。 ... [详细]
  • 本文详细介绍了如何解决Uploadify插件在Internet Explorer(IE)9和10版本中遇到的点击失效及JQuery运行时错误问题。通过修改相关JavaScript代码,确保上传功能在不同浏览器环境中的一致性和稳定性。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 深入理解Tornado模板系统
    本文详细介绍了Tornado框架中模板系统的使用方法。Tornado自带的轻量级、高效且灵活的模板语言位于tornado.template模块,支持嵌入Python代码片段,帮助开发者快速构建动态网页。 ... [详细]
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社区 版权所有