ANIMANDELPRO for Windows was intended as a tool for its developer but I offer it as an example of how scripting
animation can add a dimension to fractal art in the hopes others will adopt a similar approach.
I would love to have a better tool but one that offers just as much freedom to explore. Download Here win32 executable 353kb. |
PrerequisitesBefore you use AnimandelPro you need at least a basic level of coding experience. The "Pro" in the title means: "You better be a pro coder if you want to avoid crashing your machine with this." You will need an OpenGL Shader 2.0 compliant GPU. You will need to adjust TLDR (look it up on MSDN). You may need to turn off Data Execution Protection but its unlikely. At least the install is easy, just unzip keeping the folder structure and run AnimandelPro.exe. If you get an error saying you need some ms???.dll get it from Microsoft.When the app starts you see two windows. A text editor and a picture frame. Thats really all there is to it. If the picture frame has a picture in it then you have a compliant GPU! If not then you are best off ignoring the rest of this document as it won't help you (sorry). In AnimandelPro you write 3 kinds of scripts, an animation script that interacts with the app that then calls the vertex shader script and finally the fragment shader which does most of the work drawing the picture. Typically you use the mouse and arrow keys to "drive" the camera recording the positions for playback at a higher resolution. You then write an animation script to add "characters" or change the scene. Once you have an animation you like you can choose an audio file and start video capture. The video is captured as an AVI which is limited to a few gigs so use compression or output as TGA files. You save your scripts and data in seperate files and then save your project in a .MNDL file. MenusFile:New - ERASES ALL DATA! Use wisely. Open - Opens a text, mndt (flight data) or mndl (project) file. Save - Saves the current text file. Save as - Saves the current text, mndt or mndl file. (Specify the correct extension!) Save All - Saves all documents in the current project. Compile - Compiles all scripts and reports any errors. Run - Compiles all scripts and outputs an image. Exit - When you can't take it anymore. Edit: Undo, Copy, Cut, Paste work as you expect. Find - Allows you to find a phrase or line #. (Unfortunately it does not scroll to the end of the document.) Copy Image - If you just want to grab the current rendering and place it on the clipboard. Animation Script, Vertex Shader and Fragment Shader - To edit the script. Debug Output - To view the debug window. Options Parameters - Opens a window to set the camera position and rotation manually, adjust the timer, user defined params and load texture files. Output - Opens a window to set Width, Height, Output file etc. Here are some non-obvious ones: DOF Scale, Offset and Max Depth work together to produce realistic depth of field. A larger scale produces more of an effect. The offset is always between 0.0 (at the camera) and 1.0 (at max depth). To output TGA files just remove the .AVI extension of the output file name and a frame number and .tga will be appended. (ALL OUTPUT FILES ARE OVERWRITTEN WITHOUT WARNING! THIS IS YOUR WARNING ;) Layer Layer 1,2,3,4 - You can have up to 4 shader layers (the depth is recorded at each layer) and you can use the animation script to repeat layers so no excuses. Delete Layer - Removes the text from this layer. Files are not deleted but any unsaved edits are lost. Replay As you drive the camera your position is stored. You can play back the positions later to record the video. Rewind to Start, Jump to End - Basic recorder functions. Replay, Foreplay and Slerp - You can run the recording forwards, backwards and in slow motion (slerp uses the current animSpeed to interpolate between camera data). Mark Beginning, Mark End - Recording video will automatically halt when you reach the end of the data so mark it, record it and walk away. Record Data - Toggle the recording of movement on/off. Clear - Clear all data from memory. Navigating with the Camera (mouse and keyboard)Manually - In the Options Parameters screen you can set the x,y,z translation and x,y,z axis rotation but most often you will hit the up-arrow to start "flying". The controls are:Z - Zero the mouse. X - Release the video capture file. C - Toggle video capture on and off. V - Draw one frame. S - Stop motion (this is an important one, you'll see what I mean) Up Arrow - Accelerate and START FLYING. Down Arrow - Accelerate backwards. Left - Move left. Right - Move right. Ctrl Left - Roll left. Ctrl Right - Roll right. Mouse Pointer - Point where you want to go.(did I really need to explain this?) Writing the Animation ScriptThis is the most difficult section but to start you need to do nothing so NO WORRIES. There are 6 functions that you can optionally choose to impliment.udSetup - This function is called just after compilation so you use it to malloc any memory you will need, zero any counters etc. udResize - This function is called when you change the size of the output so that buffers can be resized. udPre - This is called once per layer (every layer that has scripts). This is where you will do most programming setting up positions, changing colors etc. udPost - This is called after each layer is drawn. Here you can save the output to use as a texture or read the depth buffer. udPostFrame - This is called after all layers have drawn. Here you can "touch up" the output before it is saved. (You only see the changes in saved output not the screen.) udShutdown - This is called right before recompilation so FREE YOUR RESOURCES. Parameters are passed as pointers to arrays which is DANGEROUS AND YOU WILL LOCK UP YOUR MACHINE at some point. I've done it twice :) But it provides a great deal of freedom which is required when creating art. udSetup(HWND hWnd, float *ud, float *pram, int *ct, char *dbug){ SAVE_SETUP_POINTERS //this saves the parameters for use globally, they are read/write arrays //"g_hWnd" is the trace window handle so you can mimic key presses //SendMessage(g_hWnd,0x100,'X',0);//sends the stop capture key //"udef" is an array of 32 floats that are sent to the shaders. You define what these mean. //"param" is an array of floats from the app. There are #defines for each TIME, MAX_DEPTH etc. //"cnt" is an array of integers from the app. There are #defines for these as well WIDTH, HEIGHT etc. //"debug" is a char array (MAX_PATH) for writing to the debug window sprintf(debug, "Hello World of Fractal Animation - the dimensions are %i %i",WIDTH, HEIGHT); } udPre(unsigned char *tex, float *replay){ //CHECK tex!=NULL BEFORE USING. It is only available when a texture is assigned to this layer //tex - is a 32bit bitmap with BGRA values that can be accessed with a texture2D call in frag shader if(tex)tex[((TEX_HEIGHT/2)*TEX_WIDTH+TEX_WIDTH/2)*4+1]=255;//I just made a green dot in the middle of the screen. //replay - this is the camera position data and timing (ALL DATA POINTS) //replay[RP_POS+0],1,2=x,y,z,3=time,4-7=rotation quaternion replay[RP_POS+MNDT_FLOATS+1]+=sin(replay[cnt[2]+3]);//I am adding bumpiness to the next y value } udPost(){ //this is where you can read the color or depth values (see file autofocus.anim for an example) } udPostFrame(unsigned char *pix, int *cnt, char *debug){ //pix is a 24 bit BGRA bitmap (NOTICE THE BIT DEPTH HAS CHANGED, NO ALPHA DATA AS THIS IS FOR OUTPUT) for(int y=0;y!=HEIGHT;y++)for(int x=0;x!=WIDTH;x++){ int accum=pix[(y*WIDTH+x)*3]; accum+=pix[(y*WIDTH+x)*3+1]; accum+=pix[(y*WIDTH+x)*3+2]; accum/=3; pix[(y*WIDTH+x)*3]=accum; pix[(y*WIDTH+x)*3+1]=accum; pix[(y*WIDTH+x)*3+2]=accum;//black and white now } } udResize(){ //WIDTH=new width, HEIGHT=new height, resize any allocated buffers } //to pass variables between functions just make them global unsigned char *myTextureBuffer=NULL; udShutDown(){ if(myTextureBuffer)free(myTextureBuffer);//this is only called once so don't need to set it to NULL } The predefined variables are: (integers) LAYER = The current layer being drawn. WIDTH = The width of the output image. HEIGHT = The height of the output image. TEX_Width = The width of this layer's texture image. TEX_HEIGHT = The height of this layer's texture image. RP_START = The camera replay start index. (typically but not always zero) RP_POS = The current camera replay position. RP_END = The last camera replay position. MNDT_FLOATS = The number of floats in each camera position (12) x,y,z,time,qrotx,qroty,qrotz,qrotw,udef0,udef1,udef2,udef3 SUPERSAMPLING = 0 if regular render, 1=second supersampled render (don't animate twice) FPS = The number of frames per second for the output AVI. Time accordingly. REBIND_TEXTURE = 1 tells the app you have changed the texture so it needs re-binding. JUMP_LAYERS = Tells the app how many layers to skip over. -1=redo this layer. 0=proceed normally (floats) CAM = The x,y,z position of the camera. CAM_QUAT = The quaternion rotation of the camera. See animandel.h for quaternion math. TIME = The app timer (not in any real units, you determine the speed) TIME_DELTA = The speed of the timer (how many ticks per frame) DOF_SCALE = The highest depth of field mipmap level. DOF_OFFSET = 0 - the dof starts at the camera, 0.5 dof starts half way to MAX_DEPTH MAX_DEPTH = The maximum depth for DOF (also sent to shaders for use as a maximum depth of raytracing) udef[0]..udef[31] = You decide what these are and use them in the shaders. 0-4 can be driven by the keybaord. g_hWnd = The trace window handle.You can access files, call OpenGL and Windows functions etc. Look in the lib directory for include files. DebuggingThere are some limited debugging capabilities with the animation script. Along with writing to the debug window you can also set watches on simple variables and array elements under the menu Edit->Watch. Give the variable name, array index (offset) and data type. The value will be displayed and updated every frame.The Vertex ShaderNot much to talk about here. The vertex shader converts the quaternion camera rotation into a ray direction for each pixel. The code is only run 4 times, once for each corner of the screen and the remaining values are interpolated. You can generally use it as is. Modify it if you are adding multiple "characters" to one fragment shader. An example is given in the default_vertex_shader.vert file.The Fragment ShaderThis is where all the work is done. Each pixel on the screen is sent thru this script so the architecture does them in parallel (in batches). It is important to keep this in mind to make your script run fast. Try not to send pixels down different code paths like://slower if(z<0.0){ //work with positive z }else{ //work with negative z } //both code paths are run (the values are just tossed out) //faster float side = sign(z); z = abs(z); //work with positive z only z*=side;To start just play with the DE function. Look at some of Knightly's scripts at FractalForums.com for inspiration. The DE function takes a point, performs some fractal movements of the point and then returns the max distance you can move before having to recheck. When this max distance becomes smaller than a pixel on the screen you stop and draw it. For example lets take a simple sphere. float DE(vec3 z0){ //this isn't a fractal so we are leaving out the fractal folding, rotations etc //we are stepping a ray into the screen and we are at the point z0. How far can we go //until we could possibly hit a sphere centered at the origin with a radius "radiusOfSphere"? //Answer: return length(z0)-radiusOfSphere; }This gets us a little bit closer to the sphere each time we call it until we either hit it or miss it. A simple approach is to use rotations, folding and scaling and then keep track of a running scale. For example start with dr=1.0 then as you scale your vector z, z=z*scale also scale dr, dr=dr*scale. Then return (length(z)-radius)/dr. For more complicated DE functions sometimes the formula: 0.5 * log(r) * r / dr is used. Here r is the radius (length from point to origin) and dr is the running derivative of the radius. For example if our function was Z=Z^8+C then dr=dr*8*r^7 but if the function was Z=Z^8+Z0 then dr is calculated as dr=dr*8*r^7+1.0. You can mix the two approaches like this: 0.5 * log(r) * r / (dr1*dr2) Look at the examples and you will get the hang of it. Now its up to you to invent better DE functions. You can see example videos at You Tube (ytalinflusa). Have fun and send us your artwork! TutorialsPositioning Lights. The Hybrid System. |