Shader Design - actnwit/RhodoniteTS GitHub Wiki

Rhodonite's shader design consists of three main components

  • Materials
  • Parameter passing classes
  • Shader code

Materials

In Rhodonite, each Mesh has one or more Primitives, and each Primitive always has one Material. Rhodonite's Material is the WebGL equivalent of a shader program (vertex shaders and fragment shaders combined). The material instance can be set to specific values of shader parameters (base color, etc.) using the setParameter method or other methods.

3 shader code construction methods

Rhodonite offers the following three methods for building shader code.

  • TypeScript class-based
  • Shaderity-based
  • Node editor

In Rhodonite, shaders can be developed using any of these three shader mechanisms.

TypeScript class-based

This is a mechanism where fragments of shader code are embedded as strings in a TypeScript class and combined to build the final shader code. This was the mechanism built in the early development of Rhodonite, but it is now deprecated and many material shader codes have already migrated to the Shaderity base. In addition, we plan to move to a node editor base in the future.

Shaderity-based

This is currently the predominant shader creation method in Rhodonite. This is a mechanism for building shaders using Shaderity, a shader utility library that is being developed as a separate project from Rhodonite. Shaderity is a shader development support library with code binding function by shader import function and utility functions such as code conversion. In the Shaderity base, shaders are generated by creating a regular GLSL file and importing it into the Rhodonite library using Shaderity's loader for WebPack. In doing so, you can use Shaderity's import feature to build shaders from files with multiple GLSL code fragments.

How to write Shaderity-based GLSL

Below is the pixel shader content of Rhodonite's Classic shader.

#pragma shaderity: require(../common/version.glsl)
#pragma shaderity: require(../common/enableExtensions.glsl)
#pragma shaderity: require(../common/glslPrecision.glsl)

/* shaderity: @{definitions} */

#pragma shaderity: require(../common/prerequisites.glsl)

in vec3 v_color;
in vec3 v_normal_inWorld;
in vec4 v_position_inWorld;
in vec2 v_texcoord_0;
in vec3 v_baryCentricCoord;

uniform int u_shadingModel; // initialValue=0
uniform float u_alphaCutoff; // initialValue=0.01
uniform float u_shininess; // initialValue=5
uniform vec4 u_diffuseColorFactor; // initialValue=(1,1,1,1)
uniform sampler2D u_diffuseColorTexture; // initialValue=(0,white)
uniform sampler2D u_normalTexture; // initialValue=(1,blue)
uniform vec4 u_diffuseColorTextureTransform; // initialValue=(1,1,0,0)
uniform float u_diffuseColorTextureRotation; // initialValue=0

#pragma shaderity: require(../common/rt0.glsl)

#pragma shaderity: require(../common/utilFunctions.glsl)

/* shaderity: @{getters} */

#pragma shaderity: require(../common/opticalDefinition.glsl)

void main ()
{

#pragma shaderity: require(../common/mainPrerequisites.glsl)

  // Normal
  vec3 normal_inWorld = normalize(v_normal_inWorld);

  vec4 diffuseColorFactor = get_diffuseColorFactor(materialSID, 0);


  // diffuseColor (Considered to be premultiplied alpha)
  vec3 diffuseColor = vec3(0.0, 0.0, 0.0);
  float alpha = 1.0;
  if (v_color != diffuseColor && diffuseColorFactor.rgb != diffuseColor) {
    diffuseColor = v_color * diffuseColorFactor.rgb;
    alpha = diffuseColorFactor.a;
  } else if (v_color == diffuseColor) {
    diffuseColor = diffuseColorFactor.rgb;
    alpha = diffuseColorFactor.a;
  } else if (diffuseColorFactor.rgb == diffuseColor) {
    diffuseColor = v_color;
  } else {
    diffuseColor = vec3(1.0, 1.0, 1.0);
  }

  // diffuseColorTexture (Considered to be premultiplied alpha)
  vec4 diffuseColorTextureTransform = get_diffuseColorTextureTransform(materialSID, 0);
  float diffuseColorTextureRotation = get_diffuseColorTextureRotation(materialSID, 0);
  vec2 diffuseColorTexUv = uvTransform(diffuseColorTextureTransform.xy, diffuseColorTextureTransform.zw, diffuseColorTextureRotation, v_texcoord_0);
  vec4 textureColor = texture2D(u_diffuseColorTexture, diffuseColorTexUv);
  diffuseColor *= textureColor.rgb;
  alpha *= textureColor.a;

#pragma shaderity: require(../common/alphaMask.glsl)

  // Lighting
  vec3 shadingColor = vec3(0.0, 0.0, 0.0);
#ifdef RN_IS_LIGHTING
  int shadingModel = get_shadingModel(materialSID, 0);
  if (shadingModel > 0) {

    vec3 diffuse = vec3(0.0, 0.0, 0.0);
    vec3 specular = vec3(0.0, 0.0, 0.0);
    for (int i = 0; i < /* shaderity: @{Config.maxLightNumberInShader} */ ; i++) {
      if (i >= lightNumber) {
        break;
      }

      // Light
      Light light = getLight(i, v_position_inWorld.xyz);

      // Diffuse
      diffuse += diffuseColor * max(0.0, dot(normal_inWorld, light.direction)) * light.attenuatedIntensity;

      float shininess = get_shininess(materialSID, 0);
      int shadingModel = get_shadingModel(materialSID, 0);

      float cameraSID = u_currentComponentSIDs[/* shaderity: @{WellKnownComponentTIDs.CameraComponentTID} */];
      vec3 viewPosition = get_viewPosition(cameraSID, 0);

      // Specular
      if (shadingModel == 2) {// BLINN
        // ViewDirection
        vec3 viewDirection = normalize(viewPosition - v_position_inWorld.xyz);
        vec3 halfVector = normalize(light.direction + viewDirection);
        specular += pow(max(0.0, dot(halfVector, normal_inWorld)), shininess);
      } else if (shadingModel == 3) { // PHONG
        vec3 viewDirection = normalize(viewPosition - v_position_inWorld.xyz);
        vec3 R = reflect(light.direction, normal_inWorld);
        specular += pow(max(0.0, dot(R, viewDirection)), shininess);
      }

    }

    shadingColor = diffuse + specular;
  } else {
    shadingColor = diffuseColor;
  }
#else
  shadingColor = diffuseColor;
#endif

  rt0 = vec4(shadingColor * alpha, alpha);
  // rt0 = vec4(u_lightNumber, 0.0, 0.0, 1.0);
  // rt0 = vec4(1.0, 0.0, 0.0, 1.0);
  // rt0 = vec4(normal_inWorld*0.5+0.5, 1.0);

#pragma shaderity: require(../common/setAlphaIfNotInAlphaBlendMode.glsl)

#pragma shaderity: require(../common/glFragColor.glsl)

}

The following pragma syntax can be used to read an external glsl file. This is statically resolved by the bundler.

#pragma shaderity: require(./foo.glsl)

On the other hand, the following comment syntax is resolved dynamically by Rhodonite, and the contents of a variable called getters inside Rhodonite are expanded here.

/* shaderity: @{getters} */

Also, the following uniform variable declarations are actually converted to parameter passing via floating point texture rather than uniform if DataTexture is used for the rendering approach. Various additional information can also be given in comments after the declaration.

uniform int u_shadingModel; // initialValue=0
uniform float u_alphaCutoff; // initialValue=0.01
uniform float u_shininess; // initialValue=5
uniform vec4 u_diffuseColorFactor; // initialValue=(1,1,1,1)
uniform sampler2D u_diffuseColorTexture; // initialValue=(0,white)
uniform sampler2D u_normalTexture; // initialValue=(1,blue)
uniform vec4 u_diffuseColorTextureTransform; // initialValue=(1,1,0,0)
uniform float u_diffuseColorTextureRotation; // initialValue=0

For example, the comment // initialValue= can be used to give an initial value to that shader parameter.

uniform int u_shadingModel; // initialValue=0

Shaderity's own syntax is written in pragma or comment form, so the Shaderity code as a whole does not deviate from the GLSL syntax.

Node Editor

Rhodonite has its own node system that allows real-time construction of shaders through node editing (under development).