Tutorial 2 - Doctor Strange Inspired Portals

March 20, 2018


In this tutorial, we're creating the portal effect seen in Doctor Strange. Although not perfect, it is close to the effect used and can likely be slightly adjusted to be exact.



This tutorial is quite VEX heavy and uses some vector math. It is fully explained below but can still be a little confusing to grasp.


We're going to create a couple of attribute wrangles and use them to manipulate normals which we will convert into velocity to be used in a pop-network. 




Below you will find all of the code used in each attribute wrangle:






This multiplies our N attribute (normals) by a multiplier channel parameter. abs() ensures that the multiplier is always positive so that our normals are never inverted.




    matrix3 rot = ident ();
    rotate (rot, radians (15), { 0, 0, 1} );
    @N *= rot;




This takes 10% of all points every frame and rotates their N attribute by 15 degrees on the Z-Axis.


if(rand(@ptnum*@Frame)<0.1) is used to choose 10% of the points at random every frame. rand(@ptnum*@Frame) returns a value between 0 and 1 per point at random, thus, 0.1 is approximately aggregated to 10% of all points. When using rand() its important to understand that the value inside the brackets is a seed, meaning that if you use only @ptnum then each point will have a random number assigned to it but it wont change as @ptnum is constant. By multiplying it by @Frame, we get a changing value per point.


matrix3 rot = ident () sets the rot attribute as an identity matrix.


rotate (rot, radians (15), { 0, 0, 1} ) applies a rotation of 15 degrees (which is converted to radians) on the Z-Axis to our matrix. 


@N *= rot takes our newly rotated matrix and multiplies our N attribute by it to in turn rotate each normal.




Each GIF shows exactly what each snippet of code does to the normals of the curve when run alone.


f@flare = fit01(abs(sin(@Time)), 0.5, 1);
@N *= @flare;


fit01(abs(sin(@Time)), 0.5, 1)  is used to create a 'breathing' effect. The numbers range between 0.5 and 1 due to the fit01 function. The abs() function sets the sine function to only output positive values or else our normals will point inside the circle at some point. As time progresses, the sin value fluctuates based on the workings of a sine graph (0 to 1 to -1) but is set to an absolute value thus preventing negative numbers.



float xdir = sin(@Time*3)/2;
float ydir = cos(@Time*3)/2;
vector direction = set(xdir, ydir, 0);
float r=fit01(rand(@ptnum*@Time), 0.1, 0.5);

@N+=cross(@N, direction)*r;


float xdir = sin(@Time*3)/2 and float ydir = cos(@Time*3)/2 create two parameters that fluctuate smoothly between 1 and -1 based on sin and cosine graphs respectively. We divide them by two to narrow their range (squash along the Z-axis).


vector direction = set(xdir, ydir, 0) assigns the two parameters to the X and Y values of a vector. We set the Z value to 0 because we do not want any movement on the Z axis.


float r=fit01(rand(@ptnum*@Time), 0.1, 0.5) merely outputs a random value between 0.1 and 0.5 every time-step (similar to our reverse node).


@N+=cross(@N, direction)*r takes our current N value and finds the cross product (the vector perpendicular to the two original vectors). To better understand cross products, go ahead and watch this. We then multiply that cross product by the random number that we generated to add noise to the value and then add it to N value. (The GIF shows the motion that this creates ignoring the random value).






float timer = 2*@Time-@curveu;

@N*=fit01(@brightness, 0.4, 0.6);


float timer = 2*@Time-@curveu outputs a constantly increasing value that allows us to loop through our timing ramp.


@brightness=chramp('timing_ramp',timer) creates a ramp parameter which is controlled using the negative timer attribute. We make it a negative so that it runs along our ramp from the end to the beginning. The ramp that is made loops for each full unit that our timer parameter completes. So it will go from 0 to 1 and then 1 to 2, resetting where it is along the curve each completion of a unit.


@N*=fit01(@brightness, 0.4, 0.6) is used to fit our brightness attribute between 0.4 and 0.6 instead of a larger range of numbers. Our N attribute is multiplied by the narrowed brightness attribute.







@N*=rand(@ptnum*@Time) is used to create noise in the effect. You will notice that it is a harsh noise which is added. I just find that it helps the effect of 'sparking' yet still keeps the motion that we have created prior to it.


@v=@N finally we cast our N attribute to our v attribute which is used as velocity.


When combined with the other code snippets, we end up with this motion.






@emitint = @brightness*chf('brightness_mult');

float fader = fit01(@Time,0,1);
@emitint*= fader;

@opac *= fader;


v@emitcolor = chramp('clr_ramp',@age);

@emitint is an attribute recognized by the principled shader. It stands for 'emission intensity' (it can be thought of as brightness).


@brightness*chf('brightness_mult') here we use our brightness attribute from earlier as well as a multiplier for better control.


float fader = fit01(@Time-1,0.1,1) creates a parameter which has a maximum of 1 and a minimum of 0.1 and increases up to 1 over 2 seconds. 


@emitint *= fader; we allow this fader parameter to fade our emitint in as time passes.


@opac = 0.025 is another attribute recognized in the shader. It is the opacity of the point. Here we set it low because we mostly want emission to be the driving parameter but we do not want to get rid of opacity entirely.


@emitint*=abs(@age-1) is used to fade the particles' intensity over their lifetime. Age is a value between 0 (alive) and 1 (dead). 


v@emitcolor = chramp('clr_ramp',@age) finally we control the color using a color ramp parameter and drive it by the age attribute so that particles change color as they age.


The parameters that will affect the style of the portal are the following:


2000seg 5mult count10000 life0.01 var0.05  (Default)



The number of segments in the re-sample node - Makes circle more/less defined



100seg 5mult count10000 life0.01 var0.05 



The velocity multiplier in the MULT wrangle - Makes streaks longer/shorter




2000seg 10mult count10000 life0.01 var0.05 


The number of particles emitted in the pop-net - Makes effect more/less definite

The particle lifetime - Increases/Decreases effect density


2000seg 10mult count75000 life0.05 var0.05 





If VEX is interesting to you then you should check out Entagma


If you would like to find documentation on VEX code


If you think we're awesome then go like our page


or subscribe to our YouTube Channel


If you would like us to recreate any movie effects then please comment down below and we'll try our best to do it.

 Thanks for watching and reading but that's it for this tutorial.



Share on Facebook
Share on Twitter
Please reload

Related Posts
Please reload

Follow Us
  • Facebook - Black Circle
  • Twitter - Black Circle
  • YouTube - Black Circle
Support our Tutorials
Search By Month
Please reload

Search By Tag
Please reload

Recent Posts