Abstract

  • This project is a study in C++ programming techniques to develop an custom pattern nodes called “HeightToColor” and “Distance3DToColor“.
  • The current implementation of the HeightToColor node allows an artist to:
    • smoothly ramp two color over a surface depending on the Y height of the shading hit point
    • swap the two colors using a checkbox 
    • manipulate the Y height of the shading hit point by using a Maya place3DTexture node
    • switch between the actual shading hit point or the height of the origin of the surface for the Y height using a checkbox.
  • The Distance3DToColor node also have the above coloration functionalities, but it is based on the distance of a Shading Point instead. 
  • To learn more, do consult Professor Malcom Kesson’s OSL:Color By Height.

Breakdown

The transition between the Outer and Inner colors was animated by scaling the Y-scale of the place3DTexture node. As for the wireframe appearance, it was done by connecting a grid texture tothe Outer Color of the pattern node 

Process

Coloration by Shading Point Height

At the most basic level, this project began with retrieving the Y-value of a Shading Point in object-space and in world space. That value is normalized by the AiSmoothStep function which returns a number between lower and upper distance that is relative to the value in the range between 0 and 1. Finally, the normalized value is used to mix between upper and lower colors.

				
					// snippet — blending shading point y value

// world space
y = sg->P.y;

//object space 
y = sg->Po.y;

// normalizing the value of a shading point to be used in the mix function
float blend = AiSmoothStep(lower,upper,y);
sg->out.RGB()= mix(upperColor,lowerColor,blend);
				
			

Coloration by user attribute

Point coordinates from 3 exported ass files (default, scaled, and frozen cube)

Understanding points' positions & matrix

Before getting to the algorithm, we must first understand how points’ positions vary across 3 situations: default, scaled, freezed transformationThe points coordinates in both default and scaled have the same values, but the difference is that the matrix is different in the scaled one which is responsible for making the geometry longer. As for a scaled cube with its transformation frozen, the modified points have their positions baked into the array.

Hence the procedures for writing our algorithm are:

  • Retrieve the matrix for our shaded object in order for us to multiply with a place3Dtexture node to manipulate the Shading Point Y-position.
  • In Maya Hypershade, we can now create a place3dTexture node and connect it to the Input Matrix of our pattern node.In summary, though our methods may seem complex, we are just using a user-control matrix to modify the current matrix of our object to shade it
				
					// node parameters // declare an identity matrix //  
AiParameterMtx("input_matrix", AI_M4_IDENTITY);
///////////////////////////////////////////////////
// shader evaluate / user space 

// A pointer variable named "input_matrix" that stores the address of input_matrix
AtMatrix input_matrix = *AiShaderEvalParamMtx(k_matrix);

// invert the matrix so that we can ...
AtMatrix mat =  AiM4Invert(input_matrix); 

// finally, we Left-multiply a point by the matrix using a input 4D point which is our Shading Global - Shading point in world space 
AtVector transformedP = AiM4PointByMatrixMult(mat, sg->P);
y = (transformedP.y);
				
			

Coloration by Surface Origin

The following code demonstrates how to obtain the height of the local origin of a surface above the origin of the world coordinate system.

				
					// snippet 
AtMatrix localToWorld_Matrix = sg->M;
AtVector origin = AtVector(0,0,0);
AtVector transformedOrigin =  AiM4PointByMatrixMult(localToWorld_Matrix, origin);
y = transformedOrigin.y;
				
			

Coloration based on distance

Instead of using the Y-position for coloration, we can use measure the length of a shading point with a AiV3Length function.

				
					len = AiV3Length(sg->P);
				
			

Algorithm

HeightToColor.cpp

				
					// maya/projects/arnold_src+c++/dd_HeightToColor.cpp

#include <ai.h>
#include <cstring>
#include "utilities.h"

#define WORLD_SPACE 0
#define OBJECT_SPACE 1
#define OBJECT_ORIGIN_SPACE 2
#define USER_SPACE 3

AI_SHADER_NODE_EXPORT_METHODS(SampleMethods);

namespace {
enum paramIndex { k_upper_color, k_lower_color, k_upper_ht, k_lower_ht, k_spacename, k_matrix, k_swap };
};

static const char* menu_items[] = { "world", "object","object_origin", "user", NULL };

node_parameters {

        AiParameterRGB("upperColor", 0.7f, 0.7f, 0);
        AiParameterRGB("lowerColor", 0.7f, 0, 0);
        AiParameterFlt("upperHt", 10);
        AiParameterFlt("lowerHt", 2);
        AiParameterEnum("space_name", WORLD_SPACE, menu_items);
        AiParameterMtx("input_matrix", AI_M4_IDENTITY);
        AiParameterBool("swap", 0);


}

shader_evaluate {
        AtRGB upperColor = AiShaderEvalParamRGB(k_upper_color);
        AtRGB lowerColor = AiShaderEvalParamRGB(k_lower_color);
        float upper = AiShaderEvalParamFlt(k_upper_ht);
        float lower = AiShaderEvalParamFlt(k_lower_ht);
        int space = AiShaderEvalParamEnum(k_spacename);
        bool swap = AiShaderEvalParamBool(k_swap);

        float y;

        // using switch statement to increase readability instead of using if/else
        switch(space) {
        case WORLD_SPACE: y = sg->P.y;
                break;

        case OBJECT_SPACE: y = sg->Po.y;
                break;

        case OBJECT_ORIGIN_SPACE:
        {
                AtMatrix localToWorld_Matrix =sg->M;
                AtVector origin =AtVector(0,0,0);
                AtVector transformedOrigin =  AiM4PointByMatrixMult(localToWorld_Matrix, origin);
                y = transformedOrigin.y;
                break;
        }

        default:
                AtMatrix input_matrix = *AiShaderEvalParamMtx(k_matrix);
                AtMatrix mat = AiM4Invert(input_matrix);
                AtVector transformedP = AiM4PointByMatrixMult(mat, sg->P);
                y = transformedP.y;
        }

        float blend = AiSmoothStep(lower,upper,y);
        if(swap)
                sg->out.RGB()= mix(upperColor,lowerColor,blend);
        else
                sg->out.RGB()= mix(lowerColor,upperColor,blend);
}

node_loader {
        if (i > 0)
                return false;
        node->methods        = SampleMethods;
        node->output_type    = AI_TYPE_RGB;
        node->name           = "ddHeightToColor";
        node->node_type      = AI_NODE_SHADER;
        strcpy(node->version, AI_VERSION);
        return true;
}

// The remaining macros can be left "empty"
node_initialize { }
node_update { }
node_finish { }

				
			

Distance3DToColor.cpp

				
					// maya/projects/arnold_src+c++/dd_Distance3DToColor.cpp

#include <ai.h>
#include <cstring>
#include "utilities.h"

#define WORLD_SPACE 0
#define OBJECT_SPACE 1
#define OBJECT_ORIGIN_SPACE 2
#define USER_SPACE 3

AI_SHADER_NODE_EXPORT_METHODS(SampleMethods);

namespace {
enum paramIndex { k_outer_color, k_inner_color, k_outer_dist, k_inner_dist, k_spacename, k_matrix, k_swap };
};

static const char* menu_items[] = { "world", "object","object_origin", "user", NULL };

node_parameters {

        AiParameterRGB("outerColor", 0.7f, 0.7f, 0);
        AiParameterRGB("innerColor", 0.7f, 0, 0);
        AiParameterFlt("outer_dist", 10);
        AiParameterFlt("inner_dist", 2);
        AiParameterEnum("space_name", WORLD_SPACE, menu_items);
        AiParameterMtx("input_matrix", AI_M4_IDENTITY);
        AiParameterBool("swap", 0);

}

shader_evaluate {
        AtRGB outerColor = AiShaderEvalParamRGB(k_outer_color);
        AtRGB innerColor = AiShaderEvalParamRGB(k_inner_color);
        float outer = AiShaderEvalParamFlt(k_outer_dist);
        float inner = AiShaderEvalParamFlt(k_inner_dist);
        int space = AiShaderEvalParamEnum(k_spacename);
        bool swap = AiShaderEvalParamBool(k_swap);

        float len;

        // using switch statement to increase readability instead of using if/else
        switch(space) {
        case WORLD_SPACE: len = AiV3Length(sg->P);
                break;

        case OBJECT_SPACE: len = AiV3Length(sg->Po);
                break;

        case OBJECT_ORIGIN_SPACE:
                {
                AtMatrix localToWorld_Matrix =sg->M;
                AtVector origin =AtVector(0,0,0);
                AtVector transformedOrigin =  AiM4PointByMatrixMult(localToWorld_Matrix, origin);
                len = AiV3Length(transformedOrigin);
                break;
                }

        default:
                AtMatrix input_matrix = *AiShaderEvalParamMtx(k_matrix);
                AtMatrix mat = AiM4Invert(input_matrix);
                AtVector transformedP = AiM4PointByMatrixMult(mat, sg->P);
                len = AiV3Length(transformedP);
        }

        float blend = AiSmoothStep(inner, outer, len);
        if(swap)
                sg->out.RGB() = mix(outerColor,innerColor, blend);
        else
                sg->out.RGB() = mix(innerColor, outerColor, blend);
}

node_loader {
        if (i > 0)
                return false;
        node->methods        = SampleMethods;
        node->output_type    = AI_TYPE_RGB;
        node->name           = "dd_Distance3DToColor";
        node->node_type      = AI_NODE_SHADER;
        strcpy(node->version, AI_VERSION);
        return true;
}

// The remaining macros can be left "empty"
node_initialize { }
node_update { }
node_finish { }

				
			

Conclusion

Learning by teaching

This project marks the completion of my 4 weeks of training in and C++, I come to realize that everything is relatively simple. I believe what really aid my learning process was using this technical breakdown to teach others, because it challenge my comprehension of the topic. If I cannot teach it to others, I most certainly have not understood the topic at hand. 

Being understandable & technical

In a recent virtual lecture with Ziye Liu, SCAD Alumni and  Senior Designer at Microsoft, Liu, despite his mastery of lighting and look development, said:

“It’s good to be technical, but it’s better to be technical and understandable.”

This statement stuck with me as I have always been a technical person, and I often found a big disconnect with sharing this attribute of mine with my peers and employers. I think Liu’s statement comes from asking ourselves:

  • Who do I intend to show this process or presentation to? Technical artists, producers, or employers?
  • How can I show it as simple as possible, but not simpler, so that anyone can comprehend even though they are not a technical artist or director. 
  • How can I show, and not tell my process, as imageries are better proof of concepts than words?

I think making things as simple as possible is one of the hardest things to do. This involves understanding not only what to show, but what NOT to show. From there, we can finally think about HOW we want to show it.

Unintentionally, the previous Python and this course have given me opportunities to grow more conscientious about how I present my process and works to others. I have done so by being contextual in my headers, introducing hierarchy and organization using headers (instead of dropping everything in “Process”, and inserting a table of content to quick navigation and understanding of the content.  This is important because as someone who wants to direct or take on leadership roles in the future, communication skills are much more important than technical expertise. As a “thinker” type of person, I believe my role is to bring clarity and critical insights to the team or project using thoughtfulness. Hence, I am glad that I am taking this course as it has informed my creative practice, not in a technical sense, but in terms of this concept of thoughtful communication.

Grounding ego

The last thing to point out is that learning C++ and Shading Development grounds me and my ego. I like to believe that I am probably the most proficient in the coding sphere among my Motion Design peers, but it’s studying this subject matter made me realized that there are a lot of things out there that I am ignorant about especially in coding, It’s a humbling experience to know that there is much to learn. And that is why I need to be more intentional and selective about the things I want to learn, but also balance between “going with the flow” to allow for spontaneity and innovation. This is something I will discuss again at the end of this programming course.