Realsoft's post processing renderer can be used for rendering various particle effects.
The 'samples/plugins/postproc/ppepoint' folder contains a sample plugin, which can be used for rendering 1d particles as spheres.
There are three kind of particles:
1D - Can be rendered as an object that look the same from all camera directions. The geometry consists of a single point in space. For example, 1d particles can be rendered as lens flares, a dust particles, stars or spheres.
2D - These particles have a property called symmetry axis. The geometry is defined by two geometry points so they can be rendered as water drops, arrows, fur etc.
3D - Geometry is defined by a coordinate system so the particles can be rendered arbitrary 3d shapes. For example, leafs of a tree, that can be in any position can be represented using 3d particles.
To implement a new post effect, three classes needs to be written:
Rendering Class.
Model Class
Property Gadget Class
Particle effects are created from the Select window's 'New' pop-up menu. To add new item to this pop-up menu, call:
#include <r3selwin.h R3DoClassA2(R3CLID_SELWIN, R3SELWINCM_REGISTERPARTICLEEFFECTCLASS, (void*)R3CLID_MYMODEL, (void*)"My Particle Effect");
Particle model classes are derived from the 'inc/real/code/r3postpm.h' base class and they are very typical model objects. There is only one post procssing specific feature: the model must create a rendering object for the renderer. Typically this is done by catching the RENDERBEGIN method and handling it as follows:
static void *r3pefmm_renderbegin(R3CLASS *cl, R3OBJ *obj, R3OBJ *renderengine, int clid, R3TAG *tags) { R3IDATA *self = R3CL_IADDR(cl, obj); if(!clid) clid = R3CLID_PPEPOINTRENDER; /* Define rendering class if not yet defined by a sub class */ return R3DoSuper3(cl, obj, R3PEFMM_RENDERBEGIN, renderengine, (void *)clid, MYRENEFFA_Foo, self->foo, R3TAG_MORE, tags); }
The above code lets the base class take care of creating the actual rendering object.
If the particle effect defines attributes that the user should be able to play with, a property gadget must be implemented. The gadget can be derived directly from the r3gadget.h base class. It is attached to the model through R3WGA_Model attribute.
To plug the property gadget into the property window, call:
#include <r3pospmg.h R3DoClassA3(R3CLID_POSTPARTICLEEFFECTMODELGADGET, R3PPEMGCM_REGISTEREFFECT, (void *)R3CLID_MYMODEL, "My Particle Effect",(void *)R3CLID_MYGADGET);
This tells the property window's particle effect gadget to create and show R3CLID_MYGADGET when R3CLID_MYMODEL is selected.
Post particle effects are derived from the 'inc/real/raytr/r3popaef.h' base class.
Due to dynamic nature of Realsoft 3D, you'll have to make sure the renderer has a post processing engine. To do this, call:
if(R3ClassFind(R3CLID_PHASEPOSTPROCESSING)) { r3phase_postprocessing = (int)R3DoClassA(R3CLID_PHASESYSTEM, R3PHSYSCM_INSTALLPHASE, (void *)R3CLID_PHASEPOSTPROCESSING); if(r3phase_postprocessing) return FALSE; ..
Make also sure all the channels you need (such as Color, Alpha and Depth, for example) are available by calling:
if(!R3RegisterAlphaChannelClass(apphnd))return FALSE; if(!R3RegisterColorChannelClass(apphnd))return FALSE; if(!R3RegisterZChannelClass(apphnd))return FALSE;
Because post processing effects need to read/write thousands if not millions of pixels, you have to fetch so called channel offsets so that you can manipulate the image to be post processed efficiently. For example, if we need to access alpha and color channels, we call:
static R3INT alpha_offset, rgb_offset; if(!(alpha_offset = (int)R3DoClassA(R3CLID_RAYSAMPLE, R3RAYSMPCM_INSTALLCHANNEL, (void *)R3CLID_ALPHACHANNEL))) return FALSE; if(!(rgb_offset = (int)R3DoClassA(R3CLID_RAYSAMPLE, R3RAYSMPCM_INSTALLCHANNEL, (void *)R3CLID_COLORCHANNEL))) return FALSE;
Let's imagine our effect uses alpha channel to control particle transparency. We have to make sure the user will be able to write materials which can access alpha channel from post processing phase (shader). To do this:
if(!R3DoClassA2(R3CLID_PHASESYSTEM, R3PHSYSCM_REGISTERINPUTCHANNEL, (void *)R3CLID_PHASEPOSTPROCESSING, (void *)R3CLID_FADECHANNEL)) return FALSE; if(!R3DoClassA2(R3CLID_PHASESYSTEM, R3PHSYSCM_REGISTEROUTPUTCHANNEL, (void *)R3CLID_PHASEPOSTPROCESSING, (void *)R3CLID_FADECHANNEL)) return FALSE;
If post processing is activated (the user has specified post image through the File Renderer's or View Property window's Post Image field), ray tracer renders the image to the specified 'post image' object rather than to the actual output device.
The renderer will create the post image object so that it contains all the channels requested by the associated post effects. It does this by fetching:
R3GetAttrs(posteffect, R3PPEA_NeedsChannels, &channelarray, R3TAG_END);
Effects return null terminated string pointer array which specifies names for all channels the effect needs to get its job done. You should not hard code channel names. Instead, fetch the channel names from the channel classes in your registration function:
/* correct, let classes define channel names */ R3GetClassAttrs(R3CLID_COLORCHANNEL, R3RCA_Name, &needschannels[0], R3TAG_END); R3GetClassAttrs(R3CLID_ZCHANNEL, R3RCA_Name, &needschannels[1], R3TAG_END); R3GetClassAttrs(R3CLID_ALPHACHANNEL, R3RCA_Name, &needschannels[2], R3TAG_END); needschannels[3] = NULL;
As described above, the renderer will call:
/* in R3RM_GET */ case R3PPEA_NeedsChannels: *(char ***)tag-value = needschannels; break;
to fetch the channels needed by the effect. This way the renderer assures the post image will contain all the channels needed by the associated effects.
These are all attribute you need to handle in the get method. Note that the renderer is not interested about any effect specific attributes your class may define so you don't have to catch them in the R3RM_GET method. Just handle them in the R3RM_SET so that the model class can define them.
When the ray tracer terminates and the post image is completely ready, your image will receive three methods:
R3PPEM_BEGIN: In this method, you prepare the post image for rendering. The parameters for the method are:
static void r3ppem_begin(R3CLASS *cl, R3OBJ *obj, R3OBJ *drawp, R3MATRIX modelview, R3TAG *tags)
The modelview matrix defines transformation which allows you to project particle coordinates to post image coordinates. Your effect can therefore use both the 3D model coordinates and 2D image coordinates.
The tag list passed as 'p3' contains the following attributes:
R3PPEA_BoxW R3PPEA_ImgW R3PPEA_Tracer R3PPEA_TraceMethod
You should create and associate a private drawport with the post image. In this method you also initialize the drawport and set desired drawing modes for desired channels. Your effect may get applied to thousands of particles so you have to prepare as many things as possible in this method. For example:
/* First we allocate a private drawport so that evaluate method repetitions do not have to redefine drawing settings (which could be modified by other effects) */ R3GetAttrs(drawp, R3DRAWPA_Output, &output, R3DRAWPA_Aspect, &aspect_ratio, R3TAG_END); /* create drawport for post image 'output' */ self->drawp=R3New(R3CLID_DRAWPORT, R3DRAWPA_Output, output, R3DRAWPA_Aspect, &aspect_ratio, R3TAG_END); /* initialize */ R3DoA(self-drawp, R3DRAWPM_INITIALIZE, NULL); /* set desired draw mode for the 'Color' channel */ R3DoA3(self-drawp, R3DRAWPM_SETDRAWMODE, colorname, R3DRAWP_SUBCH0, (void*)R3DRAWPMODE_REPLACE); R3DoA3(self-drawp, R3DRAWPM_SETDRAWMODE, colorname, R3DRAWP_SUBCH1, (void*)R3DRAWPMODE_REPLACE); R3DoA3(self-drawp, R3DRAWPM_SETDRAWMODE, colorname, R3DRAWP_SUBCH2, (void*)R3DRAWPMODE_REPLACE);
R3PPEM_END
Correspondingly, in the R3PPEM_END method you should delete the drawport or any other resources you allocate in the R3PPEM_BEGIN method.
R3PPEM_EVALUATE:
This is the actual work horse. In this method, you render your effect to the post image. The syntax of the method is as follows:
static void *r3ppem_evaluate(R3CLASS *cl, R3OBJ *obj, int phase, void *raysample)
The raysample contains properties for the particle to be rendered. For example, to fetch the position of the particle, call:
R3VECTOR *pos = R3CHADDR(raysample, asc_offset);
To map the position to post image coordinates, call:
R3VECTOR p; R3MxTransform(&p, self-modelview, pos); /* project point to image space */
Note: you get the modelview matrix as a parameter to R3PPEM_BEGIN.
Then, to draw a disk whose center is on 'pos', call:
/* Three pens asr used when drawing discs. Define channels */ /* PEN0 defines middle point properties */ R3DoA(drawp, R3DRAWPM_SELECTPEN, R3DRAWP_PEN0); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH0, (void*)&col-red); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH1, (void*)&col-green); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH2, (void*)&col-blue); R3DoA2(drawp, R3DRAWPM_SETPEN, alphaname, (void*)a); R3DoA2(drawp, R3DRAWPM_SETPEN, zname, (void*)&dist); R3DoA2(drawp, R3DRAWPM_SETPEN, R3DPCH_COVERAGE, (void*)&coverage); /* PEN1 defines middle radius */ R3DoA(drawp, R3DRAWPM_SELECTPEN, R3DRAWP_PEN1); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH0, (void*)&col-red); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH1, (void*)&col-green); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH2, (void*)&col-blue); R3DoA2(drawp, R3DRAWPM_SETPEN, alphaname, (void*)a); R3DoA2(drawp, R3DRAWPM_SETPEN, zname, (void*)&dist); R3DoA2(drawp, R3DRAWPM_SETPEN, R3DPCH_COVERAGE, (void*)&coverage); /* PEN2 defines outer edge */ R3DoA(drawp, R3DRAWPM_SELECTPEN, R3DRAWP_PEN2); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH0, (void*)&col-red); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH1, (void*)&col-green); R3DoA3(drawp, R3DRAWPM_SETPEN, colorname, R3DRAWP_SUBCH2, (void*)&col-blue); R3DoA2(drawp, R3DRAWPM_SETPEN, alphaname, (void*)a); R3DoA2(drawp, R3DRAWPM_SETPEN, zname, (void*)&dist); /* Outer edge coverage is always zero to fade particle edge smoothly away, * no need to redefine it here */ R3DoA3(drawp, R3DRAWPM_DISC, &p.r, &p.s, &rad);