Jump to content

3d texture projection question


jonp

Recommended Posts

I found some pretty good info on projecting textures here:

http://forums.odforce.net/index.php?showtopic=2749&st=12

However, I was wondering if there is a way to achieve the same effect without using a light shader or NDC space. I'm trying to project density into a volume, and if I understand the order of shading in Mantra, using a light shader prevents me from using that density projection for normal calculation or shadowing... correct?

-JP

Link to comment
Share on other sites

I found some pretty good info on projecting textures here:

http://forums.odforce.net/index.php?showtopic=2749&st=12

However, I was wondering if there is a way to achieve the same effect without using a light shader or NDC space. I'm trying to project density into a volume, and if I understand the order of shading in Mantra, using a light shader prevents me from using that density projection for normal calculation or shadowing... correct?

-JP

If your light shader can (by projection or otherwise) solve for density at Ps and export it, then the surface/volume shader should be able to import it and use it. For the volume shader to then go ahead and compute N from derivatives of the imported densities is something that needs to be tested though (I've never tried it myself and, even though Du(), Dv(), and Dw() are now available for volume shaders, I'm not certain if they would work on imported values... they should, but it needs testing).

With regard to shadows, I believe the volume shader is simply called for each sample along the march toward the light and its Of output accumulated, none of which should be broken by having the density that Of depends on come in as an import as opposed to a bound parameter... but I could be missing something.

It's worth a try, and it should be pretty straight forward to test it out.

Cheers.

Link to comment
Share on other sites

So far my workflow has been:

Create a separate illuminance loop in the surface shader for the projection light using light mask parameters to isolate its contribution, then multiply my bound density by its output, then pipe that into the standard calcOpacity subnet vop for volumes. I'll try importing a density var from a light shader later today but I don't know exactly how that would be different from what I'm doing. Why I am most confused about this is that for one light(light with shadow contribution) to be affected by another(light with density contribution) you would have to make sure they are called in the right order in the shader, right?

-jp

Link to comment
Share on other sites

Cool idea!

I got the basics to work doing exactly what you're saying, but importing a variable from the light shader. I'm not sure how to get correct shadows though, seems youre right the density would have to be modified before the surface shader is called.

Link to comment
Share on other sites

So far my workflow has been:

Create a separate illuminance loop in the surface shader for the projection light using light mask parameters to isolate its contribution, then multiply my bound density by its output, then pipe that into the standard calcOpacity subnet vop for volumes. I'll try importing a density var from a light shader later today but I don't know exactly how that would be different from what I'm doing. Why I am most confused about this is that for one light(light with shadow contribution) to be affected by another(light with density contribution) you would have to make sure they are called in the right order in the shader, right?

-jp

Hmm.. yes, I "get" your workflow, but I guess I misunderstood your intent from your initial post. I thought the light was defining the density everywhere, and since you said "using a light shader prevents me from using that density projection for normal calculation or shadowing", I thought you needed to calc normals from the density gradient of said projections... which is why I mentioned derivs..

But from this last post it seems you just want to scale an existing bound density field by a projected image-based scaling factor... like a cookie cutter?

Anyway, here's a quick sketch of what I was talking about, assuming a simple "cookie-cutter density light":

2 lights, both have shadows, 1 of them is standard illumination only, and the other one projects illumination as usual, but also exports a density scaling factor (which is the alpha of the image in this example), which the volume shader imports and uses to scale the bound density.

post-148-1240594206_thumb.jpgpost-148-1240594539_thumb.jpg

projden.hip

But I take it then that this is not what you're after?

P.S: Here's the code for the "density light" and the volume shader used above:

vol_projden.vfl:

#pragma label  denscale  "Normal Density"
#pragma label  pdenscale "Projected Density"
#pragma hint   density  hidden
surface vol_projden (
      float denscale  = 1;   // density scaling for bound density
      float pdenscale = 10;  // density scaling for projected density
      float density   = 1;   // bound density field (hidden)
   )
{
   vector illum = 0;
   float  dens  = density;

   if(!isshadowray()) {
      illuminance(P,{0,1,0},M_PI) {
         float kd = 1; 
         if(limport("density",kd)) dens *= kd*pdenscale;
         shadow(Cl);
         illum += Cl;
      } 
   }
   Of = 1 - exp(-denscale*dens);
   Cf = illum * Of;
   F  = isotropic();
}

light_projden.vfl:

#pragma label denimage  "Density Image"
#pragma label dmin      "Minimum Density"
#pragma hint  density   hidden
light light_projden (
      string denimage = "default.rat";
      float  dmin     = 0;
      export float density = 1;
   )
{
   Cl = 0;
   if(denimage!="") {
      vector Pndc = toNDC(wo_space(Ps));
      vector4 rgba = texture(denimage,Pndc.x,Pndc.y,
                             "filter","gauss",
                             "width" ,2,
                             "mode","black");
      float dout = rgba.a;
      density = lerp(dmin,dout,filterstep(dmin,dout,"filter","gauss"));
      Cl = (vector)rgba;
   }
}

Link to comment
Share on other sites

...but the projected butterfly doesn't itself cast shadows... that's the problem, isn't it?

Huh. Interesting challenge, this.

You can't remove the if(!isshadow()) because you'll end up with infinite recursion.

There might be a way using the new getlights() and eval_light() functions to roll your own illuminance (and sampling your own shadows)...

Link to comment
Share on other sites

...but the projected butterfly doesn't itself cast shadows... that's the problem, isn't it?

Huh. Interesting challenge, this.

You can't remove the if(!isshadow()) because you'll end up with infinite recursion.

There might be a way using the new getlights() and eval_light() functions to roll your own illuminance (and sampling your own shadows)...

Yep, that's the crux right there. I want the density projection from a light shader to cast shadows in the volume from other lights.

Link to comment
Share on other sites

Oh, man! I suck!

I left the files at work thinking I'd ftp them home later... and forgot they were scheduled to start moving everything to a new ISP starting tonight. And they said it'd take up to 40+ hours.

I left in a rush and forgot. So sorry!

Anyway. Doesn't matter. All it was is moving the projection to the surface shader. You'll need to know the name of light object to project from (this will let you do the P transform), then some of the projection parameters depending on how "true-to-the-light you want the projection to be (focal, aperture, etc), then some parms for the density scaling. Finally, but optionally, if you want maintain the pixel aspect regardless of zoom, you can use the teximport() function to grab "texture:resolution" (a vector). Of course, you could also simplify the whole NDC-project to a simple scaled {x,y} (after transform) since you don't have to duplicate a given light's frustum (just some scaled/zoomed version of its POV).

Yup, a bit of a PITA, but really, I'm quite convinced now that there's no way to query light sources in any way when the surface/volume shader is being called during an opacity march.

Hope that makes sense.

I'll post the files as soon as I can get at them.

Link to comment
Share on other sites

Bah! I still can't get at the files, so I rewrote it (with a little more attention to the projection and overall filtering).

The only problem with this method is that if you just use the projector light as the IFD-bound object to transform to, then it won't work with mapped shadows. This is because the only light object included in the IFD snippet which generates the shadow map will be the light for which the shadow is being generated (and only that one). So, the way around this, is to create a null, parented to each projector light, which is set to output as a transform to the IFD (a "space"), then use that as a reference instead of the actual light object.

Also, there seems to be a possible bug (needs further testing to make sure), with the ptransform() function. In transforming P to calculate the projection, we want the inverse of the current-to-light transform, but trying to write this as ptransform(light_obj,"space:current",P) fails during shadow map generation, so instead, I write it as P*invert(otransform(light_obj)); which seems to work properly in all modes.

Other than that, it's just a duplication of the light projection, done inside the surface shader. Hope it makes sense.

Cheers!

post-148-1240781832_thumb.jpg

projden5.hip

projden.vfl :

#include <math.h>

vector maxfv(float a; vector b ) {
   return set( max(a,b.x), max(a,b.y), max(a,b.z) );
}

vector gamma(vector col; float g) {
   return pow(col,1./g);
}

float depthavg(float val; vector voxdim; int doabs) {
   float x  = val;
   float dx = Du(x);
   float dy = Dv(x);
   float dz = Dw(x);
   if(doabs) {
      dx = abs(dx);
      dy = abs(dy);
      dz = abs(dz);
   }
   float a = voxdim.x * (2.*x + dx) / 2.;
   float b = voxdim.y * (2.*x + dy) / 2.;
   float c = voxdim.z * (2.*x + dz) / 2.;

   return (a+b+c) / (voxdim.x+voxdim.y+voxdim.z);
}

vector depthavgV(vector val; vector voxdim; int doabs) {
   float x = val.x;
   float y = val.y;
   float z = val.z;
   return set(depthavg(x,voxdim,doabs),
              depthavg(y,voxdim,doabs),
              depthavg(z,voxdim,doabs) );
}


#pragma label denscale  "Density"
#pragma label plight1   "Proj Light 1"
#pragma label pmap1     "Proj Image 1"
#pragma label pgamma1   "Proj Gamma 1"
#pragma label pfw1      "Proj Filter Width 1"
#pragma label paov1     "Proj Angle 1"
#pragma label pzoom1    "Proj Zoom 1"
#pragma label pdens1    "Proj Density 1"
#pragma hint density hidden
surface projden (
      float    denscale = 1; 

      // projector block 
      string   plight1  = "";
      string   pmap1    = "";
      float    pgamma1  = 0.454545; // = 1/2.2, because assumes display gamma=2.2
      float    pfw1     = 1.5;
      float    paov1    = 45;
      float    pzoom1   = 1;
      float    pdens1   = 1;

      // bound fields
      float    density  = 1; 
   )
{
   float  dens   = density*denscale;
   vector illum  = 0;
   vector voxdim = set(length(dPds),length(dPdt),dPdz);

   // go through all the projectors and update dens/illum
   float   kdens = 1;
   vector  p,res;
   vector4 rgba;
   float   u,v,d;
   if(plight1!="" && pmap1!="") {
      // the following doesn't work... though it should
      //p    = ptransform(plight1,"space:current",P);

      // so I use otransform() instead. Make sure the 'plight' object is
      // a null object (child of the light) set to output transform to the IFD
      p    = P*invert(otransform(plight1));

      res  = teximport(pmap1,"texture:resolution",res);
      d    = 2*tan(pzoom1*radians(paov1)/2);
      u    = 0.5 - (res.x*p.x/p.z) / d;
      v    = 0.5 - (res.y*p.y/p.z) / d;
      rgba = texture(pmap1,u,v,"mode","black","filter","gauss","width",pfw1);

      illum += gamma((vector)rgba,pgamma1);
      kdens *= rgba.a*pdens1;
   }
   float pdens = density * kdens;

   // set density (watch for discontinuity at projection's edge)
   d  = lerp(dens,pdens,filterstep(dens,pdens,"filter","gauss"));
   d  = max(0,depthavg(d,voxdim,0));
   Of = 1 - exp(-d);

   // normal illuminance
   illuminance(P,{0,1,0},M_PI) {
      shadow(Cl);
      illum += maxfv(0,Cl);
   }
   illum = maxfv(0,depthavgV(illum,voxdim,0));

   Cf = illum * Of;
   F = isotropic();
}

Link to comment
Share on other sites

Thanks Mario! Impressive!

The shader makes more sense every time I look at the code, but alas I haven't yet had time to test it out. What's great (I'm guessing) is that the code that mimics the light NDC space could be applied to other special cases as well (transparency mapping for surfaces, etc).

-JP

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...