Thursday, 25 December 2014

Block Models [1.8]

The way that Minecraft renders blocks has changed significantly for 1.8.  Previously, the shape of blocks was defined in the java code.  This meant that (for example) a BlockTorch would always have the same shape, and only the textures could be changed.  Minecraft now uses model files to define both the shape and the texture.

To skip straight to some sample code, see here.

The rendering code uses an IBakedModel to tell it how to render a block.  The Model(s) for a given Block are specified using two types of JSONfiles:

1-BlockStates file

Each block has a corresponding BlockStates configuration file located in
assets/minecraft/blockstates, for vanilla blocks, or
{resources}/assets/yourmodid/blockstates for your mod.
By default, the filename is the block name- for your custom blocks this will be the name you provided to GameRegistry.registerBlock().  

The BlockStates configuration file has entries showing where to find the model for each state of the block, for example for the torch:
File: blockstates/torch.json
{
    "variants": {
        "facing=up": { "model": "normal_torch" },
        "facing=east": { "model": "normal_torch_wall" },
        "facing=south": { "model": "normal_torch_wall", "y": 90 },
        "facing=west": { "model": "normal_torch_wall", "y": 180 },
        "facing=north": { "model": "normal_torch_wall", "y": 270 }
    }
}

In this case the torch uses one of two model files- “normal_torch” or “normal_torch_wall”, depending on which way it is facing.  For custom blocks you should use

 “model”: “yourmodid:modelfilename”

Useful vanilla examples to look at include blockstates/cobblestone, bed, and dark_oak_log.
NB - by default, minecraft looks for a blockstates file with a filename that matches your block name.  You can customise this if you want - for example, a different blockstates file for different block variants, or ignoring one of the properties in your IBlockState.  See here for more information.

2-Model file

The appropriate model file, located in
assets\minecraft\models\block\ for vanilla blocks, or
{resources}\assets\yourmodid\models\block for your mod models,
is then used to provide the instructions for rendering the block in its current state.  

This link does an excellent job of describing the format of the block model files.

Useful vanilla examples include models/block/stone, acacia_log, bed_foot.

Some clarifications of some of the key points:

The model consists of a number of rectangular faces (quads), based on cuboid “elements”.  Each quad is defined in two steps:
  1. A cuboid is defined by specifying the 3D coordinates of two opposite corners, “from” [x,y,z] and “to” [x,y,z].  A full cube such as a block of stone uses [0,0,0] to [16,16,16] - so for example [8,8,8] is the centre of the block.
  2. One or more quads are then defined from the faces of that cuboid – for example, in half_slab.json shown below, the faces are defined using the cuboid from [0, 0, 0] to [16, 8, 16], i.e. a slice 8/16 units high.
This is repeated for all the cuboid “elements” in the model - most models only have one cuboid element, but there can be many - for example the stairs has two - the bottom half slab and the quarter cube "step" on top.

The stairs model is made up of two cuboid "elements"

Each quad is covered with a texture.  The UV coordinates correspond to the texture specified for the quad, in the order [u1, v1, u2, v2] – where [0 – 16, 0 - 16] corresponds to the full texture [width, height].  In the example from half_slab.json below, the down and up faces both use the full texture, but the other faces (east, west, north, south) use only half the texture.  Note that the v coordinate in texture [u,v] coordinates is “upside down”, i.e. [0,0] is the top left corner, not the bottom left corner as you might expect.  So in the example below, the half slab extends from y=0 to y=8, and the texture corresponding to this range is v=8 to v=16.  


Excerpt from models/block/half_slab.json:
"elements": [
  { "from": [ 0, 0, 0 ],
    "to": [ 16, 8, 16 ],
    "faces": {
      "down": {"uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" },
      "up":   {"uv": [ 0, 0, 16, 16 ], "texture": "#top" },
      "north":{"uv": [ 0, 8, 16, 16 ], "texture": "#side",   "cullface": "north" },
      "south":{"uv": [ 0, 8, 16, 16 ], "texture": "#side",   "cullface": "south" },
      "west": {"uv": [ 0, 8, 16, 16 ], "texture": "#side",   "cullface": "west" },
      "east": {"uv": [ 0, 8, 16, 16 ], "texture": "#side",   "cullface": "east" }
    }
  }
]
Half_slab model

The order of the coordinates is important; if you flip them around, for example [16, 0, 0, 16] instead of [0, 0, 16, 16], then the texture will be flipped left-right.   Quads are only visible from one side - for example, the east face is invisible if you are standing behind it, i.e. looking towards it from the west.
The “texture” field in elements must be a variable texture such as “#mytexture”, and you must define “mytexture” in one of your “textures”: fields.  You can’t use the texture directly, for example the model file below will give you a missing texture on the down face.


Modified excerpt from models/block/half_slab.json:
"elements": [
  { "from": [ 0, 0, 0 ],
    "to": [ 16, 8, 16 ],
    "faces": {
      "down": {"uv": [ 0, 0, 16, 16 ], 
               "texture": "blocks/half_slab_texture",
               "cullface": "down" },
// the down face texture will not work! 

For further explanation of quads and texturing used in Block Models, see here.

Some further notes

The faces sometimes specify a value for cullface.  These are handled slightly differently to non-cullface quads:

  1.  Cullface quads might not be rendered, depending on the adjacent block; minecraft will call Block.shouldSideBeRendered() to see whether it should render this face or not.  For example, BlockGlass overrides shouldSideBeRendered(), and returns false if the adjacent block is also a glass block - see the picture below.
    The “cullface” value specifies which direction it points in.  In contrast, non-cullface quads are always rendered.
  2. Cullface quads use lighting brightness (ie sky light and block light) taken from adjacent blocks – for example the east face of the block being rendered at [x,y,z] takes its lighting brightness from the block at [x+1, y, z] i.e. located one position to the east.  In contrast, non-cullface quads use lighting brightness from the block being rendered.

BlockGlass.shouldSideBeRendered(): left = vanilla code; right = overridden to always return true

  • The “ambientocclusion” flag turns on/off ambient occlusion when rendering this block.  Ambient occlusion is a way to improve the appearance of lighting and shadows, see here for more information.  Generally speaking, ambient occlusion is suitable for full blocks, but not for small or complicated shapes such as a torch or grass.
  • The “tintindex” flag is used for faces which get a colour from Block.colorMultiplier() to modify the rendering colour – for example used by grass to change to a brownish colour in drier biomes, or by redstone wire to change the wire brightness according to the power level.  See here for further explanation.
  • The “shade” flag is used to control lighting.  If false, it turns off the direction-dependent face brightness (i.e. the up face is drawn brighter than the down face).  See here for some background info on lighting.
  • The “particle” texture is the 2D representation of the block, used for (eg) the flying block particles when the block is broken, or the view if the player is inside the block.
  • Vanilla has a set of “built in” blocks for which it doesn’t look for models, such as BlockAir, BlockChest, BlockStaticLiquid, defined in BlockModelShapes.registerAllBlocks().  It also does an awful lot of messing around in BlockModelShapes.registerAllBlocks() to define which properties of each block affect the rendering.  This doesn't appear to be necessary for Forge-registered blocks.


18 comments:

  1. Any idea how to convert a .obj to a .json file?

    ReplyDelete
  2. Hi. I'm not familiar with .obj files, but given how restrictive the minecraft block model format is, I think in many cases it won't be possible to convert an .obj to the block model.
    You could try this program:
    BDcraft cubik
    http://bdcraft.net/cubik

    I haven't tried it but I've heard good things about it. You might be able to import obj (and export minecraft block model) with it.

    -TGG

    ReplyDelete
  3. Hey TGG, I've finally gotten around to upgrading some mods to 1.8. I'm pretty comfortable upgrading the Java stuff, but I have to say that converting to BlockPos, IBlockState, and these JSONs is pretty tedious. Anyway, I think I understand the JSON concept -- a JSON that maps the block states to models, and then models that map to textures based on conditions of what is being rendered. However, my effort is failing with "model definition not found" errors. I have a thread here (http://www.minecraftforge.net/forum/index.php/topic,28211.0.html) that shows my asset structure, JSON file content and other related code. I'm pretty sure it is just some typo, or wrong folder structure but I've been looking at it for days with no success. Can you take a look?

    ReplyDelete
    Replies
    1. Nevermind, I solved it. In order for me to avoid typos I used unlocalized name in several places. But forgot that the method for getting the unlocalized name appends the "tile." or "item." to the name -- I hate that because I like setter and getter methods to be symmetrical. Anyway, adding a .substring(5) to each fixed that.

      Delete
    2. Actually, still having some problem with this. I can get item textures to work fine, but blocks still have the pink and black checkered default texture. Even a pretty simple block. Do you have a github repository of working code I could peek at? Just need a block with working texture.

      Delete
    3. Same feeling, the json file looks like some sort of code/script but actually its not. And u can not set breakpoints inside a json file, takes me years to find out syntax errors /w\

      I made a 1.11.2 mod DEMO
      https://github.com/rikka0w0/MinecraftModBasicDemo

      Have a look at BasicBlock.java tut_block.json(2 of them), the texture is tut_block.png

      Delete
  4. Hi Julian Yeah I feel your pain, it's very hard to get it right first time and tracking down why the texture is checkered isn't so easy.
    This repository has some examples of blocks with textures
    https://github.com/TheGreyGhost/MinecraftByExample
    Try MBE01 for a very simple example.

    -TGG

    ReplyDelete
    Replies
    1. It seems like my code is really similar to yours. In client proxy I have a registerBlockRenderers() which has code:
      public void registerItemRenderers()
      {
      // DEBUG
      System.out.println("Registering item renderers");

      RenderItem renderItem = Minecraft.getMinecraft().getRenderItem();

      renderItem.getItemModelMesher().register(MagicBeans.magicBeans, 0, new ModelResourceLocation(MagicBeans.MODID + ":" + MagicBeans.magicBeans.getUnlocalizedName().substring(5), "inventory"));
      renderItem.getItemModelMesher().register(MagicBeans.itemGoldenEgg, 0, new ModelResourceLocation(MagicBeans.MODID + ":" + MagicBeans.itemGoldenEgg.getUnlocalizedName().substring(5), "inventory"));
      renderItem.getItemModelMesher().register(MagicBeans.goldenGooseMeat, 0, new ModelResourceLocation(MagicBeans.MODID + ":" + MagicBeans.goldenGooseMeat.getUnlocalizedName().substring(5), "inventory"));
      renderItem.getItemModelMesher().register(MagicBeans.bootsOfSafeFalling, 0, new ModelResourceLocation(MagicBeans.MODID + ":" + MagicBeans.bootsOfSafeFalling.getUnlocalizedName().substring(5), "inventory"));
      }

      In the console, the debug statement prints out indicating that this code is run. There is no complaints at all in the console about missing models or textures. The block item in the creative tab is a pink and black checkered default texture, but when placed in the world the block is invisible (you can see the bounding box if you hover and it casts some shadow as expected for that block).

      I'll keep chipping away at it, but it is weird that it is showing default texture but without any console complaint about missing texture...

      Delete
  5. I was able to take your code and get blocks to render. So now I just need to work through and see what's wrong with mine. Definitely thanks for giving me a reference point!

    ReplyDelete
    Replies
    1. Okay, finally got it working. I had two problems going on, first one was a cut and paste error where the block states json was actually pointing to another non-working json. Secondly, this block was supposed to be translucent but it seems there is some subtle changes to the way that works in 1.8 -- for example there is some EnumBlockLayer.TRANSLUCENT that needs to be implemented.

      My only beef is that the console should have better errors when it can't find texture. It seems strange it will sometimes display the default texture even though it didn't complain...

      Delete
  6. Keen. The reason it doesn't always complain is because there are several steps between specifying the texture and rendering the model. If the file is not found, you get an error message, but if it doesn't find your variant in the bakery, it just silently substitutes missing. I don't know why they did that, it's very confusing. -TGG

    ReplyDelete
  7. Sorry, need help again. I've successfully created several blocks that are full blocks, with orientable parent. Now I'm trying a more complicated shape -- in this case it is a tanning rack that has two posts and a plane between them. I want the posts to have a wood texture and the plane will have a leather hide texture.

    Anyway, the cuboid shapes work fine but I'm trying to map a texture and it is just using the pink and black checkered default. To keep it simple, I started by avoiding the uv mapping and just trying to map a hardened clay texture onto all faces of all three elements. However, it doesn't seem to put the textures on. Here is my JSON:
    {
    "elements":[
    {
    "__comment":"The skin on the tanning rack",
    "from":[ 4, 3, 7.5 ],
    "to":[ 12, 15, 7.5 ],
    "faces":{
    "north":{ "texture":"blocks/hardened_clay_stained_brown" },
    "south":{ "texture":"blocks/hardened_clay_stained_brown" }
    }
    },
    {
    "__comment":"The right post of the tanning rack",
    "from":[ 13, 0, 7 ],
    "to":[ 15, 16, 8 ],
    "faces":{
    "down":{ "texture":"blocks/hardened_clay_stained_brown" },
    "up":{ "texture":"blocks/hardened_clay_stained_brown" },
    "north":{ "texture":"blocks/hardened_clay_stained_brown" },
    "south":{ "texture":"blocks/hardened_clay_stained_brown" },
    "west":{ "texture":"blocks/hardened_clay_stained_brown" },
    "east":{ "texture":"blocks/hardened_clay_stained_brown" }
    }
    },
    {
    "__comment":"The left post of the tanning rack",
    "from":[ 1, 0, 7 ],
    "to":[ 3, 16, 8 ],
    "faces":{
    "down":{ "texture":"blocks/hardened_clay_stained_brown" },
    "up":{ "texture":"blocks/hardened_clay_stained_brown" },
    "north":{ "texture":"blocks/hardened_clay_stained_brown" },
    "south":{ "texture":"blocks/hardened_clay_stained_brown" },
    "west":{ "texture":"blocks/hardened_clay_stained_brown" },
    "east":{ "texture":"blocks/hardened_clay_stained_brown" }
    }
    }
    ]
    }

    Any ideas? I understand the concept, but I must have a type or am missing something. Do I need to specify a parent? (I tried that but it makes the model into a full block).

    ReplyDelete
    Replies
    1. Okay, I figured it out based on your reference you sent me on another forum. The issue is that you can't put the full texture name down in the elements, but rather you need to define all the textures first and reference them with the # annotation. So a working example looks like this:
      {

      "textures":
      {
      "particle": "blocks/lapis_block",
      "leather": "items/leather",
      "pole": "blocks/stone"
      },
      "elements":[
      {
      "__comment":"The skin on the tanning rack",
      "from":[ 4, 3, 7.5 ],
      "to":[ 12, 15, 7.5 ],
      "faces":{
      "north":{ "uv": [ 0, 0, 15, 15 ], "texture":"#leather" },
      "south":{ "uv": [ 0, 0, 15, 15 ], "texture":"#leather" }
      }
      },
      {
      "__comment":"The right post of the tanning rack",
      "from":[ 13, 0, 7 ],
      "to":[ 15, 16, 8 ],
      "faces":{
      "down":{ "texture":"#pole" },
      "up":{ "texture": "#pole" },
      "north":{ "texture":"#pole" },
      "south":{ "texture":"#pole" },
      "west":{ "texture":"#pole" },
      "east":{ "texture":"#pole" }
      }
      },
      {
      "__comment":"The left post of the tanning rack",
      "from":[ 1, 0, 7 ],
      "to":[ 3, 16, 8 ],
      "faces":{
      "down":{ "texture": "#pole" },
      "up":{ "texture": "#pole" },
      "north":{ "texture": "#pole" },
      "south":{ "texture": "#pole" },
      "west":{ "texture": "#pole" },
      "east":{ "texture": "#pole" }
      }
      }
      ]
      }

      Delete
  8. This comment has been removed by the author.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Can someone tell me the problem? The particles are displaying the null texture. Thanks.

    {
    "textures": {
    "particle": "blocks/anvil_base",
    "0": "blocks/anvil_base",
    },

    ReplyDelete
  11. Hi. Try asking the question here, there are lot of helpful folks that can give advice.
    http://www.minecraftforge.net/forum/index.php/board,73.0.html
    Also perhaps this link
    http://greyminecraftcoder.blogspot.com.au/2015/03/troubleshooting-block-and-item-rendering.html
    -TGG

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete