GLUtils, ou comment faire des modèles 3D facilement
-
Bonjour tout le monde! C’est l’ heure de “Présente ta création”!
–Attention : Des parties de ce tutoriel sont obsolètes pour les verions 1.8 et plus (notemment le rendu d’objet avec .obj)–
Introduction
Beaucoup de personnes ont demandé à charger des fichiers .obj dans Minecraft.
Beaucoup ont réussi.
Mais beaucoup ont échoué.
Tout ça à cause d’un petit truc insignifiant mais tellement important: le fichier .mtl !
Ce dernier est crucial, sans lui, la plupart des textures ne s’appliqueront pas comme voulu! SurprisMais alors, comment faire ?
Avant ce post, vous aviez plusieurs choix:
- Chercher pendant des heures comment charger correctement ce .mtl avec Forge sans rien trouver
- Utiliser des librairies externes sans trop comprendre ce qu’il s’y passe
- Abandonner
Mais maintenant, plus besoin de se casser la tête!
GLUtils est là pour vous aider!Récupérer GLUtils
Vous pouvez soit:
Prendre toutes les sources ici (contient aussi d’autres codes qui peuvent vous intéresser)
Ou télécharger directement le .jar ici pour les mods Minecraft | ici pour le resteEnsuite, ajoutez le code au votre (si vous avez pris les sources) ou ajoutez le .jar dans le classpath (dans le cas du… .jar ^^')
Transformer un model en objet GLModel
C’est surement la partie la plus simple:
ajoutez cette ligne là où vous voulez créer l’instance du model:model = new TessellatorModel("/assets/modid/models/ModelDeLaMortQuiTue.obj");
et celle-ci en dehors de toute méthode:
private TessellatorModel model;
(L’API assume que vous déposez les fichiers de textures et .mtl dans le même fichier)
Et voilà, vous avez fini cette partie, bravo!Dessiner son model
Il suffit d’appeler la méthode render() sur l’instance de votre model:
model.render();
Exemple
Voici un très simple exemple avec notre ami Yoshi qui sera aujourd’hui un bloc de Minecraft (dessiné avec TESR)! (parce que… pourquoi pas ?)
Bonjour Yoshi!
Code du rendu:package tutorial.glutils; import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; import net.minecraft.tileentity.TileEntity; import org.jglrxavpok.glutils.TessellatorModel; import org.lwjgl.opengl.GL11; public class TileTestObjRenderer extends TileEntitySpecialRenderer { private TessellatorModel glmodel; public TileTestObjRenderer() { glmodel = new TessellatorModel("/assets/obj/Yoshi.obj"); } @Override public void renderTileEntityAt(TileEntity tileentity, double d0, double d1, double d2, float f) { GL11.glPushMatrix(); GL11.glTranslated(d0+0.5, d1, d2+0.5); GL11.glScaled(0.01, 0.01, 0.01); glmodel.render(); GL11.glPopMatrix(); } }
Résultat:
Et voilà, c’est tout ce qu’il y a à faire Grand sourire
Bonus
Si vous avez regardé le résultat juste au dessus, vous avez surement remarqué que l’on voit très facilement les différentes faces du model.
Pour s’en débarrasser, ajoutez juste AVANT d’appeler la méthode render() du model:GL11.glShadeModel(GL11.GL_SMOOTH);
Je vous conseille aussi d’appeler
model.regenerateNormals();
Après avoir créer le model afin de corriger les possibles problèmes dans le fichier du model et avoir une réflexion de la lumière correcte.
Code de Yoshi corrigé:
package tutorial.glutils; import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; import net.minecraft.tileentity.TileEntity; import org.jglrxavpok.glutils.TessellatorModel; import org.lwjgl.opengl.GL11; public class TileTestObjRenderer extends TileEntitySpecialRenderer { private TessellatorModel glmodel; public TileTestObjRenderer() { glmodel = new TessellatorModel("/assets/obj/Yoshi.obj"); glmodel.regenerateNormals(); } @Override public void renderTileEntityAt(TileEntity tileentity, double d0, double d1, double d2, float f) { GL11.glPushMatrix(); GL11.glTranslated(d0+0.5, d1, d2+0.5); GL11.glScaled(0.01, 0.01, 0.01); GL11.glShadeModel(GL11.GL_SMOOTH); glmodel.render(); GL11.glPopMatrix(); } }
Cela vous donnera un rendu beaucoup plus lisse:
Vidéo bonus :
https://www.youtube.com/watch?v=zWXUrGBsg4Y
Animer un modèle
Pour cette partie, nous prendons la Vorpal Blade du jeu Alice: Madness Returns modifié par mes soins et nous ferons basculer la lame d’avant en arrière.
Parce que je suis sympa, je vous donne le code pour l’afficher en tant que modèle fixe:package fr.mff.tuto; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import net.minecraft.entity.Entity; import net.minecraft.item.ItemStack; import net.minecraftforge.client.IItemRenderer; import org.jglrxavpok.glutils.TessellatorModel; import org.jglrxavpok.glutils.TessellatorModelEvent; import org.jglrxavpok.glutils.TessellatorModelEvent.RenderGroupEvent; import org.lwjgl.opengl.GL11; public class ItemKnikeRenderer implements IItemRenderer { private TessellatorModel model; private double bladeAngle; private int direction; public ItemKnikeRenderer() { model = new TessellatorModel("/assets/obj/VorpalBlade.obj"); model.regenerateNormals(); } @Override public boolean handleRenderType(ItemStack item, ItemRenderType type) { switch(type) { case ENTITY: return true; case EQUIPPED_FIRST_PERSON: return true; case EQUIPPED: return true; case INVENTORY: return true; default: return false; } } @Override public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper) { if(type == ItemRenderType.INVENTORY && helper == ItemRendererHelper.INVENTORY_BLOCK) return true; return false; } @Override public void renderItem(ItemRenderType type, ItemStack item, Object... data) { GL11.glScaled(0.05,0.05,0.05); switch(type) { case ENTITY: { GL11.glPushMatrix(); GL11.glTranslated(0,0.1,0); GL11.glRotated(((Entity)data[1]).rotationYaw+=0.1, 0, 1, 0); GL11.glRotated(10, 0, 0, 1); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case EQUIPPED_FIRST_PERSON: { GL11.glPushMatrix(); GL11.glTranslated(10,10.0,0.0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(-10, 1, 0, 0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(20, 1, 0, 0); GL11.glRotated(-10, 0, 0, 1); GL11.glRotated(25, 0, 1, 0); GL11.glRotated(10, 1, 0, 0); GL11.glRotated(180, 0, 1, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case EQUIPPED: { GL11.glPushMatrix(); GL11.glTranslated(10,0.0,0.0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(-10, 1, 0, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case INVENTORY: { GL11.glPushMatrix(); GL11.glTranslated(10, -10, 0); GL11.glRotated(90, 0, 1, 0); GL11.glRotated(-45, 1, 0, 0); GL11.glRotated(180, 0, 1, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glPopMatrix(); break; } } } }
Premièrement, vous devez savoir que les animations marchent à partir d’events Forge ! (yeah) Donc vous devez récupérer les events (nous utiliserons la classe de render pour ce tutoriel mais vous pouvez très bien utiliser une classe annexe).
Pour enregistrer votre classe et intercepter les events, ajoutez ceci dans votre classe:TessellatorModel.MODEL_RENDERING_BUS.register(this);
Et
model.setID("votreIDDeModel");
Cet identifiant vous permet de savoir quel modèle est affiché.
Fil rouge:
package fr.mff.tuto; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import net.minecraft.entity.Entity; import net.minecraft.item.ItemStack; import net.minecraftforge.client.IItemRenderer; import org.jglrxavpok.glutils.TessellatorModel; import org.jglrxavpok.glutils.TessellatorModelEvent; import org.jglrxavpok.glutils.TessellatorModelEvent.RenderGroupEvent; import org.lwjgl.opengl.GL11; public class ItemKnikeRenderer implements IItemRenderer { private TessellatorModel model; private double bladeAngle; private int direction; public ItemKnikeRenderer() { model = new TessellatorModel("/assets/obj/VorpalBlade.obj"); model.regenerateNormals(); model.setID("blade"); TessellatorModel.MODEL_RENDERING_BUS.register(this); } @Override public boolean handleRenderType(ItemStack item, ItemRenderType type) { switch(type) { case ENTITY: return true; case EQUIPPED_FIRST_PERSON: return true; case EQUIPPED: return true; case INVENTORY: return true; default: return false; } } @Override public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper) { if(type == ItemRenderType.INVENTORY && helper == ItemRendererHelper.INVENTORY_BLOCK) return true; return false; } @Override public void renderItem(ItemRenderType type, ItemStack item, Object... data) { GL11.glScaled(0.05,0.05,0.05); switch(type) { case ENTITY: { GL11.glPushMatrix(); GL11.glTranslated(0,0.1,0); GL11.glRotated(((Entity)data[1]).rotationYaw+=0.1, 0, 1, 0); GL11.glRotated(10, 0, 0, 1); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case EQUIPPED_FIRST_PERSON: { GL11.glPushMatrix(); GL11.glTranslated(10,10.0,0.0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(-10, 1, 0, 0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(20, 1, 0, 0); GL11.glRotated(-10, 0, 0, 1); GL11.glRotated(25, 0, 1, 0); GL11.glRotated(10, 1, 0, 0); GL11.glRotated(180, 0, 1, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case EQUIPPED: { GL11.glPushMatrix(); GL11.glTranslated(10,0.0,0.0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(-10, 1, 0, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case INVENTORY: { GL11.glPushMatrix(); GL11.glTranslated(10, -10, 0); GL11.glRotated(90, 0, 1, 0); GL11.glRotated(-45, 1, 0, 0); GL11.glRotated(180, 0, 1, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glPopMatrix(); break; } } } }
Il y a différents events:
- TessellatorModelEvent.RenderPre : Appelé avant d’afficher le modèle.
- TessellatorModelEvent.RenderGroupEvent.Pre : Appelé avant d’afficher un groupe de faces
- TessellatorModelEvent.RenderGroupEvent.Post : Appelé après avoir afficher un groupe de faces
Pour notre exemple, nous voulons balancer la lame donc avant d’afficher le modèle, nous gérons les angles:
@SubscribeEvent public void onPreRenderModel(TessellatorModelEvent.RenderPre event) { if(event.model.getID() != null && event.model.getID().equals("blade")) bladeAngle+=direction; if(bladeAngle > 65) { direction = -2; } if(bladeAngle < -65) { direction = 2; } if(direction == 0) direction = 2; }
Ainsi, la lame se balancera entre 65° et -65°.
Ensuite, il faut appliquer la rotation:
@SubscribeEvent public void onPreRenderGroup(RenderGroupEvent.Pre event) { if(event.model.getID() != null) { if(event.model.getID().equals("blade")) { if(event.group.equals("Blade")) { GL11.glPushMatrix(); double x = 0; double y = 6; double z = 0; GL11.glTranslated(x, y, z); GL11.glRotated(bladeAngle, 1, 0, 0); GL11.glTranslated(-x, -y, -z); } } } }
if(event.model.getID().equals("blade")) { if(event.group.equals("Blade")) {
Nous vérifions que le groupe à dessiner est la lame du couteau.
GL11.glPushMatrix(); double x = 0; double y = 6; double z = 0; GL11.glTranslated(x, y, z); GL11.glRotated(bladeAngle, 1, 0, 0); GL11.glTranslated(-x, -y, -z);
GL11.glPushMatrix(); on sauvegarde l’état d’OpenGL.
GL11.glRotated Application de la rotation.
GL11.glTranslated(x, y, z); et GL11.glTranslated(-x, -y, -z); permet de définir un point de rotation. (par défaut (0;0;0) )Puisque l’on a sauvegardé l’état d’OpenGL, il faut le rétablir après avoir dessiné le groupe:
@SubscribeEvent public void onPostRenderGroup(RenderGroupEvent.Post event) { if(event.model.getID() != null) { if(event.model.getID().equals("blade")) { if(event.group.equals("Blade")) { GL11.glPopMatrix(); } } } }
Exemple complet :
package fr.mff.tuto; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import net.minecraft.entity.Entity; import net.minecraft.item.ItemStack; import net.minecraftforge.client.IItemRenderer; import org.jglrxavpok.glutils.TessellatorModel; import org.jglrxavpok.glutils.TessellatorModelEvent; import org.jglrxavpok.glutils.TessellatorModelEvent.RenderGroupEvent; import org.lwjgl.opengl.GL11; public class ItemKnikeRenderer implements IItemRenderer { private TessellatorModel model; private double bladeAngle; private int direction; public ItemKnikeRenderer() { model = new TessellatorModel("/assets/obj/VorpalBlade.obj"); model.regenerateNormals(); model.setID("blade"); TessellatorModel.MODEL_RENDERING_BUS.register(this); } @SubscribeEvent public void onPreRenderModel(TessellatorModelEvent.RenderPre event) { if(event.model.getID() != null && event.model.getID().equals("blade")) bladeAngle+=direction; if(bladeAngle > 65) { direction = -2; } if(bladeAngle < -65) { direction = 2; } if(direction == 0) direction = 2; } @SubscribeEvent public void onPreRenderGroup(RenderGroupEvent.Pre event) { if(event.model.getID() != null) { if(event.model.getID().equals("blade")) { if(event.group.equals("Blade")) { GL11.glPushMatrix(); double x = 0; double y = 6; double z = 0; GL11.glTranslated(x, y, z); GL11.glRotated(bladeAngle, 1, 0, 0); GL11.glTranslated(-x, -y, -z); } } } } @SubscribeEvent public void onPostRenderGroup(RenderGroupEvent.Post event) { if(event.model.getID() != null) { if(event.model.getID().equals("blade")) { if(event.group.equals("Blade")) { GL11.glPopMatrix(); } } } } @Override public boolean handleRenderType(ItemStack item, ItemRenderType type) { switch(type) { case ENTITY: return true; case EQUIPPED_FIRST_PERSON: return true; case EQUIPPED: return true; case INVENTORY: return true; default: return false; } } @Override public boolean shouldUseRenderHelper(ItemRenderType type, ItemStack item, ItemRendererHelper helper) { if(type == ItemRenderType.INVENTORY && helper == ItemRendererHelper.INVENTORY_BLOCK) return true; return false; } @Override public void renderItem(ItemRenderType type, ItemStack item, Object... data) { GL11.glScaled(0.05,0.05,0.05); switch(type) { case ENTITY: { GL11.glPushMatrix(); GL11.glTranslated(0,0.1,0); GL11.glRotated(((Entity)data[1]).worldObj.getB.rotationYaw+=0.1, 0, 1, 0); GL11.glRotated(10, 0, 0, 1); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case EQUIPPED_FIRST_PERSON: { GL11.glPushMatrix(); GL11.glTranslated(10,10.0,0.0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(-10, 1, 0, 0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(20, 1, 0, 0); GL11.glRotated(-10, 0, 0, 1); GL11.glRotated(25, 0, 1, 0); GL11.glRotated(10, 1, 0, 0); GL11.glRotated(180, 0, 1, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case EQUIPPED: { GL11.glPushMatrix(); GL11.glTranslated(10,0.0,0.0); GL11.glRotated(200, 0, 1, 0); GL11.glRotated(-10, 1, 0, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glPopMatrix(); break; } case INVENTORY: { GL11.glPushMatrix(); GL11.glTranslated(10, -10, 0); GL11.glRotated(90, 0, 1, 0); GL11.glRotated(-45, 1, 0, 0); GL11.glRotated(180, 0, 1, 0); GL11.glShadeModel(GL11.GL_SMOOTH); model.render(); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glPopMatrix(); break; } } } }
N’hésitez pas à poser des questions!
Voilà! Ce tutoriel est maintenant fini!
-
@‘jglrxavpok’:
Avant ce post, vous aviez plusieurs choix:
Chercher pendant des heures comment charger correctement ce .mtl avec Forge sans rien trouver
Utiliser des librairies externes sans trop comprendre ce qu’il s’y passe
AbandonnerSuper résumé de mon post, merci Clin d’oeil
J’arrivais nickel jusqu’au fichier .mtl, et là, aucun moyen d’appliquer du multitexturing, j’étais désespéré, et c’est la qu’est arrivé Super jglrxavpok et son tuto qui m’a sauvé la vie Sourire
Jte montrerai mes créations
Continue comme ça et merci Clin d’oeil
-
Merci
J’ai hâte de voir ce que tu va en faire
-
Je cherche quelque chose d’impressionnant
Ps: Maintenant si tu arrives à faire un tutoriel pour ajouter des objets animés, je t’appellerais maître toute ma vie
-
-
Possible d’avoir les sources du tuto ?
Parsque j’ai une ptit erreur:
java.lang.ArrayIndexOutOfBoundsException: -1
-
Fais voir ton code et le log
-
Je sais pourquoi, il y a un nombre de face limité pour le chargement
Adieu gros models à charger
-
Je vais regarder ça ;)___
Je ne crois pas qu’il y est une limiteJ’arrive à charger ce gros modèle…
Envoie moi tes fichiers pour le modèle en MP
-
Avec maintenant une vidéo pour montrer les animations
Edit: Tutoriel modifié pour utiliser TessellatorModel.
Partie sur les animations bientôt! -
[lien d’image mort …]
Un petit exemple bien simpa aussi !
-
Comment faire avec un mob ? Pour appliquer ce genre de model.obj à un mob ? Est-ce lourd pour minecraft ?
-
@‘ZeAmateis’:
Comment faire avec un mob ? Pour appliquer ce genre de model.obj à un mob ? Est-ce lourd pour minecraft ?
Pour un mob, utilise un render et appelle la méthode render() du model dans renderEntity() du render.
(Pour les renders, une petite partie est expliquée ici)
Pour le poids, tout dépend du model. -
Très bien j’irais tester tout ça ! Mais j’ai un problème… Mon Model est blanc est-ce normal ?
Edit: Oublié le .mtl ><
Edit: 2 Une autre question niveau animations… Comment faire ? Est-ce possible ? Toujours pareil, est-ce lourd pour Minecraft ? Un tuto là dessus serait sympa de ta part jglrxavpok ^^
-
Je suis toujours en train de travailler là-dessus.
Peut-être un tuto dans 1 semaine ? -
@‘jglrxavpok’:
Je suis toujours en train de travailler là-dessus.
Peut-être un tuto dans 1 semaine ?Ah ! ça c’est cool ! Ça me sera utile pour mon projet !
-
L’api ne fonctionne pas en 1.7… ^^
Caused by: java.lang.NoClassDefFoundError: net/minecraftforge/event/Event
-
Elias, tu as du télécharger les mauvaises sources
En 1.7 la classe Event n’est plus la même https://github.com/jglrxavpok/MCGLUtils/commit/a2647cfdb3b08319e7c1955aa993e29f71324608 -
C’est génial! j’attend avec impatience la partie sur l’animation ^^
Elles est tj d’actualité? Et elle sera pour quelle version: 1.7 ou 1.6? -
Gargan, étant donné que cette lib est une lib LWJGL et non Minecraft, elle devrait marcher tant que Minecraft utilise LWJGL.
Soit : pour toujours