Skip to content
This repository has been archived by the owner on Dec 14, 2021. It is now read-only.

HMAT file format (0.1.0): Custom Materials

David Lenaerts edited this page Sep 18, 2018 · 1 revision

Introduction

HMAT is Helix's custom JSON-based material format, made for easy editing and testing. It's made to write and load custom materials.

Layout

An HMAT looks like the following:

{
    "version": "0.1",

    "geometry": {
        "vertexShader": "glsl/vertex_shader.glsl",
        "fragmentShader": "glsl/fragment_shader.glsl"
    },

    "lightingModel": "lighting_model_shader.glsl",

    "uniforms": {
        "someUniformFloat1": 0.01,
        "someUniformVec2": [ 0.0, 0.0 ],
        "someUniformVec3": [ 0.2, 0.1, 0.09 ]
    },

    "textures": {
        "someSampleUniform": "filename.png"
    },

    "cullMode": "back",
    "writeDepth": true,
    "writeColor": true,
    "blend": {
        "source": "one",
        "destination": "one"
        "operator": "add"
    },
    
}

Most properties except the version and the geometry block are optional and take Helix's default value. We'll take a look at all the possible blocks individually.

Blocks

version

The version of the HMAT format. Must currently be "0.1.0"

geometry

This contains links to the glsl files containing the geometry stage of the Material. Remember: lighting and Geometry code is separated in Helix. How the shaders are written can be seen below. geometry is itself a block with two properties:

  • vertexShader: evidently, the link to the glsl file containing the vertex shader for the geometry
  • fragmentShader: evidently, the link to the glsl file containing the fragment shader for the geometry. This computes all elements required in the lighting stage.

lightingModel

This either contains a "shorthand" for Helix's built-in lighting models, or a link to a glsl file implementing a custom lighting model. See below for details. If not provided, it will use the default lighting model defined in InitOptions. The available shorthands are currently:

  • unlit
  • ggx
  • ggx_full
  • blinn

cullMode

Any of the following values:

  • front
  • back
  • both
  • none

writeDepth, writeColor

Whether the material writes depth or color values, respectively. Either true or false.

blend

This defines the blend state used by the material. It has 3 sub-fields:

  • source: the source factor in the blending equation. It has any of the following values:
    • one
    • zero
    • sourceColor
    • oneMinusSourceColor
    • sourceAlpha
    • oneMinusSourceAlpha
    • destinationColor
    • oneMinusDestinationColor
    • destinationAlpha
    • oneMinusDestinationAlpha
    • sourceAlphaSaturate
  • destination: the destination factor in the blending equation. It has any of the following values:
    • one
    • zero
    • sourceColor
    • oneMinusSourceColor
    • sourceAlpha
    • oneMinusSourceAlpha
    • destinationColor
    • oneMinusDestinationColor
    • destinationAlpha
    • oneMinusDestinationAlpha
    • sourceAlphaSaturate
  • operator: the operator in the blending equation. One of the following:
    • add
    • subtract
    • reverseSubtract

Writing Shaders

Geometry

Vertex Shader

The vertex shader provides anything the fragment shader needs to calculate the values it needs in a function called hx_geometry. For example:

vertex_attribute vec4 hx_position;
vertex_attribute vec3 hx_normal;

varying_out vec2 uv;
varying_out vec3 vertexNormal;

uniform mat4 hx_worldMatrix;
uniform mat4 hx_viewProjectionMatrix;
uniform mat3 hx_normalWorldViewMatrix;

void hx_geometry()
{
    vec4 worldPos = hx_worldMatrix * hx_position;
    uv = worldPos.xy * 0.1;
    vertexNormal = hx_normalWorldViewMatrix * hx_normal;
    gl_Position = proj = hx_viewProjectionMatrix * worldPos;
} 

Note the difference with vanilla GLSL in that the attribute keyword in Helix is vertex_attribute, and the outgoing varying is varying_out. This is done to internally support WebGL 1 and 2. The calculations are done in hx_geometry rather than in main (which is used internally instead).

Fragment Shader

Another hx_geometry function here, but this time it returns an object of type HX_GeometryData. This object looks like this:

struct HX_GeometryData
{
    vec4 color;                        // the albedo color and alpha for dielectrics or the specular color for metallics. This must be in linear space, not gamma!
    vec3 normal;                       // the surface normal vector. Must be in view space!
    float metallicness;                // 0 for dielectrics, 1 for metallics
    float normalSpecularReflectance;   // The reflection strength looking at a surface head on for dielectrics. When in doubt, use 0.027
    float roughness;                   // The roughness from 0 to 1 of the specular reflection
    float occlusion;                   // The amount of ambient occlusion for this point. This is a multiplier, so 1.0 is unoccluded
    vec3 emission;                     // The color emitted by this surface.
    vec4 data;                         // this can be any extra data a custom lighting model requires
} 

A fragment shader that generates this could look like this:

varying_in vec2 uv;
varying in vec3 vertexNormal;

uniform sampler2D albedoMap;

HX_GeometryData hx_geometry()
{
    HX_GeometryData data;
    data.color = hx_gammaToLinear(texture2D(albedoMap, uv));
    data.normal = normalize(vertexNormal);
    data.metallicness = 0.0;
    data.normalSpecularReflectance = 0.027;
    data.roughness = 0.5;
    data.occlusion = 1.0;
    data.emission = vec3(0.0);
    return data;
}

Similar to before, notice the use of varying_in.

Lighting Models

Lighting Model shaders are a single glsl file containing a BRDF function. The signature of the function looks like this:

void hx_brdf(in HX_GeometryData geometry, in vec3 lightDir, in vec3 viewDir, in vec3 viewPos, in vec3 lightColor, vec3 normalSpecularReflectance, out vec3 diffuseColor, out vec3 specularColor)
{
    // your lighting code, assign diffuse contribution (fe: Lambert, Oren-Nayar) to diffuseColor, and specular reflections (Trowbridge-Reitz, Phong, Ward, ...) to specularColor
}

Implementing lighting models is an advanced topic. If you know what you're doing, you'll need to know about the conventions used.

  • lightDir points from the light to the surface
  • viewDir points from the camera to the surface
  • normalSpecularReflectance is either the same dielectric value as provided to geometry, or - for metallics - it contains the metallic color. You can still choose to implement a custom metallic shader with the data from geometry if you want to be more physically correct.
  • diffuseColor, specularColor: The outputs are expected in linear (not gamma) space.

Available vertex attributes

The following default attributes should be used for the vertex shader, rather than creating your own for the same type of data. All coordinates are in object space (bind space when skinned).

  • vec4 hx_position: The vertex position
  • vec3 hx_normal: The vertex normal
  • vec4 hx_tangent: The tangent normal. The w-component is used to calculate the bitangent: bitangent = cross(hx_tangent.xyz, normal) * hx_tangent.w)
  • vec2 hx_texCoord: The texture coordinate
  • vec4 hx_jointIndices: 4 joint indices the current vertex is bound to
  • vec4 hx_jointWeights: 4 joint weights defining the influence of each joint

Available uniforms

Helix has a set of built-in uniforms you can access in your shaders when you declare them.

  • mat4 hx_viewMatrix: The matrix transforming from world to view space.
  • mat4 hx_projectionMatrix: The matrix transforming from view to ndc space.
  • mat4 hx_inverseProjectionMatrix: The matrix transforming from ndc to view space.
  • mat4 hx_viewProjectionMatrix: The matrix transforming from world to ndc space.
  • mat4 hx_inverseViewProjectionMatrix: The matrix transforming from ndc to world space .
  • mat4 hx_worldMatrix: Tranforms from object space to world space.
  • mat4 hx_worldViewMatrix: Tranforms from object space to view space.
  • mat4 hx_wvpMatrix: The full world-view-projection matrix.
  • mat4 hx_inverseWVPMatrix: The inverse of the world-view-projection matrix.
  • mat3 hx_normalWorldMatrix: The matrix used to transform normals from object to world space.
  • mat3 hx_normalWorldViewMatrix: The matrix used to transform normals from object to view space.
  • mat4 hx_cameraWorldMatrix: The matrix transforming from view space to world space
  • vec3 hx_cameraWorldPosition: The position of the camera in world space
  • float hx_cameraFrustumRange: The camera's farDistance - nearDistance
  • float hx_rcpCameraFrustumRange: 1.0 / (farDistance - nearDistance)
  • float hx_cameraNearPlaneDistance: The camera's near clip plane distance
  • float hx_cameraFarPlaneDistance: The camera's far clip plane distance
  • vec2 hx_renderTargetResolution: The current render target size
  • vec2 hx_rcpRenderTargetResolution: The reciprocal of the current render target size
  • vec2 hx_dither2DTextureScale: Maps screen coordinates to dither texture coordinates. Used with the hx_dither2D texture.
  • vec3 hx_ambientColor: The ambient color of the scene.
  • vec2 hx_poissonDisk[64]: The default set of Poisson disk points (array size allowed to be smaller)
  • vec3 hx_poissonSphere[64]: The default set of 3D Poisson sphere points (array size allowed to be smaller)
  • mat4 hx_bindShapeMatrix: The matrix transforming from object space to skeleton bind space.
  • mat4 hx_bindShapeMatrixInverse: The matrix transforming from skeleton space to object space.
  • vec3 hx_skinningMatrices[HX_MAX_SKELETON_JOINTS * 3]: Only if HX_USE_SKINNING is defined and HX_USE_SKINNING_TEXTURE is not. An array containing all the skeleton joint poses. Every 3 elements form a single affine matrix.
  • float hx_morphWeights[8]: An array containing all the morph weights. Only if HX_USE_NORMAL_MORPHING is defined. These relate to vertex_attributes hx_morphPosition0 to hx_morphPosition7 and hx_morphNormal0 to hx_morphNormal3.

Available textures

Similarly, Helix also provides a bunch of default textures for your convenience.

  • hx_dither2D: a 2D texture used for dithering. Every pixel contains (cos(randomAngle), sin(randomAngle), randomValue, 1.0) so it can be used for random offsetting and random rotations.
  • hx_normalDepthBuffer: the normal depth buffer of the opaque elements in the scene. The depth value encoded here is linear as opposed to the gpu's depth buffer values.
  • hx_backbuffer: The texture containing the rendered opaque objects. If used in a material, it will be considered a transparent material. Using this has a performance impact, especially on mobile devices.
  • hx_ssao: The texture containing the SSAO occlusion.
  • hx_shadowMap: the shadow map.
  • hx_skinningTexture: the texture containing the skeleton pose. Every row represents a row in the matrix, every column a joint. Only if HX_USE_SKINNING and HX_USE_SKINNING_TEXTURE are both defined.

Convenience functions

  • type saturate(type value): Synonymous to clamp(value, 0.0, 1.0)
  • vec4 hx_floatToRGBA8(float value): Encodes a float value from 0.0 - 1.0 in a vec4.
  • float hx_RGBA8ToFloat(vec4 rgba): Decodes a vec4 to a float value (0.0 - 1.0).
  • vec2 hx_floatToRG8(float value): Encodes a float value from 0.0 - 1.0 in a vec2.
  • float hx_RG8ToFloat(vec4 rgba): Decodes a vec2 to a float value (0.0 - 1.0).
  • vec2 hx_encodeNormal(vec3 normal): Encodes a vec3 normal in a vec2
  • vec3 hx_decodeNormal(vec4 data): Decodes the normal from a hx_normalDepthBuffer sample.
  • float hx_decodeLinearDepth(vec4 data): Decodes the depth from a hx_normalDepthBuffer sample.
  • float hx_log10(float val): Calculates the log base 10.
  • vec4/vec3 hx_gammaToLinear(vec4/vec3 color): Converts from gamma color space to linear space.
  • vec4/vec3 hx_linearToGamma(vec4/vec3 color): Converts from linear color space to gamma space.
  • vec3 hx_getFrustumVector(vec2 position, mat4 unprojectionMatrix): Uses an inverse projection matrix to calculate the "absolute" frustum vector. This is essentially a vector going from near plane to far plane on the top-right edge of the frustum. This is used for interpolation with symmetric frusta.
  • vec3 hx_getLinearDepthViewVector(vec2 position, mat4 unprojectionMatrix): This returns the view vector for a given screen position (normalized -1.0 to 1.0), scaled so the Y component is 1. This means that if we calculate the Y ("into screen") coordinate, we can scale it with the vector to obtain the view position.
  • vec3 hx_fresnel(vec3 normalSpecularReflectance, vec3 lightDir, vec3 halfVector): Calculates the Fresnel factor.
  • vec3 hx_fresnelProbe(vec3 normalSpecularReflectance, vec3 lightDir, vec3 normal, float roughness): Calculates the Fresnel factor for light probes, taking into account geometrical attenuation due to roughness.
  • float hx_luminance(vec4/vec3 color): calculates the luminance of a color
  • float hx_linearStep(float lower, float upper, float x): A linear (ie: non-smooth) variant of smoothstep
  • vec4 hx_sampleDefaultDither(sampler2D ditherTexture, vec2 uv): Samples the default dither texture.
  • vec3 hx_intersectCubeMap(vec3 rayOrigin, vec3 cubeCenter, vec3 rayDir, float cubeSize): Allows "correcting" a reflection vector for "sized" cube maps.
  • mat4 hx_getSkinningMatrix(v): Returns the skinning matrix for the current vertex. Requires vertex_attributes hx_jointWeights and hx_jointIndices to be provided. The v parameter is due to a bug in IE and Edge with #define: simply pass in 0.