rob Posted May 7, 2008 Share Posted May 7, 2008 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 lightshaders_project.rar Quote Link to comment Share on other sites More sharing options...
rob Posted May 9, 2008 Author Share Posted May 9, 2008 (edited) 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 Edited May 9, 2008 by rob Quote Link to comment Share on other sites More sharing options...
Mario Marengo Posted May 9, 2008 Share Posted May 9, 2008 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! Quote Link to comment Share on other sites More sharing options...
rob Posted May 11, 2008 Author Share Posted May 11, 2008 (edited) 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 May 11, 2008 by rob Quote Link to comment Share on other sites More sharing options...
old school Posted May 11, 2008 Share Posted May 11, 2008 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. Quote Link to comment Share on other sites More sharing options...
rob Posted May 12, 2008 Author Share Posted May 12, 2008 Thanks Jeff .... lerp ????? was someone down the pub slerping beer when they thought of that ? . Thanks for the pointer Quote Link to comment Share on other sites More sharing options...
Mario Marengo Posted May 12, 2008 Share Posted May 12, 2008 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! Quote Link to comment Share on other sites More sharing options...
labuzz Posted May 12, 2008 Share Posted May 12, 2008 (edited) Just to add that you can find some useful functions and infos in: (%HFS/houdini/vex/include) voplib.h voptype.h prman.h http://odforce.net/happy_rendering_with_vex/ vexnotes.h For example there's vop_colormix, gain, bias functions ... I am learning vex/vop too, tks for the infos guys. Edited May 12, 2008 by labuzz Quote Link to comment Share on other sites More sharing options...
rob Posted May 12, 2008 Author Share Posted May 12, 2008 (edited) 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; } enhancedAmbient.rar Edited May 12, 2008 by rob Quote Link to comment Share on other sites More sharing options...
rob Posted May 12, 2008 Author Share Posted May 12, 2008 (edited) double post , super ambient renders Edited May 12, 2008 by rob Quote Link to comment Share on other sites More sharing options...
rob Posted May 12, 2008 Author Share Posted May 12, 2008 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 . Quote Link to comment Share on other sites More sharing options...
Mario Marengo Posted May 12, 2008 Share Posted May 12, 2008 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. Quote Link to comment Share on other sites More sharing options...
rob Posted May 12, 2008 Author Share Posted May 12, 2008 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 Quote Link to comment Share on other sites More sharing options...
rob Posted May 15, 2008 Author Share Posted May 15, 2008 (edited) 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 Edited May 15, 2008 by rob Quote Link to comment Share on other sites More sharing options...
symek Posted May 15, 2008 Share Posted May 15, 2008 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. Quote Link to comment Share on other sites More sharing options...
JoshJ Posted May 15, 2008 Share Posted May 15, 2008 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. Quote Link to comment Share on other sites More sharing options...
Mario Marengo Posted May 16, 2008 Share Posted May 16, 2008 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: 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: 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: 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: 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: 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: 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: 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: 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: 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. 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: 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: 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: 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): 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"): And here with Specular turned on, and a little less weight on the occlusion (0.9). Test18: 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. Quote Link to comment Share on other sites More sharing options...
Ole Posted May 16, 2008 Share Posted May 16, 2008 Had to bookmark this. Thanks Mario and rob Quote Link to comment Share on other sites More sharing options...
rob Posted May 16, 2008 Author Share Posted May 16, 2008 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 Quote Link to comment Share on other sites More sharing options...
rob Posted May 25, 2008 Author Share Posted May 25, 2008 (edited) 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 May 25, 2008 by rob Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.