function FDFDemo(dotDensity, dotLifetime) % FDFDemo(dotDensity, dotLifetime) - Demo of "formless dot field" stimulus % via moglFDF. % % This demo generates a simple "formless dot field" random dot motion % stimulus to create "structure from motion" percept by use of the moglFDF % function for formless dot field rendering. See "help moglFDF" for more % details. % % The demo shows a simple spinning 3D sphere, rendered as random dot % stimulus. % % The following optional parameters can be provided to FDFDemo: % % dotDensity = Number of dots to use for both, the background- and % foreground distribution. Defaults to 10000. % % dotLifetime = Lifetime of dots in frames. Defaults to 10 frames. % % % Control keys: % % ESCape key finishes the demo. % % SPACE key toggles between a slowly rotating sphere and a static sphere. % % 'd' toggles the display between the formless dot field stimulus and some % debug visualization. % % 't' toggles drawing of foreground dots in the colors defined by the % texture map of the drawn object. % % 'r' resets the distribution to empty, then incrementally recreates it. % % 'h' resets the distribution to a completely new random one. % % Arrow left/right control the density of dots, the 'dotDensity' paramter % in decrements/increments of 5%. % % Arrow up-/down controls the 'dotLifetime' in steps of +/- 1. % % History: % 05/02/08 Written (MK). % 11/03/08 Documentation update, preparation for public release (MK). % 11/15/08 Demonstrate new features of moglFDF, allow runtime change of % some params (MK). % Setup default settings: if nargin < 1 || isempty(dotDensity) % 10000 dots by default: dotDensity = 10000; end if nargin < 2 || isempty(dotLifetime) % 10 frames lifetime per dot by default: dotLifetime = 10; end debug = 0; % Is the script running in OpenGL Psychtoolbox? Abort, if not. AssertOpenGL; % Setup unified keyboard mapping: KbName('UnifyKeyNames'); escape = KbName('ESCAPE'); space = KbName('space'); dKey = KbName('d'); rkey = KbName('r'); hkey = KbName('h'); tkey = KbName('t'); upArrow = KbName('UpArrow'); downArrow = KbName('DownArrow'); leftArrow = KbName('LeftArrow'); rightArrow = KbName('RightArrow'); % Find the screen to use for display: screenid=max(Screen('Screens')); % Setup Psychtoolbox for OpenGL 3D rendering support and initialize the % mogl OpenGL for Matlab wrapper: InitializeMatlabOpenGL([], 0); % Open a double-buffered full-screen window on the main displays screen, % with fast Offscreen window support enabled and black background clear % color. Fast Offscreen windows support is needed for moglFDF to work. PsychImaging('PrepareConfiguration'); PsychImaging('AddTask', 'General', 'UseFastOffscreenWindows'); [win , winRect] = PsychImaging('OpenWindow', screenid, 0); try % Prepare texture to by applied to the sphere: Load & create it from an image file: myimg = imread([PsychtoolboxRoot 'PsychDemos/OpenGL4MatlabDemos/earth_512by256.jpg']); % Make a special power-of-two texture from the image by setting the enforcepot - flag to 1 % when calling 'MakeTexture'. GL_TEXTURE_2D textures (==power of two textures) are % especially easy to handle in OpenGL. If you use the enforcepot flag, it is important % that the texture image 'myimg' has a width and a height that is exactly a power of two, % otherwise this command will fail: Allowed values for image width and height are, e.g., % 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 and on some high-end gfx cards % 4096 pixels. Our example image has a size of 512 by 256 pixels... mytex = Screen('MakeTexture', win, myimg, [], 1); % Retrieve OpenGL handles to the PTB texture. These are needed to use the texture % from "normal" OpenGL code: [gltex, gltextarget] = Screen('GetOpenGLTexture', win, mytex); % Debug output settings for moglFDF: Most useful are -1 for real object % render and 1 for silhouette render, as well as 0 for normal op. moglFDF('DebugFlag', 0); % Stimulus parameters: % Size of the final output window to be drawn to: rect = Screen('Rect', win); % Texture coordinates on the surface of our demo object are in the % range 0.0 to 1.0 in both x- and y- direction: texCoordMin = [0.0 , 0.0]; texCoordMax = [1.0 , 1.0]; % Resolve motion with 512 x 512 resolution: texResolution = [256 , 256]; % Probability with which a randomly located dot within the silhouette % is drawn -- Kind of "Signal to noise" ratio within the objects % silhouette, if the "object-induced dot motion" is considered the % signal and the noise is considered the noise. % Values between 0 - 1 are meaningful: BGSilhouetteAcceptanceProbability = 0.0; % Use max 'dotDensity' foreground dots for sampling the objects % surface: In the current moglFDF implementation, maxFGDots must be an % integral multiple of the dotLifetime! maxFGDots = (1 - BGSilhouetteAcceptanceProbability) * dotDensity; % Use max 'dotDensity' dots for background distribution: maxBGDots = dotDensity; % Init texture mapping toggle flag to "texturemapping off": textoggle = 1; % Use occlusion culling: Dots that would stick to the occluded part of % the 3D objects surface are not drawn. By default - if this parameter % is omitted or set > 1 - all dots are drawn, even "occluded" ones. zThreshold = 0.0001; fdf = moglFDF('CreateContext', win, rect, texCoordMin, texCoordMax, texResolution, maxFGDots, maxBGDots, dotLifetime, zThreshold, BGSilhouetteAcceptanceProbability); % Define actual string of commands that renders the 3D object or scene: % This command sequence will draw our sphere 'mysphere' at its current % orientation. See setup code below for the definition of 'mysphere'. callbackEvalString = 'gluSphere(mysphere, 0.7, 100, 100);'; fdf = moglFDF('SetRenderCallback', fdf, callbackEvalString); % If texture mapping is on, load a texture mapping shader to % demonstrate mixing static color with texture: if textoggle > 0 drawShader = LoadGLSLProgramFromFiles('moglFDFTexturedDotsRenderShader.frag', 1); glUseProgram(drawShader); glUniform1i(glGetUniformLocation(drawShader, 'Image'), 0); % Tell shader that it should roll its own pointsprite based % anti-aliasing, instead of leaving the job up to the GPU: % Very recent GPU's may be able to do this themselves, e.g., NVidia % Geforce 8800 and later on OS/X 10.5.6 and later. In such a case, % setting doPointSprites to zero may provide a slight speedup: doPointSprites = 1 glUniform1i(glGetUniformLocation(drawShader, 'doSmooth'), doPointSprites); % Assign mixweight: 0.0 = static color only, 1.0 = texture only, % intermediate levels provide a mix between 0% and 100% texture: glUniform1f(glGetUniformLocation(drawShader, 'texWeight'), 0.5); glUseProgram(0); % Assign shader for 2D foreground dot draw: fdf = moglFDF('SetDrawShader', fdf, drawShader, [], doPointSprites); % Enable texture: fdf = moglFDF('SetColorTexture', fdf, gltex, gltextarget); else drawShader = []; end % Setup the OpenGL rendering context of the onscreen window for use by % OpenGL wrapper. After this command, all following OpenGL commands will % draw into the onscreen window 'win': Screen('BeginOpenGL', win); % Get the aspect ratio of the screen: ar=winRect(4)/winRect(3); % Setup default drawing color to white (R,G,B)=(1,1,1): glColor3f(1,1,1); % Enable proper occlusion handling via depth tests: glEnable(GL.DEPTH_TEST); % Set projection matrix: This defines a perspective projection, % corresponding to the model of a pin-hole camera - which is a good % approximation of the human eye and of standard real world cameras -- % well, the best aproximation one can do with 3 lines of code ;-) glMatrixMode(GL.PROJECTION); glLoadIdentity; % Field of view is 25 degrees from line of sight. Objects closer than % 0.1 distance units or farther away than 100 distance units get clipped % away, aspect ratio is adapted to the monitors aspect ratio: gluPerspective(25,1/ar,0.1,100); % Setup modelview matrix: This defines the position, orientation and % looking direction of the virtual camera: glMatrixMode(GL.MODELVIEW); glLoadIdentity; % Reposition camera: Sitting at (x,y,z)==(0,0,5), looking at the origin % (0,0,0), in an upright (0,1,0) orientation: gluLookAt(0,0,5,0,0,0,0,1,0); % Set background clear color to 'black' (R,G,B,A)=(0,0,0,0): glClearColor(0,0,0,0); % Clear out the backbuffer: This also cleans the depth-buffer for % proper occlusion handling: You need to glClear the depth buffer whenever % you redraw your scene, e.g., in an animation loop. Otherwise occlusion % handling will screw up in funny ways... glClear; % Enable texture mapping for this type of textures... glEnable(gltextarget); % Bind our texture, so it gets applied to all following objects: % Btw. this whole texture setup is pretty futile for random dot field % rendering as we don't apply any textures, we only use the texture % coordinates! We apply a texture anyway, so the users sees something % nice if one of the debug modes is enabled where the object is % renderered in a "normal way" for illustrative purposes. glBindTexture(gltextarget, gltex); % Textures color texel values shall modulate the color computed by lighting model: glTexEnvfv(GL.TEXTURE_ENV,GL.TEXTURE_ENV_MODE,GL.MODULATE); % Clamping behaviour shall be a cyclic repeat: glTexParameteri(gltextarget, GL.TEXTURE_WRAP_S, GL.REPEAT); glTexParameteri(gltextarget, GL.TEXTURE_WRAP_T, GL.REPEAT); % Set up minification and magnification filters. This is crucial for the thing to work! glTexParameteri(gltextarget, GL.TEXTURE_MIN_FILTER, GL.LINEAR); glTexParameteri(gltextarget, GL.TEXTURE_MAG_FILTER, GL.LINEAR); % Create the sphere as a quadric object. This is needed because the simple glutSolidSphere % command does not automatically assign texture coordinates for texture mapping onto a sphere: % mysphere is a handle that you need to pass to all quadric functions: mysphere = gluNewQuadric; % Enable automatic generation of texture coordinates for our quadric object: gluQuadricTexture(mysphere, GL.TRUE); % Apply some static rotation to the object to have a nice view onto it: % This basically rotates our spinning earth into an orientation that % roughly matches the real orientation in space... % First -90 degrees around its x-axis... glRotatef(-90, 1,0,0); % ...then 18 degrees around its new (rotated) y-axis... glRotatef(18,0,1,0); % OpenGL setup done: Switch back to 2D mode: Screen('EndOpenGL', win); % Init the rotation 'toggle' flag (see above) to "rotation enabled": toggle = 1; % Our framecounter, we love stats ;-) fcount = 0; % Toggle reset of dot distribution at first loop iteration: resetDistribution = 1; % Initial Flip to have a nice black display, and to record the 'tstart' % timestamp of this demo animation: tstart = Screen('Flip', win); % Now for our little animation loop. This loop will run until a key is pressed. % It rotates the object by a few degrees (actually: Applies a rotation transformation % to all objects to be drawn) and then redraws it at its new % orientation: while 1 % Want to reinit the dot distribution? if resetDistribution if resetDistribution == 2 % Perform a single initial object-render, update & recompute cycle for % set of dots. moglFDF will compute the new dot distribution, based on % the current 3D scene appearance, but it won't draw the new dot % distribution for the next frame yet. The special flag '1' asks % 'Update' to generate a full initial distribution: fdf = moglFDF('Update', fdf, 1); else % Reset state to empty distribution, so it can recreate from scratch: fdf = moglFDF('ResetState', fdf); end resetDistribution = 0; end % Update rotation angle of rotating sphere for this redraw cycle: % glRotate the object by 0.1 degrees around its z-axis if toggle is set to 1. Screen('BeginOpenGL', win); glRotatef(toggle * 0.1, 0, 0, 1); Screen('EndOpenGL', win); % Perform object-render, update & recompute cycle for set of dots. % moglFDF will compute the new dot distribution, based on the % current 3D scene appearance, but it won't draw the new dot % distribution for the next frame yet: fdf = moglFDF('Update', fdf); % Wanna have nice looking random dots? if 1 % Enable alpha blending and smooth dots for nice looking % anti-aliased dots: glBlendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); glEnable(GL.BLEND); glPointSize(5.0); glEnable(GL.POINT_SMOOTH); end % Render 2D dot set in white: glColor3f(1,1,1); % This performs the actual high-speed drawing of the dot field into % window 'win': fdf = moglFDF('Render', fdf, win, [1 1]); % Set to 1 for readback of dots and "manual" visualization: if 0 % This is a slow alternative to moglFDF('Render'): % The final dot distribution is read back from the GPU into a % Matlab dot matrix 'xydots'... xydots = moglFDF('GetResults', fdf); % For the fun of it, some stats of the xydots are computed and % printed -- here the minimum and maximum 2D dot locations... minx=min(xydots(1,:)) %#ok miny=min(xydots(2,:)) %#ok maxx=max(xydots(1,:)) %#ok maxy=max(xydots(2,:)) %#ok % And good'ol Screen('DrawDots') is used to draw the 'xydots' % vector of dots: Screen('DrawDots', win, xydots, 2, [255 0 0], [], 1); end % Done with drawing, disable alpha blending again: glDisable(GL.BLEND); % Check for and handle keyboard input: [pressed secs keyCode] = KbCheck; if pressed % Pressing ESCape finishes the demo by breaking out of the % animation loop: if keyCode(escape) break; end % Pressing SPACE toggles the 3D rotation of the sphere. As soon % as rotation stops and motion information therefore gets lost, % the perception of the sphere will quickly degrade into random % dot blinking: if keyCode(space) KbReleaseWait; toggle = 1 - toggle; end if keyCode(tkey) KbReleaseWait; textoggle = 1 - textoggle; if textoggle % Enable texture mapping: fdf = moglFDF('SetColorTexture', fdf, gltex, gltextarget); if ~isempty(drawShader) fdf = moglFDF('SetDrawShader', fdf, drawShader); end else % Disable texture mapping: fdf = moglFDF('SetColorTexture', fdf, [], []); fdf = moglFDF('SetDrawShader', fdf, []); end end % 'r' key resets the distribution: if keyCode(rkey) resetDistribution = 1; end % 'h' key resets the distribution and reinits it immediately: if keyCode(hkey) resetDistribution = 2; end % Arroy keys control dot density and lifetime: if any(keyCode([leftArrow, rightArrow, upArrow, downArrow])) % Change of distribution parameters requested: if keyCode(leftArrow) dotDensity = max(dotLifetime, round(dotDensity * 0.95)); end if keyCode(rightArrow) dotDensity = min(1000000, round(dotDensity * 1.05)); end if keyCode(upArrow) dotLifetime = min(500, dotLifetime + 1); end if keyCode(downArrow) dotLifetime = max(1, dotLifetime - 1); end % Recompute number of dots in distribution: maxFGDots = (1 - BGSilhouetteAcceptanceProbability) * dotDensity; % Use max 'dotDensity' dots for background distribution: maxBGDots = dotDensity; % Reinit context with new settings, but disable debug % output while doing so, so we don't clutter the Matlab % window: olddebug = moglFDF('DebugFlag', -2); fdf = moglFDF('ReinitContext', fdf, rect, texCoordMin, texCoordMax, texResolution, maxFGDots, maxBGDots, dotLifetime, zThreshold, BGSilhouetteAcceptanceProbability); moglFDF('DebugFlag', olddebug); % Hotstart the context, as if 'h' key is pressed: resetDistribution = 2; end if keyCode(dKey) KbReleaseWait; debug = mod(debug+1, 3); switch debug case 0, moglFDF('DebugFlag', 0); case 1, moglFDF('DebugFlag', 1); case 2, moglFDF('DebugFlag', -1); end end end % Show new image at next retrace: Screen('Flip', win); % Update framecounter: fcount = fcount + 1; % Ready for next draw loop iteration... end; % End of animation loop: Take end-timestamp: tend = Screen('Flip', win); % Compute and show average framerate: avgfps = fcount / (tend - tstart) %#ok avgdur = 1000/avgfps %#ok % Enable OpenGL context for cleanup work: Screen('BeginOpenGL', win); % Delete our sphere object: gluDeleteQuadric(mysphere); % Unselect our texture... glBindTexture(gltextarget, 0); % ... and disable texture mapping: glDisable(gltextarget); % End of OpenGL rendering... Screen('EndOpenGL', win); % Destroy FDF context, release all ressources: moglFDF('DestroyContext', fdf); % Close onscreen window and release all other ressources: sca; catch % In case of error, a Screen('CloseAll') will also do a good % post-mortem cleanup job: sca; psychrethrow(psychlasterror); end % Well done! return