Wednesday, February 12, 2014

Writing mental ray shaders: Simple Diffuse

Intro 

the previous simpler diffuse doesn't have a light loop to automatically go through all the lights in the scene and apply it to the shader. This simple diffuse shader will use a light array type to gather the lights in the scene and apply it tothe shader.



MI Source

declare shader
 color "jc_simple_diffuse" (
  color "diffuse" default 1 1 1,
  array light "lights")
 version 1
 apply material
end declare

C Source

#include "shader.h"

DLLEXPORT

struct jc_simple_diffuse {
 miColor diffuse;
 int i_light; //index to first light, the i_ prefix can be attached to arrays to get the index
 int n_light; //number of lights, the n_ prefix can be attached to arrays to get the size of the array
 miTag light[1];
 };

DLLEXPORT

int jc_simple_diffuse_version(void) {return(1);}

DLLEXPORT

miBoolean jc_simple_diffuse(
 miColor *result,
 miState *state,
 struct jc_simple_diffuse *params) {

 miColor *diffuse = mi_eval_color(&params->diffuse); 
 int n_l = *mi_eval_integer(&params->n_light); //number of lights
 int i_l = *mi_eval_integer(&params->i_light); //offset to first light
 miTag *light = mi_eval_tag(&params->light) + i_l; //name of light + offset, ie light1, or light23
 miColor light_color; 
 miVector dir;
 miScalar dot_nl;
 int i, samples;

 for(i = 0; i < n_l; i++, light++) {//go through all the lights
  samples = 0;
  if(mi_sample_light(&light_color,  &dir, &dot_nl, state, *light, &samples)) {
   result->r += diffuse->r * light_color.r * dot_nl;
   result->g += diffuse->g * light_color.g * dot_nl;
   result->b += diffuse->b * light_color.b * dot_nl;
  }
 }
 return(miTRUE);
}

Notes

the light array type comes with the use i_ and n_ prefix in the struct portion of the C source. i_ gives the index to the first light, and n_ gives the number of lights. The miTag string is the same as a light type except its in array form now, light[1], note, not light[0], because 0 is the base address of the array(i think). 

Sunday, February 9, 2014

Writing mental ray shaders: Simpler Diffuse!!

Intro

The previous simplest diffuse shader doesn't really take lights in the scene into account. To get that information, a shader parameter declaration of type "light" will be needed. 

Mi Source
declare shader
	color "jc_simpler_diffuse" (
		color "diffuse" default 0 0 0,
		light "onelight")
	version 1
	apply material
end declare

Notes:

The shader parameter declaration of type light is a string, so in the C sturct portion we'll use miTag

C Source

#include "shader.h"

DLLEXPORT

struct jc_simpler_diffuse {
	miColor diffuse;
	miTag light;
	};

DLLEXPORT
int jc_simpler_diffuse_version(void) {return(1);}

DLLEXPORT

miBoolean jc_simpler_diffuse(
	miColor *result,
	miState *state,
	struct jc_simpler_diffuse *params) {
	
	miColor light_color;
	miVector dir;
	miScalar dot_nl;
	int samples;
	miColor *diffuse = mi_eval_color(&params->diffuse);
	miTag *light = mi_eval_tag(&params->light);
	if(mi_sample_light(&light_color, &dir, &dot_nl, state, *light, &samples)) {

	result->r = diffuse->r * dot_nl * light_color.r;
	result->g = diffuse->g * dot_nl * light_color.g;
	result->b = diffuse->b * dot_nl * light_color.b;
	result->a = 1.0;
	}
	
	
	return(miTRUE);
}

Notes

the function mi_sample_light returns light_color, direction, and the dot_nl.
Inside maya, the shader node has a text box that can create a light, or type in the light name. It only accepts a single light, thus, simpler diffuse shader. Regardless, its the first light based shader.

Monday, January 27, 2014

Writing mental ray shaders: Simplest Diffuse!

Intro 

Back to the basics. The function for lambertian reflectance is


source: wikipedia
Basically, final result equals the dot product of light normal(L) and surface normal(N), multiply by diffuse color(C), multiply by color of light(IL). I'll write the simplest diffuse shader that doesn't get into light loops and getting light information. 


MI Source

declare shader
 color "jc_simple_diffuse" (
  color "diffuse" default 1 1 1,
  vector "light_dir" default 0 0 0)
 version 1
 apply material
end declare

C Source


#include "shader.h"

DLLEXPORT

struct jc_simple_diffuse {
 miColor diffuse;
 miVector lightdir;
 };

DLLEXPORT

int jc_simple_diffuse_version(void) {return(1);}

DLLEXPORT

miBoolean jc_simple_diffuse(
 miColor *result,
 miState *state,
 struct jc_simple_diffuse *params) {

 miScalar dot_nl;

 miColor *diff = mi_eval_color(&params->diffuse);
 miVector *dir = mi_eval_vector(&params->lightdir);
 dot_nl = -mi_vector_dot(&state->normal, dir);
 result->r = diff->r * dot_nl;
 result->g = diff->g * dot_nl;
 result->b = diff->b * dot_nl;
 result->a = 1.0;
 
 return(miTRUE);
 }

Conclusions

The only inputs are diffuse color and a light vector. Inputing -1 -1 -1 in light direction gives it a light looking like in fig 1.
Fig 1: simple diffuse

Fig 2: simple diffuse attribute editor
Since light dir is expressed as vectors, by connecting transforms of x, y, z to Light Dir we can change the light direction of this particular diffuse shader.

Fig 3: demo 01
In fig 3, I connected the translation of xyz of a cube to the light dir, as well as created a direction light and aim constraint it to the cube to better illustrate. When I move the cube.z to 1, the light points left.

Fig 4: demo 02
In fig 4, when i move the cube.z to 5, the falloff becomes really harsh. This is due to dot product of the surface normal and light dir becoming smaller and smaller as the light dir moves further away.

Fig 5: demo 03
In fig 5, basic 45 degree angle light. Harsh falloff could be fixed by normalizing light dir to unit vectors.



Sunday, January 12, 2014

Writing mental ray shaders: Compiling

Environment Setup

Here are whats needed to compile and load the shaders. If in doubt, check the Autodesk MentalRay Technical Documentation . Everything needed is there, its a convoluted process to get it working so read it through carefully. Compiling on linux with gcc seems a more straightforward process, but it might just be because I did not have to install and setup the packages. Here are my notes on setting up the windows environment.

Loading Custom Shaders

I'll start with this, as there might have existing shaders that needs to be loaded. There are two ways. You can put .mi and .dll into the default shader directory

C:\Program Files\Autodesk\Maya2012\mentalray\include
C:\Program Files\Autodesk\Maya2012\mentalray\lib

or

you can specify a path in maya.env found here

C:\Users\equinoxin\Documents\maya\(maya version)

and add this line,

MI_CUSTOM_SHADER_PATH = add a path here.

Put both mi and dll at that location and it will load at startup.

Compiling Shaders

Using visual studio express 2013, free iso download from Microsoft here, Open the command line compiler called "VS2013 x64 Cross Tools Command Prompt" found under Visual Studio Tools. The command line uses DOS commands, so navigate to your source directory. Also make sure you set the correct compiler version with vcvarsall.bat in the visual studio directory. Description and options are here. Depending on your machine and output target, run the correct cl.

Compile

cl /c /O2 /MD /I "C:\Program Files\Autodesk\Maya2012\devkit\mentalray\include" /W3 -DWIN_NT -DBIT64 jc_color_gain.c

cl compiler options can be found here. Here's an explanation of the options above,
1. /c compiles without linking. Note: lower case c.
2. /O2 creates fast code
3. /MD Compiles to create a multithreaded DLL, by using MSVCRT.lib.
4. /I specifies an include library, point this to the directory containing shader.h
5. /W3 sets warning level to 3.
6. -DWIN_NT -DBIT64 sets the compiled target to windows 64 bit.

This will output an obj file. 

Link

link /nodefaultlib:LIBC.LIB /OPT:NOREF /DLL /OUT:jc_color_gain.dll jc_color_gain.obj shader.lib

make sure to copy shader.lib from devkit/mentalray/lib to your visual studio/vc/lib directory.
This will output the dll. 

Declare Shader

Create a .mi file to declare the shader, make sure there's a version and node id statements. 

 version 1
#: nodeid 3002;

C Source File

Make sure there are DLLEXPORT preceding the shader,  version, init and exit shaders. Otherwise the shaders wouldn't work. Under linux, DLLEXPORT evaluated to an empty word. Explanation here.

Resources

MentalRay Technical Documentation