本文以修改模型中的材质贴图作为例子,该库可以将glTF的不同文件格式进行相互转换。
1. 引入java库,pom文件如下(可以用阿里远程仓库或者maven中央仓库):
<dependency>
<groupId>de.javagl</groupId>
<artifactId>jgltf-model</artifactId>
<version>2.0.3</version>
</dependency>
2.读取本地或者远程的glTF文件,创建模型对象:
GltfModelReader modelReader = new GltfModelReader(); URI uri = new URI(path); DefaultGltfModel gltfModel = (DefaultGltfModel)modelReader.read(uri);
3.根据修改的材质的索引信息获取贴图的对象,meshIndex(修改的材质在模型中meshes数组的索引值)meshPrimitiveIndex (材质中primitives修改的primitive的第几个索引值)
public MaterialModelV2 getMaterialModel(int meshIndex,int meshPrimitiveIndex)throws Exception{
MaterialModelV2 materialModel = (MaterialModelV2)this.gltfModel.getMeshModel(meshIndex).getMeshPrimitiveModels().get(meshPrimitiveIndex).getMaterialModel();
return materialModel;
}
DefaultTextureModel baseColorTexture =(DefaultTextureModel) materialModel.getBaseColorTexture();
4.将需要变更的照片读取成 ByteBuffer对象
public void replaceMaterialPhoto(GLTF gltf, String pthotoUrl, DefaultTextureModel baseColorTexture) throws IOException { String format = pthotoUrl.substring(pthotoUrl.lastIndexOf(".")+1); String photoName = pthotoUrl.substring(pthotoUrl.lastIndexOf("/") + 1); if("jpg".equalsIgnoreCase(format)){ format="jpeg"; } URL url = new URL(pthotoUrl); BufferedImage image = ImageIO.read(url); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(image, format, baos); byte[] bytes = baos.toByteArray(); ByteBuffer imageData = ByteBuffer.wrap(bytes); gltf.updatePhoto(this.gltfModel, baseColorTexture, format,imageData,photoName); }
5.替换贴图照片,替换时根据源文件贴图的位置做不同处理,一种是uri指定外部的照片,另一种是将照片编码成base64放到gltf中。
public void updatePhoto(DefaultGltfModel gltfModel, DefaultTextureModel baseColorTexture, String format, ByteBuffer imageData, String phtotName) throws IOException { DefaultImageModel imageModel =(DefaultImageModel) baseColorTexture.getImageModel(); BufferViewModel bufferViewModel = imageModel.getBufferViewModel(); if(bufferViewModel != null){ embeddedPhoto(gltfModel, baseColorTexture, format, imageData, imageModel); }else { //创建ImageModel对象 imageModel.setMimeType("image/"+ format); //照片和BufferViewModel关联 imageModel.setImageData(imageData); imageModel.setUri(phtotName); imageModel.setName(phtotName.substring(0,phtotName.lastIndexOf("."))); //材质和照片关联 baseColorTexture.setImageModel(imageModel); } }
6.1 将照片编码成base64放到gltf中的,需要新建一个DefaultBufferViewModel,和DefaultBufferModel对象,将对象跟材质对象相关联
private void embeddedPhoto(DefaultGltfModel gltfModel, DefaultTextureModel baseColorTexture, String format, ByteBuffer imageData, DefaultImageModel imageModel) throws IOException { //创建BufferView对象 DefaultBufferViewModel bufferViewModel = new DefaultBufferViewModel(null); bufferViewModel.setByteOffset(0); bufferViewModel.setByteLength(imageData.capacity()); //创建BufferModel对象 DefaultBufferModel bufferModel = new DefaultBufferModel(); bufferModel.setBufferData(imageData); bufferViewModel.setBufferModel(bufferModel); gltfModel.addBufferModel(bufferModel); gltfModel.addBufferViewModel(bufferViewModel); //创建ImageModel对象 imageModel.setMimeType("image/"+ format); //照片和BufferViewModel关联 imageModel.setBufferViewModel(bufferViewModel); //材质和照片关联 baseColorTexture.setImageModel(imageModel); }
6.2 uri指定外部的照片,直接替换材质对象
imageModel.setMimeType("image/"+ format); //照片和BufferViewModel关联 imageModel.setImageData(imageData); imageModel.setUri(phtotName); imageModel.setName(phtotName.substring(0,phtotName.lastIndexOf("."))); //材质和照片关联 baseColorTexture.setImageModel(imageModel);
7.与6雷同,根据照片存放位置序列化gltf
public ByteArrayInputStream gltfModelConvert() throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); GltfModelWriter modelWriter = new GltfModelWriter(); modelWriter.writeEmbedded(gltfModel,byteArrayOutputStream); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); return byteArrayInputStream; }
8.jgltf-model 中有两处代码冗余了照片,下面做了修改
BinaryAssetCreatorV2类
import de.javagl.jgltf.impl.v2.Buffer; import de.javagl.jgltf.impl.v2.BufferView; import de.javagl.jgltf.impl.v2.GlTF; import de.javagl.jgltf.impl.v2.Image; import de.javagl.jgltf.model.*; import de.javagl.jgltf.model.io.Buffers; import de.javagl.jgltf.model.io.MimeTypes; import de.javagl.jgltf.model.io.v2.GltfAssetV2; import de.javagl.jgltf.model.v2.GltfCreatorV2; import java.nio.ByteBuffer; import java.util.*; import java.util.logging.Logger; import java.util.stream.Collectors; public class BinaryAssetCreatorV2 { private static final Logger logger = Logger.getLogger(BinaryAssetCreatorV2.class.getName()); BinaryAssetCreatorV2() { } GltfAssetV2 create(GltfModel gltfModel) { GlTF outputGltf = GltfCreatorV2.create(gltfModel); int binaryGltfBufferSize = computeBinaryGltfBufferSize(gltfModel); ByteBuffer binaryGltfByteBuffer = Buffers.create(binaryGltfBufferSize); Buffer binaryGltfBuffer = new Buffer(); binaryGltfBuffer.setByteLength(binaryGltfBufferSize); outputGltf.setBuffers(Collections.singletonList(binaryGltfBuffer)); List<Image> oldImages = copy(outputGltf.getImages()); List<ByteBuffer> bufferDatas = (List)gltfModel.getBufferModels().stream().map(BufferModel::getBufferData).collect(Collectors.toList()); Map<Integer, Integer> bufferOffsets = concatBuffers(bufferDatas, binaryGltfByteBuffer); // List<ByteBuffer> imageDatas = (List)gltfModel.getImageModels().stream().map(ImageModel::getImageData).collect(Collectors.toList()); List<ImageModel> imageModels = gltfModel.getImageModels(); // 原文件ByteBuffer已经存在的照片,新的生成的ByteBuffer照片还是指向原来的 Map<Integer, Integer> imageOffsets = concatBuffersImages(bufferOffsets,gltfModel,imageModels, binaryGltfByteBuffer); binaryGltfByteBuffer.position(0); List<BufferView> oldBufferViews = copy(outputGltf.getBufferViews()); List<BufferView> newBufferViews = new ArrayList(); int byteLength; for(int i = 0; i < oldBufferViews.size(); ++i) { BufferView oldBufferView = (BufferView)oldBufferViews.get(i); BufferView newBufferView = copy(oldBufferView); newBufferView.setBuffer(0); Integer oldBufferIndex = oldBufferView.getBuffer(); int oldByteOffset = (Integer) Optionals.of(oldBufferView.getByteOffset(), 0); int bufferOffset = (Integer)bufferOffsets.get(oldBufferIndex); byteLength = oldByteOffset + bufferOffset; newBufferView.setByteOffset(byteLength); newBufferViews.add(newBufferView); } List<Image> newImages = new ArrayList(); for(int i = 0; i < oldImages.size(); ++i) { Image oldImage = (Image)oldImages.get(i); Image newImage = copy(oldImage); ImageModel imageModel = (ImageModel)gltfModel.getImageModels().get(i); ByteBuffer imageData = imageModel.getImageData(); byteLength = imageData.capacity(); int byteOffset = (Integer)imageOffsets.get(i); BufferView imageBufferView = new BufferView(); imageBufferView.setBuffer(0); imageBufferView.setByteOffset(byteOffset); imageBufferView.setByteLength(byteLength); int newBufferViewIndex = newBufferViews.size(); newImage.setBufferView(newBufferViewIndex); newImage.setUri((String)null); String imageMimeTypeString = MimeTypes.guessImageMimeTypeString(oldImage.getUri(), imageData); if (imageMimeTypeString == null) { logger.warning("Could not detect MIME type of image"); } else { newImage.setMimeType(imageMimeTypeString); } newBufferViews.add(imageBufferView); newImages.add(newImage); } if (!oldImages.isEmpty()) { outputGltf.setImages(newImages); } if (!newBufferViews.isEmpty()) { outputGltf.setBufferViews(newBufferViews); } return new GltfAssetV2(outputGltf, binaryGltfByteBuffer); } private static int computeBinaryGltfBufferSize(GltfModel gltfModel) { int binaryGltfBufferSize = 0; Iterator var2; ByteBuffer imageData; for(var2 = gltfModel.getBufferModels().iterator(); var2.hasNext(); binaryGltfBufferSize += imageData.capacity()) { BufferModel bufferModel = (BufferModel)var2.next(); imageData = bufferModel.getBufferData(); } for(var2 = gltfModel.getImageModels().iterator(); var2.hasNext(); ) { ImageModel imageModel = (ImageModel)var2.next(); BufferViewModel bufferViewModel = imageModel.getBufferViewModel(); if(bufferViewModel==null){ binaryGltfBufferSize += imageModel.getImageData().capacity(); } } return binaryGltfBufferSize; } /* private static Map<Integer, Integer> concatBuffers(List<? extends ByteBuffer> buffers, ByteBuffer targetBuffer) { Map<Integer, Integer> offsets = new LinkedHashMap(); for(int i = 0; i < buffers.size(); ++i) { ByteBuffer oldByteBuffer = (ByteBuffer)buffers.get(i); int offset = targetBuffer.position(); offsets.put(i, offset); targetBuffer.put(oldByteBuffer.slice()); } return offsets; }*/ private static Map<Integer, Integer> concatBuffers(List<? extends ByteBuffer> buffers, ByteBuffer targetBuffer) { Map<Integer, Integer> offsets = new LinkedHashMap(); for(int i = 0; i < buffers.size(); ++i) { ByteBuffer oldByteBuffer = (ByteBuffer)buffers.get(i); int offset = targetBuffer.position(); offsets.put(i, offset); targetBuffer.put(oldByteBuffer.slice()); } return offsets; } private static Map<Integer, Integer> concatBuffersImages(Map<Integer, Integer> bufferOffsets,GltfModel gltfModel,List<ImageModel> buffers, ByteBuffer targetBuffer) { Map<Integer, Integer> offsets = new LinkedHashMap(); for(int i = 0; i < buffers.size(); ++i) { ImageModel imageModel = buffers.get(i); ByteBuffer oldByteBuffer = imageModel.getImageData(); BufferViewModel bufferViewModel = imageModel.getBufferViewModel(); int byteOffset = 0; if(bufferViewModel!=null){ List<BufferModel> bufferModels = gltfModel.getBufferModels(); for (int j = 0; j < bufferModels.size(); j++) { BufferModel bufferModel = bufferModels.get(j); if(bufferModel == bufferViewModel.getBufferModel()){ byteOffset = bufferOffsets.get(j)+bufferViewModel.getByteOffset(); } } }else { int offset = targetBuffer.position(); targetBuffer.put(oldByteBuffer.slice()); byteOffset = offset; } offsets.put(i, byteOffset); // targetBuffer.put(oldByteBuffer.slice()); } return offsets; } private static <T> List<T> copy(List<T> list) { return (List)(list == null ? Collections.emptyList() : new ArrayList(list)); } static BufferView copy(BufferView bufferView) { BufferView copy = new BufferView(); copy.setExtensions(bufferView.getExtensions()); copy.setExtras(bufferView.getExtras()); copy.setName(bufferView.getName()); copy.setBuffer(bufferView.getBuffer()); copy.setByteOffset(bufferView.getByteOffset()); copy.setByteLength(bufferView.getByteLength()); copy.setTarget(bufferView.getTarget()); copy.setByteStride(bufferView.getByteStride()); return copy; } static Image copy(Image image) { Image copy = new Image(); copy.setExtensions(image.getExtensions()); copy.setExtras(image.getExtras()); copy.setName(image.getName()); copy.setUri(image.getUri()); copy.setBufferView(image.getBufferView()); copy.setMimeType(image.getMimeType()); return copy; } }
EmbeddedAssetCreatorV2 类
import de.javagl.jgltf.impl.v2.Buffer; import de.javagl.jgltf.impl.v2.GlTF; import de.javagl.jgltf.impl.v2.Image; import de.javagl.jgltf.model.*; import de.javagl.jgltf.model.io.IO; import de.javagl.jgltf.model.io.MimeTypes; import de.javagl.jgltf.model.io.v2.GltfAssetV2; import de.javagl.jgltf.model.v2.GltfCreatorV2; import java.nio.ByteBuffer; import java.util.Base64; import java.util.List; public class EmbeddedAssetCreatorV2 { EmbeddedAssetCreatorV2() { } GltfAssetV2 create(GltfModel gltfModel) { GlTF outputGltf = GltfCreatorV2.create(gltfModel); List<Buffer> buffers = Optionals.of(outputGltf.getBuffers()); for(int i = 0; i < buffers.size(); ++i) { Buffer buffer = (Buffer)buffers.get(i); convertBufferToEmbedded(gltfModel, i, buffer); } // Buffer中已经纯在,不需要重复添加照片数据 /*List<Image> images = Optionals.of(outputGltf.getImages()); for(int i = 0; i < images.size(); ++i) { Image image = (Image)images.get(i); convertImageToEmbedded(gltfModel, i, image); }*/ return new GltfAssetV2(outputGltf, (ByteBuffer)null); } private static void convertBufferToEmbedded(GltfModel gltfModel, int index, Buffer buffer) { String uriString = buffer.getUri(); if (!IO.isDataUriString(uriString)) { BufferModel bufferModel = (BufferModel)gltfModel.getBufferModels().get(index); ByteBuffer bufferData = bufferModel.getBufferData(); byte[] data = new byte[bufferData.capacity()]; bufferData.slice().get(data); String encodedData = Base64.getEncoder().encodeToString(data); String dataUriString = "data:application/gltf-buffer;base64," + encodedData; buffer.setUri(dataUriString); } } private static void convertImageToEmbedded(GltfModel gltfModel, int index, Image image) { String uriString = image.getUri(); if (!IO.isDataUriString(uriString)) { ImageModel imageModel = (ImageModel)gltfModel.getImageModels().get(index); ByteBuffer imageData = imageModel.getImageData(); String uri = image.getUri(); String imageMimeTypeString = MimeTypes.guessImageMimeTypeString(uri, imageData); if (imageMimeTypeString == null) { throw new GltfException("Could not detect MIME type of image " + index); } else { byte[] data = new byte[imageData.capacity()]; imageData.slice().get(data); String encodedData = Base64.getEncoder().encodeToString(data); String dataUriString = "data:" + imageMimeTypeString + ";base64," + encodedData; image.setUri(dataUriString); } } } }