Jump to content

Ambient light shader


rob

Recommended Posts

Hi All ,

I have been busy going through the renderman shading language guide book and of course trying to translate that into some mantra shaders that I can use. As I am pretty much doing this on my own I would be greatfull for comment as I am sure I have got some code incorrect or I have failed to find the right functions and theres features prman features in the book I just dont know where to look for in houdini

1 ) basic ambient with intensity

#pragma hint lightcolor color
#pragma hint intensity float


light rs_basic_ambient( vector lightcolor = 0.5;
								   float intensity =1.0;
)


/* shader description goes here */


{
   Cl = lightcolor * intensity;
	L = 0;

}

next a enhanced ambient shader

#pragma hint lightcolor color
#pragma hint intensity float


light rs_enhanced_ambient( vector lightcolor = 0.5;
						float intensity =1.0;
						string ambientMap = "mandril.pic";
						string envSpace = "shader";
						float ambientMapBlur = 0.1;
						float ambientMapFilterWidth = 1;
						string ambientMapFilter = "gaussian";

						)
{

/*Variables */

vector  Ns = normalize(N);

vector  ambientMapColor = lightcolor;



		{
		if (ambientMap != "")
		  ambientMapColor = environment (ambientMap,Ns,"envobject","space:current",
										 "blur",ambientMapBlur,
										 "filter",ambientMapFilter,
										 "width",ambientMapFilterWidth);

		Cl = lightcolor * intensity * ambientMapColor;
		L = Ns;
		}

}

I have a few problems with the enhanced ambient light shader , the book mentions the use the light category defined as string__category = ""ambient"

I understand this is used by a surface shaders illuminance to restrict the use of the shader to only certain shader categories. is this required in Mantra and how would it be impelemented ?

Another statement that gets used is one of 2 light statements in prman solar() and illuminate() I didnt find anything that would match with these. as you can see from the bottom render I have had some success but I get a specular contribution and I still seem to get the diffuse() call being considered . The book deals with using custom illumination functios to get rid of this and thats where I started to get really confused. So if I can get some pointers that would be great as I not even sure if my map blur filters even work correctly....

find attached shader otls RSL shaders and a scene file / comments very very welcome and who knows some might be right !

rob

post-1566-1210161737_thumb.jpg

post-1566-1210161750_thumb.jpg

lightshaders_project.rar

Link to comment
Share on other sites

  • Replies 43
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Posted Images

enhanced ambient v02

 * 
 *
 * NAME:		Enhanced  ambient.vfl ( VEX )
 *
 * COMMENTS: A simple ambient light shader
 */
#pragma label ambientMapBlur			 "MapBlur"
#pragma label ambientMapFilterWidth	  "MapFilterWidth"
#pragma label ambientMapFilter		   "MapFilter"

#pragma hint lightcolor   color
#pragma hint intensity	float
#pragma hint ambientMap   image

#pragma hint  envSpace		  hidden
#pragma hint __nonspecular	  hidden
#pragma hint __category		 hidden

light rs_enhanced_ambient( vector lightcolor = 0.5;
						float intensity =1.0;
						string ambientMap = "$HFS/houdini/pic/mandril.pic";
						string envSpace = "shader";
						float ambientMapBlur = 0.1;
						float ambientMapFilterWidth = 1;
						string ambientMapFilter = "gaussian";
						string __category = "ambient";
						export int __nonspecular=1;
						)
{

/*Variables */

vector  Ns = normalize(N);

vector  ambientMapColor = lightcolor;



		{
		if (ambientMap != "")
		  ambientMapColor = environment (ambientMap,Ns,"envobject","space:current",
										 "blur",ambientMapBlur,
										 "filter",ambientMapFilter,
										 "width",ambientMapFilterWidth);

		Cl = lightcolor * intensity * ambientMapColor;
		L = Ns;
		}

}

I found that using export int __nonspecular=1; cancels out the specular() call. The pragma stuff is not so scary now. After some trial and error

post-1566-1210294674_thumb.jpg

Edited by rob
Link to comment
Share on other sites

Hey rob,

Just scanning it quickly... it seems mostly OK.

The only thing that jumps out is a potential problem with how you initialize ambientMapColor: it gets initialized with the value of lightcolor. Now imagine the if() statement failed (because no map was defined), then the final output becomes:

Cl = lightcolor * intensity * ambientMapColor;
equivalent to => Cl = lightcolor * intensity * lightcolor;
and therefore => Cl = lightcolor^2 * intensity;

So you end up with lightcolor squared when there's no map. See how that works?

I'm pretty sure that's not what you intended, so just initialize it to 1:

   vector ambientMapColor = 1;
   if(...) { }
   Cl = lightcolor * intensity * ambientMapColor;

That way, when there's no map, you just end up with Cl=lightcolor*intensity, which is what you probably wanted.

One more little thing: I think you're thinking RSL with that "blur" option. I could be wrong but I don't think VEX's texture lookup functions support such an option (RSL does) -- check the docs. There is "pixelblur" if you like (which is blur in pixel units, not in terms of fractions of the diagonal like "blur" in RSL), but you already have "width", which is probably better anyway, so you don't need both. I'd say just pick one of them and go with it.

Oh, and a couple of really small comments (nothing that "breaks" your shader or anything):

1. The parameter envSpace is never used. It's not as crucial in a light shader as it is in a surface shader, but seeing that you have it, might as well use it :)

   string space = (envSpace=="") ? "space:current" : envSpace;
   ambientMapColor = environment(... , "envobject", space, ... );

That way, if no envSpace is given, it falls back to current, but if one is given, then it uses it.

2. Just in case you're not aware: you don't need those curly brackets enclosing the body after the variable definitions. Not harmful, just not necessary.

3. Historically, the sort-of agreed upon exported light switches have been __nonspecular and __nondiffuse (from the RSL camp). There's also __nofog, which I think is a VEX extension. Anyway, it's good to have them because all surface shaders *should* test for them, but understand that it's not necessarily guaranteed that they will.

Good job. Happy shading!

Link to comment
Share on other sites

Hi Mario , thankyou so much for the pointers. I understand the point about the ambient light colour totally.

I presume your string goes thus ....

if (ambientMap != "")
{
		  string space = (envSpace=="") ? "space:current" : envSpace;
		  ambientMapColor = environment (ambientMap,Ns,"envobject","space:current",
										 "filter",ambientMapFilter,
										 "width",ambientMapFilterWidth);
}

I will say find trying to find anything in the docs is a nightmare !. Apprently there is no Mix() function and I dont have a clue what its called in vex. The next stage is to mix in occlusion into my light shader ! should be fun

Rob

Edited by rob
Link to comment
Share on other sites

I will say find trying to find anything in the docs is a nightmare !.

Yes and no. I agree that there is not as much documentation for Mantra as there is for say RenderMan. There are quite a few books on writing RenderMan shaders that apply for the most part to Mantra. Just search this forum as this has come up before.

Apprently there is no Mix() function and I dont have a clue what its called in vex. The next stage is to mix in occlusion into my light shader ! should be fun

You seek the lerp() function:

bash_shell$ vcc -X surface | grep lerp
	   vector4 lerp( vector4, vector4; float )
	   vector lerp( vector, vector; float )
	   float lerp( float, float, float )
	   vector4 slerp( vector4, vector4; float )

Many VOPs have different names than the vex functions they wrap around. Putting down a Mix VOP and RMB > Type Properties and inspect the code answers that for you.

#ifdef __vex
  $blend = lerp($input1, $input2, $bias);
#else
  $blend = mix($input1, $input2, $bias);
#endif

which means that if the render requires vex (mantra) use lerp() else use mix() (renderman compliant renderers).

Tip: You can also use the functions included in voptype.h and voplib.h.

Another way to mix two colors is to use the vop_composite() function (found in the composite VOP in the surface context). Too heavy handed for just mixing two colors but good to know just in case.

You can see the actual code for the vop_composite() function in $HFS/houdini/vex/include/voplib.h. You can glean a lot of info from these additional functions generally used in VOPs but certainly available to written shaders as well.

Link to comment
Share on other sites

I presume your string goes thus ....

if (ambientMap != "")
{
		  string space = (envSpace=="") ? "space:current" : envSpace;
		  ambientMapColor = environment (ambientMap,Ns,"envobject","space:current",
										 "filter",ambientMapFilter,
										 "width",ambientMapFilterWidth);
}

Close. But not quite.

It would go like this:

if (ambientMap != "") {
   string space = (envSpace=="") ? "space:current" : envSpace;
   ambientMapColor = environment (ambientMap,Ns,"envobject",space,
								  "filter",ambientMapFilter,
								  "width",ambientMapFilterWidth);
}

The difference is in the parameter that follows "envobject": it should be space instead of "space:current" -- space is the string variable you just computed in the previous line. The idea is to use it in the environment call, else there's no point in creating it, right?

Oh, and "lerp" is a very common short-hand form of "linear interpolation" -- actually, it's arguably a more common name for the operation that you were looking for than "mix". But don't expect shading languages to share function names; they generally don't. Anyway, now that you know the usual names used for it, you'll likely know how to look for it in any shading language :)

Cheers!

Link to comment
Share on other sites

Thanks people ,for all the information and help .Heres the final enhanced ambient light shader with otl and a scene , theres more to follow as next I want to add occlusion , export parameters ....

#pragma hint lightcolor   color
#pragma hint intensity	float
#pragma hint ambientMap   image

#pragma hint  envSpace		  hidden
#pragma label ambientMapFilterWidth	  "MapFilterWidth"
#pragma label ambientMapFilter		   "MapFilter
#pragma hint __nonspecular	  hidden
#pragma hint __nofog			hidden
#pragma hint __category		 hidden

light rs_enhanced_ambient( vector lightcolor = 0.5;
						float intensity =1.0;
						string ambientMap = "$HFS/houdini/pic/mandril.pic";
						string envSpace = "shader";
						float ambientMapFilterWidth = 1;
						string ambientMapFilter = "gaussian";
						string __category = "ambient";
						export int __nonspecular=1;
						export int __nofog=1;
						)
{

/*Variables */

vector  Ns = normalize(N);
vector  ambientMapColor = 1;




if (ambientMap != ""){ 
		  string space = (envSpace=="") ? "space:current" : envSpace;
		  ambientMapColor = environment (ambientMap,Ns,"envobject",space,
										 "filter",ambientMapFilter,
										 "width",ambientMapFilterWidth);
}

 Cl = lightcolor * intensity * ambientMapColor;
 L = Ns;


}

post-1566-1210593892_thumb.jpg

enhancedAmbient.rar

Edited by rob
Link to comment
Share on other sites

Super Ambient :- Today in work I hacked out an occlusion / enhanced ambient shader again using the renderman shader language guide .Yes my code / knowledge being quite thin means it has mistakes :) Again please correct me as I am here to learn.

#pragma label light_color	   "Global tint"

#pragma label ambientMap			"AmbientMap"
#pragma hint intensity	float



#pragma label samples		   "Sampling Quality"
#pragma label doadaptive		"Adaptive Sampling"
#pragma label maxdist		   "Maximum intersect distance"
#pragma label cone_angle		"Sampling Angle"
#pragma label bias			  "Ray tracing bias"

#pragma hint light_color		color
#pragma hint ambientMap		 image
#pragma label background		"Background color"
#pragma hint background		 color
#pragma hint __nonspecular	  hidden
#pragma hint doadaptive		 toggle
#pragma hint __category		 hidden
#pragma label ambientMapFilterWidth	  "MapFilterWidth"
#pragma label ambientMapFilter		   "MapFilter"

light
rs_super_ambient(vector light_color=1;
			 float intensity =1.0;
			 int samples=256;
			 int doadaptive=0;
			 float bias=0.01;
			 float maxdist=1e7;
			 float cone_angle=90;
			 string ambientMap ="";
			vector background={1,1,1};
			 export int __nonspecular=1;
			float ambientMapFilterWidth = 1;
			string ambientMapFilter = "gaussian";
			string envSpace = "shader";
			 string __category = "ambient")
{ 

	vector	  pp = Ps;
	vector	  Ns = normalize(N);
	vector  ambientMapColor =1;
	Cl = 0; 

if (ambientMap != "") {
   string space = (envSpace=="") ? "space:current" : envSpace;
   ambientMapColor = environment (ambientMap,Ns,"envobject",space,
								  "filter",ambientMapFilter,
								  "width",ambientMapFilterWidth);
}






	if (getglobalraylevel() == 0)

		{ 
		Cl += occlusion(pp, Ns,
								"environment", ambientMap,
								 "background", background,
								"samples", samples,
								"adaptive", doadaptive,
								"bias", bias,
								"maxdist", maxdist,
								"angle", radians(cone_angle));




		  Cl *= light_color  *intensity *  ambientMapColor;
		  L = Ns;

		  }
}

I got these renders out using the code .

Link to comment
Share on other sites

Again please correct me as I am here to learn.

OK. It obviously produces some kind of AO-looking result, and that's promising :)

But here are few comments which might be useful:

1. Occlusion is an attenuation of light (not a reflectance or transmission). IOW, occlusion "dims" illumination, it doesn't augment it. The mathematical operation related to this concept is multiplication, not addition or subtraction. Right now you have Cl += occlusion(...);, which is an addition.

The reason it appears to work has to do with the form of the occlusion function you chose to use. Have another look at the docs, then modify the shader to only output the result of the occlusion call, then try both forms of the occlusion function and see what kind of output they provide. You'll notice that the form you chose returns, as its maximum contribution, a 50% grey, not full white as you might expect. Experiment with the two forms a bit and see if you can make sense of their output.

2. There's no option called "adaptive" for occlusion (in either form). Adaptive sampling is handled differently in Mantra than in PRMan. I'd suggest you ignore that side of things for now, but if you're interested, have a look at the "variance" and "variancevar" options. In any case, that "adaptive" option in your shader will be ignored by Mantra.

3. I'm not sure why you chose to limit occlusion to primary global rays only. Can you explain? The effect of this choice is that occlusion won't show up in secondary bounces (indirect lighting and reflections), which might not be a desirable thing.

4. You've placed the final assignment to Cl inside getglobalraylevel() block as well, meaning that this shader will only ever produce a reliable result for primary global rays, and only give default values (have you checked what those are?) for all other cases. Surely not what you want, right?

So. Not bad. And don't worry, this duality with the occlusion function has caught all of us at one point or another :)

Cheers.

Link to comment
Share on other sites

The reason it appears to work has to do with the form of the occlusion function you chose to use. Have another look at the docs, then modify the shader to only output the result of the occlusion call, then try both forms of the occlusion function and see what kind of output they provide. You'll notice that the form you chose returns, as its maximum contribution, a 50% grey, not full white as you might expect. Experiment with the two forms a bit and see if you can make sense of their output.

looking in docs I found : -

1

#

vector occlusion(vector P, vector N)

Computes ambient occlusion at the point P with the normal N. Just as in the irradiance function, the hemisphere is sampled. However, unlike irradiance, surfaces intersected during the hemisphere sampling are not shaded. For this function to work properly, either a constant background color or an environment map must be specified in the optional scope parameters.

2

#

void occlusion(float coverage&, vector missed_direction&, vector P, vector N)

Instead of computing color information from ambient occlusion, this form computes the coverage (the percentage of occlusion) and the average direction of empty space. The average direction can be used to look up the color in a pre-blurred environment map.

I will see what I can come up with

R

Link to comment
Share on other sites

example 1

#pragma label light_color	   "Global tint"
#pragma label envmap			"Environment map"
#pragma label background		"Background color"
#pragma label samples		   "Sampling Quality"
#pragma label maxdist		   "Maximum intersect distance"
#pragma label cone_angle		"Sampling Angle"
#pragma label bias			  "Ray tracing bias"
#pragma hint light_color		color
#pragma hint envmap			 image
#pragma hint background		 color
#pragma hint __nonspecular	  hidden


light
rs_occlusion(vector light_color=1;

		int samples=256;
	   float bias=0.01;
		float maxdist=1e7;
		float cone_angle=90;
		string envmap="";
		vector background={1,1,1};
		export int __nonspecular=1)

{ 

	vector	  pp = Ps;

	*/ N The shading normal for the surface.*/ 

	vector	  nn = normalize(N);

	*/ Cl Light color */

	Cl = 0; 

	*/ so CL + occlusion is 0 + occlusion */

		{ 
		Cl += occlusion(pp, nn,
								"environment", envmap,
								"background", background,
								"samples", samples,
								"bias", bias,
								"maxdist", maxdist,
								"angle", radians(cone_angle));


  */ So then within the block multiply Cl by Light colour	

		  Cl *= light_color;
		  L = nn;
		}

}

I presume my example worked because of the constant background colour being used or a environment map , which drove the occlusion. It worked because as the Cl value is set to zero your adding that to the occlusion and then within the block you are finlly mulitplying it to get a result. I presume if you are using a colour it has something to do with never getting a standard white AO render or am I totally wrong ?. As for the second example I am not even sure I can use it in a light shader . I hope reading some of the occlusion notes here might help .

http://www.fundza.com/rman_shaders/ray/amb...n/occlude1.html

post-1566-1210852726_thumb.jpg

Edited by rob
Link to comment
Share on other sites

I presume if you are using a colour it has something to do with never getting a standard white AO render or am I totally wrong ?. As for the second example I am not even sure I can use it in a light shader . I hope reading some of the occlusion notes here might help .

http://www.fundza.com/rman_shaders/ray/amb...n/occlude1.html

The reason why you always get the half of total illumination with that version of occlusion() call is that it computes illumination based on cone_angle, not the percent of coverage. Use the second version of it as mentioned in Help to produce full black/white scale AO images. The second one occlusion() is also perhaps the more preferred and used here.

Link to comment
Share on other sites

Is there a quick and easy way to turn the second form of occlusion into a custom vop, or how would one write it in an in-line vop? I'm pretty good with vops, but haven't learned vex syntax yet, though I'd like to test out this second form of the occlusion call that everyone is talking about :)

The reason why you always get the half of total illumination with that version of occlusion() call is that it computes illumination based on cone_angle, not the percent of coverage. Use the second version of it as mentioned in Help to produce full black/white scale AO images. The second one occlusion() is also perhaps the more preferred and used here.
Link to comment
Share on other sites

Hey rob,

Glad to see you didn't give up! :thumbsup:

I presume my example worked because of the constant background colour being used or a environment map , which drove the occlusion. It worked because as the Cl value is set to zero your adding that to the occlusion and then within the block you are finlly mulitplying it to get a result. I presume if you are using a colour it has something to do with never getting a standard white AO render or am I totally wrong ?. As for the second example I am not even sure I can use it in a light shader . I hope reading some of the occlusion notes here might help .

Mmnnyeah, kind'a... point is you end up with two competing sources, instead of a modulated single source. But let me start at the beginning and see if I can clear things up a bit.

Here's the test bundle, including a hipfile with a separate take for each one of the tests I mention below.

TestAmbient.tar.gz

Open it up, switch to take Test1, and follow along.

All surfaces get a vex_plastic shader assigned to them.

Outside the teapot and floor, there's just the camera and a single light template object, sitting at {0,0,0}, with the various versions of our ambient light shader assigned to it (one per take). Switch between takes to duplicate the results below.

Version 1:

Good old raw ambient. Two lines. Couldn't be simpler, right?

Yet one of those two lines should make you go "Huh!?".

light amb1 (
	  vector lightcolor = 1;
	  float  intensity  = 1;
   )
{
   Cl = lightcolor*intensity;
   L  = 0;
}

L is the direction of the light. So why is it set to zero? Why not L=N; like you had in some of your other versions? Let's find out...

Presumably our light is an ambient light (as opposed to a directional one). To test this assumption, we'll set the plastic shader's Diffuse and Specular reflectance to black and leave just its Ambient color at 1. After all, if our shader truly represents an ambient light, then things should still get illuminated as long as their ambient reflectance is greater than zero (no matter what the settings of the diffuse and specular components are).

These are the settings in take Test1. Switch to that take and render the "Mantra_MP" ROP, which produces:

post-148-1210898249_thumb.jpg

Perfect!

Even though all objects have a diffuse and specular reflectance of zero, they nevertheless reflect our ambient light's illumination as though it were coming at full intensity and from all directions (the definition of ambient).

To make sure we didn't just get lucky, let's double-check by setting the plastic shader's Diffuse and Specular to white and its Ambient to black. We should get a black image right? (there's only one light source, and it is strictly an ambient source, not diffuse or specular).

Switch to take Test2 and render:

post-148-1210898253_thumb.jpg

Cool. Just what we expected. But how does it know to do that, exactly? How does Mantra know that this is an ambient light and not some other type? Could it have something to do with that weird assignment to L? What happens if we don't set it to zero, but to something else, like the surface's shading normal (N)?

Version 2:

Let's change the shader so that instead of setting L to zero as in version 1, it assigns it the surface normal N:

light amb2 (
	  vector lightcolor = 1;
	  float  intensity  = 1;
   )
{
   Cl = lightcolor*intensity;
   L  = normalize(N);
}

And let's try our original test first: the light shader at default, and the plastic shader set to Diffuse=Specular=0, and Ambient=1. If it works as before, then we should see a white image. Switch to take Test3 and render:

post-148-1210898262_thumb.jpg

Huh... Suddenly our light is no longer an ambient light...

How about setting just Diffuse to white and everything else to black. Switch to Test4 and render:

post-148-1210898268_thumb.jpg

Er... What the heck is that? Looks like what the ambient should have been, and yet this is supposed to be diffuse.

OK. How about just Specular to white and nothing else... Test5:

post-148-1210898273_thumb.jpg

Looks like the specular spot is lined up perfectly with the camera. Weird. The light is nowhere near the position of the camera. What if we move the camera around a bit? Test6:

post-148-1210898279_thumb.jpg

Yup. It follows the camera. Confused yet?

OK. Time for some 'splainin'... I'll go through the Version2 results, one at a time:

Test3: Setting L to some direction other than the zero-vector killed the ambient contribution of our light. This means that when the surface shader (vex_plastic) called the function ambient(), it received black (or zero ambient contribution from the lights in our scene). The inference is that ambient() (which, btw, is the only way for a surface shader to fetch true ambient illumination) only considers illumination from light shaders which specifically set L to zero. To balance things out, an illuminance loop will ignore such lights, else their contribution could potentially be included twice by the surface shader. To put it another way, if a light shader sets L to anything other than zero, that light will be ignored by ambient() and instead become a member of the set of lights scanned by illuminance(). I hope that made some sense.

PRMan has a similar implicit switch for what constitutes an ambient light. In RSL, any light that doesn't sport an illuminate() or solar() block (and instead sets Cl to a literal value) is considered an ambient light (and is similarly only visible to the ambient() function). Else it gets picked up by the illuminance block like a normal light (same as in VEX). The main difference between VEX and RSL lights is that in VEX you describe the light's directionality by assigning a value to L, whereas in RSL you set it implicitly via an illuminate() or solar() block.

Test4: Diffuse suddenly produces a non-zero result because our light is no longer an ambient light and so gets picked up by an illuminance block (which is what all of the built-in reflectance functions, except ambient(), will run internally). So when vex_plastic calls diffuse() it now gets full white coming from direction N. The reason it looks completely white is that diffuse only depends on the normal and the direction to the light, and now the direction to the light is always the same: It matches the normal, so it's at the maximum intensity that diffuse would ever put out, for every shading point.

Test5 and Test6: The underlying reasons why specular now works are identical to those for the diffuse case above. The reason it looks like it's "following" the camera is because of the way phong works: It has maximal reflectance when the direction to the light (incidence) matches the direction of mirror reflection. And because in our current configuration, incidence matches N, then maximum specular reflection happens when the viewing direction also matches N, so the spot appears to follow the camera.

The important thing to take away from all this is that, in order to have a true ambient light, you need to set L to zero. As soon as you add a directional component (like an environment map, for example), you end up with a "directional ambient" (whatever that is), and it will no longer show up in an ambient() call. Instead, we end up with something that should more properly be called "ambient diffuse".

Version 3:

Now we'll allow for the ambient light to be "tinted" (a multiplication) by a user-specified environment map. There should be nothing new here, except that now you hopefully understand why you're suddenly having to set L to N.

light amb3 (
      vector lightcolor = 1;
      float  intensity  = 1;
      string env_map    = "Mandril.rat";
      string env_space  = "space:world";
   )
{
   vector n    = normalize(N);
   vector Cenv = 1;

   if(env_map!="") {
      string space = env_space=="" ? "space:world" : env_space;
      Cenv = environment(env_map,n,"envobject",space);
   }

   Cl = lightcolor*intensity*Cenv;
   L  = n;
}

From now on, all the tests will set the plastic shader's Diffuse and/or Specular to 1 else you won't see anything (the Ambient setting will no longer make a difference). If you render take Test7, you get:

post-148-1210898283_thumb.jpg

Version 4:

So now we have a coloured source lined up directly above each shade point... and we come to the dreaded occlusion. The version of the occlusion function you used can also take an environment map, so let's just try it by itself and see what happens:

light amb4 (
      vector lightcolor   = 1;
      float  intensity    = 1;
      string env_map      = "Mandril.rat";
      string env_space    = "space:world";
      int    occ_enable   = 1;
      float  occ_angle    = 90;
      int    occ_samples  = 100;
      vector occ_bgcol    = 1;        // fallback color if no env given
      float  occ_maxdist  = -1;       // -1 stands for "infinitely far"
      string occ_distrib  = "cosine"; // either "cosine" or "nonweighted"

   )
{
   vector n     = normalize(N);
   string space = env_space=="" ? "space:world" : env_space;

   Cl = lightcolor*intensity;
   if(occ_enable) {
      Cl *= occlusion(Ps,n,
                      "angle"       , radians(occ_angle),
                      "samples"     , occ_samples,
                      "maxdist"     , occ_maxdist,
                      "distribution", occ_distrib,
                      "environment" , env_map,
                      "envobject"   , space,
                      "background"  , occ_bgcol,
                      "variancevar" , Cl);
   }
   L  = n;
}

Let's first try it without an environment map: Test8:

post-148-1210898287_thumb.jpg

Without the environment map, it falls back to the background color we specified which, at default, is full white. Yet we see a pretty dark image. What we're seeing is occluded irradiance. Without going into details, the result is that, at maximum intensity, this will give you 50% of the specified background intensity. One way to counteract this is to set the bg color to {2,2,2}, Which is what Test9 does:

post-148-1210898292_thumb.jpg

That's closer to what you might expect to see. Good. So let's bring in the texture map. We'll keep the bgcol (which doubles as the environment tint) at 2 so that the result has the expected intensity. Test10 does this.

post-148-1210898306_thumb.jpg

A couple of things to note: 1) The environment map is very blurred. This in itself is not a problem since that is the correct result, but I just wanted to point out that you have no control over just how blurred it gets (at least not via the single occlusion call we're using). For example: what if you wanted a crisp slide projector type of environment with occlusion? And 2) This is more subtle, but if you think about it, the diffuse() call is receiving our light directly down the normal, but the points that are semi-occluded are really getting most of the illumination from some direction that doesn't necessarily coincide with the surface normal (in fact, in a lot of cases, this is the direction being occluded the most).

Version 5:

OK. Now let's try the other version of occlusion.

light amb5 (
      vector lightcolor   = 1;
      float  intensity    = 1;
      int    occ_enable   = 1;
      float  occ_angle    = 90;
      int    occ_samples  = 100;
      float  occ_maxdist  = -1;       // -1 stands for "infinitely far"
      string occ_distrib  = "cosine"; // either "cosine" or "nonweighted"

   )
{
   vector n     = normalize(N);

   Cl = lightcolor*intensity;
   if(occ_enable) {
      float  occ   = 1;
      vector Nbent = n;
      occlusion(occ, Nbent, Ps, n,
                "angle"       , radians(occ_angle),
                "samples"     , occ_samples,
                "maxdist"     , occ_maxdist,
                "distribution", occ_distrib,
                "variancevar" , Cl);
      Cl *= occ;
   }
   L  = n;
}

Note the environment-related occlusion parameters from the previous version were removed, as these will be ignored by this form of the function. Using this form, we have to take care of the environment ourselves (in exactly the same way we did before the last version, actually), but we'll leave that for later. Here's the output of Test11:

post-148-1210898314_thumb.jpg

The function stuffs the "amount of occlusion" into the float variable that we passed for the first argument ("occ" in our case). It also puts some value into the second vector argument ("Nbent" in our case), but let's look at "occ" first. As you can see, wherever "occ" is white, there's occlusion going on, and where it's dark there's less occlusion, all the way to black which means no occlusion at all. We can use this to modulate (again, that means "multiply") the amount of

ambient light that reaches the surface. That's what the shader is doing, but we want to modulate it with the complement of "occ", which is "1-occ" (i.e: we want "visibility" instead of "percent occlusion").

Version 6:

Here we'll change "occ" to "1-(occ*occ_amp)". The "occ_amp" parameter attenuates the strength of the computed occlusion so the user can play with the weight. We'll also bring back the environment stuff as it was before, so that we can have a look at the whole "Nbent" thingie as well.

light amb6 (
      vector lightcolor   = 1;
      float  intensity    = 1;
      string env_map      = "Mandril.rat";
      string env_space    = "space:world";
      int    occ_enable   = 1;
      float  occ_amp      = 1;        // controls the strength of the occlusion
      int    occ_bendN    = 1;        // whether to use the bent normal
      float  occ_angle    = 90;
      int    occ_samples  = 100;
      float  occ_maxdist  = -1;       // -1 stands for "infinitely far"
      string occ_scope    = "*";      // tracing scope
      float  occ_bias     = 0.01;     // tracing bias
      string occ_distrib  = "cosine"; // either "cosine" or "nonweighted"

   )
{
   vector n     = normalize(N);

   // Calculate occlusion and bent normal
   float  vis   = 1; // Default values chosen so that they will
   vector Nbent = n; // have no effect in the final assignment to Cl
   if(occ_enable) {
      float occ = 0;
      occlusion(occ, Nbent, Ps, n,
                "angle"       , radians(occ_angle),
                "samples"     , occ_samples,
                "maxdist"     , occ_maxdist,
                "distribution", occ_distrib,
                "bias"        , occ_bias,
                "scope"       , occ_scope,
                "variancevar" , Cl);
      vis = clamp(1.0-(occ*occ_amp),0,1);
   }

   // We let the user decide whether to use the bent normal
   // or the original surface normal.
   if(occ_bendN) n = Nbent;

   // Fetch environment (possibly in the bent normal direction)
   vector Cenv = 1;  // Again, this default means no adverse effects later
   if(env_map!="") {
      string space = env_space=="" ? "space:world" : env_space;
      Cenv = environment(env_map,n,"envobject",space);
   }

   // Final color is the standard ambient from version 1, but now
   // modulated (multiplied) by both the environment color (Cenv)
   // and the percent visibility (vis).
   Cl = lightcolor*intensity*vis*Cenv;
   L  = n;
}

So let's first try the default behaviour but without the environment for now. We'll also tell it not to use the bent normal so as to have something closer to our last test. This is the output of Test12:

post-148-1210898322_thumb.jpg

If you compare it to Test9, you'll see they're pretty much identical.

Now that we've matched the output of the first version with this one, let's see what else we can do with this version that we probably couldn't with the "occluded irradiance" version.

For one thing, the occlusion weight is a separate quantity, so we can modify it to make it stronger or more subtle (this kind of control is always a good thing). Here, Test13 and Test14 change the value of occ_amp so as to make the occlusion effect subtler and stronger than normal respectively:

post-148-1210898328_thumb.jpg post-148-1210898335_thumb.jpg

The direction that gets placed in the second parameter ("Nbent") is the so-called "bent normal" direction. This is the average unoccluded direction, and it has to do with the issue I mentioned before: if a shading normal is roughly pointing down then it's mostly occluded in that direction by the floor, so whatever amount of light actually reaches it is really coming mostly from above, not directly along N as it would if we left it alone. The "bent normal" is just such a direction: the average direction from which whatever light is reaching the point is mostly coming from. This is useful in two ways: 1) it let's us look up the map in a more correct direction and 2) it provides the diffuse() call with a more realistic direction of incidence. To really see this effect we'll activate the environment again. I'm using a pre-blurred sky map that I included in the bundle.

Here are the results; first without using Nbent (Test15), and then using Nbent (Test16):

post-148-1210898340_thumb.jpg post-148-1210898345_thumb.jpg

Sometimes, depending on the geometry, there's a lot of occlusion going on and most of the bent normals end up quite different from the original shading normal. Now, because the default distribution of the occlusion call (both versions) is "cosine", this means that in those cases, the cosine weighting is almost being applied twice (once implicitly by the distribution, and then again by the diffuse() function). The teapot is not one of those extremes, but just so you can see the difference, Test17 shows you what it looks like with the "nonweighted" distribution (instead of the default "cosine"):

post-148-1210898350_thumb.jpg

And here with Specular turned on, and a little less weight on the occlusion (0.9). Test18:

post-148-1210898356_thumb.jpg

You can keep adding parameters to taste, but these are the basics.

Whew! This ended up being a mini-tutorial. Hope it's useful.

Cheers.

Link to comment
Share on other sites

Mario , Thank you for the detailed reply. I did get a a lot of white and black renders where I thought I had nothing. Hence lack of part 2. I am just trying to digest your post and cross reference the details with the docs and Prman :). I'm sure there will be questions to follow.

R

Link to comment
Share on other sites

  • 2 weeks later...

Well Mario , Its taken some time to digest the information you gave . I have even added a few more features to the second example of the occlusion set up you showed. So its helped me learn a lot. !

I already have questions on "how I can define the environment colours " ie assign one colour for the +Y hemisphere and one colour for the - Y hemisphere and then to be able to blend between the two. So I can have a sky and ground colour ! . I might just try and mix a surface shader first before I attempt that .

Thanks so much

Rob

Edited by rob
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...