1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
function [gratingid, gratingrect] = CreateProceduralSmoothedApertureSineGrating(windowPtr, width, height, backgroundColorOffset, radius, contrastPreMultiplicator, sigma, useAlpha, method)
% [gratingid, gratingrect] = CreateProceduralSmoothedApertureSineGrating(windowPtr, width, height [, backgroundColorOffset =(0,0,0,0)] [, radius=inf] [, contrastPreMultiplicator=1] [, sigma=0] [, useAlpha=0] [, method=0])
%
% Creates a procedural texture that allows to draw sine grating stimulus patches
% with a smoothed aperture in a very fast and efficient manner on modern
% graphics hardware.
%
% 'windowPtr' A handle to the onscreen window.
% 'width' x 'height' The maximum size (in pixels) of the grating. More
% precise, the size of the mathematical support of the grating. Providing too
% small values here would 'cut off' peripheral parts or your grating. Too big
% values don't hurt wrt. correctness or accuracy, they just hurt
% performance, ie. drawing speed. Use a reasonable size for your purpose.
%
% 'backgroundColorOffset' Optional, defaults to [0 0 0 0]. A RGBA offset
% color to add to the final RGBA colors of the drawn grating, prior to
% drawing it.
%
% 'radius' Optional parameter. If specified, a circular aperture of
% 'radius' pixels is applied to the grating. By default, no aperture is
% applied.
%
% 'contrastPreMultiplicator' Optional, defaults to 1. This value is
% multiplied as a scaling factor to the requested contrast value. If you
% specify contrastPreMultiplicator = 0.5 then the per grating 'contrast'
% value will correspond to what practitioners of the field usually
% understand to be the contrast value of a grating.
%
% 'sigma' Optional. Edge smoothing value in pixels. Defaults to 0.
%
% 'useAlpha' Optional, defaults to 0. Whether to use color (0) or alpha (1)
% for smoothing channel. Defaults to 0 (color).
%
% 'method' Optional. Whether to use cosine (0), smoothstep(1) or
% inverse smoothstep (2) smoothing function. Defaults to 0 (cosine).
%
% The function returns a procedural texture handle 'gratingid' that you can
% pass to the Screen('DrawTexture(s)', windowPtr, gratingid, ...) functions
% like any other texture handle. The 'gratingrect' is a rectangle which
% describes the size of the support.
%
% A typical invocation to draw a grating patch looks like this:
%
% Screen('DrawTexture', windowPtr, gratingid, [], dstRect, Angle, [], [],
% modulateColor, [], [], [phase+180, freq, contrast, 0]);
%
% Draws the grating 'gratingid' into window 'windowPtr', at position 'dstRect'
% or in the center if dstRect is set to []. Make sure 'dstRect' has the
% size of 'gratingrect' to avoid spatial distortions! You could do, e.g.,
% dstRect = OffsetRect(gratingrect, xc, yc) to place the grating centered at
% screen position (xc,yc). 'Angle' is the optional orientation angle,
% default is zero degrees. 'modulateColor' is the base color of the grating
% patch - it defaults to white, ie. the grating has only luminance, but no
% color. If you'd set it to [255 0 0] you'd get a reddish grating. 'phase' is
% the phase of the grating in degrees, 'freq' is its spatial frequency in
% cycles per pixel, 'contrast' is the contrast of your grating.
%
% For a zero degrees grating without aperture (radius = inf):
%
% g(x,y) = modulatecolor * contrast * contrastPreMultiplicator * sin(x*2*pi*freq + phase) + Offset
%
% NOTE: The modulation is also applied to the alpha channel, so if you use alpha
% blending, make sure to take this into account, e.g., by setting the alpha component
% of modulateColor - or the globalAlpha parameter - to zero, if you don't want the
% alpha channel modulated by a sine wave as well! Then you can use the alpha component
% of backgroundColorOffset to write a constant alpha value. Or you can use the
% Screen('Blendfunction', ...) to set the color write mask to prevent alpha channel
% updates.
%
% For a zero degrees grating with aperture (radius = some non-infinite value) iff
% 'useAlpha' is set to zero or false:
%
% g(x,y) = modulatecolor * contrast * contrastPreMultiplicator * sin(x*2*pi*freq + phase) * weight(dist(x,y), radius, sigma) + Offset
%
% weight(dist(x,y), radius, sigma) is a weighting function which is 1.0 for a dist(x,y)
% position on a circle around the center within 'radius' - 'sigma', then
% falls off to 0.0 for positions inside a circle between 'radius' - 'sigma' and
% 'radius', in other words, the sine grating "fades out" to zero contrast at the
% border inside 'radius' over a range of 'sigma' pixels. The specific weight()ing
% function can be selected via the 'method' parameter: It is either a cosine falloff,
% or a linear falloff.
%
% Again, alpha channel is also affected the same way.
%
% For a zero degrees grating with aperture (radius = some non-infinite value) iff
% 'useAlpha' is set to 1 or true:
%
% g(x,y).rgb = modulatecolor.rgb * contrast * contrastPreMultiplicator * sin(x*2*pi*freq + phase) + Offset.rgb
%
% As you can see, the color channels rgb are calculated without an aperture applied,
% and the aperture weight function is instead applied to the alpha channel only:
%
% g(x,y).a = modulateColor.a * weight(dist(x,y), radius, sigma) + Offset.a
%
% This makes only sense if you enable alpha blending to apply the falloff!
%
%
% Make sure to use the Screen('DrawTextures', ...); function properly to
% draw many different gratings simultaneously - this is much faster!
%
% History:
% 06-Jun-2011 Modified from PTB function (Ian Andolina).
% 08-Nov-2018 Update help text to explain the smoothing functions and alpha
% application better. (MK)
debuglevel = 1;
% Global GL struct: Will be initialized in the LoadGLSLProgramFromFiles
% below:
global GL;
if nargin < 3 || isempty(windowPtr) || isempty(width) || isempty(height)
error('You must provide "windowPtr", "width" and "height"!');
end
if nargin < 4 || isempty(backgroundColorOffset)
backgroundColorOffset = [0 0 0 0];
else
if length(backgroundColorOffset) < 4
error('The "backgroundColorOffset" must be a 4 component RGBA vector [red green blue alpha]!');
end
end
if nargin < 5 || isempty(radius)
% Don't apply circular aperture if no radius given:
radius = inf;
end
if nargin < 6 || isempty(contrastPreMultiplicator)
contrastPreMultiplicator = 1.0;
end
if nargin < 7 || isempty(sigma)
sigma = 0.0;
end
if nargin < 8 || isempty(useAlpha)
useAlpha = 0.0;
end
if nargin < 9 || isempty(method)
method = 0.0;
end
% Switch to windowPtr OpenGL context:
Screen('GetWindowInfo', windowPtr);
% Make sure we have support for shaders, abort otherwise:
AssertGLSL;
if isinf(radius)
% Load standard grating shader:
gratingShader = LoadGLSLProgramFromFiles('BasicSineGratingShader', debuglevel);
else
% Load grating shader with circular aperture and smoothing support:
gratingShader = LoadGLSLProgramFromFiles('SineGratingSmoothedApertureShader', debuglevel);
end
% Setup shader:
glUseProgram(gratingShader);
% Set the 'Center' parameter to the center position of the gabor image
% patch [tw/2, th/2]:
glUniform2f(glGetUniformLocation(gratingShader, 'Center'), width/2, height/2);
glUniform4f(glGetUniformLocation(gratingShader, 'Offset'), backgroundColorOffset(1),backgroundColorOffset(2),backgroundColorOffset(3),backgroundColorOffset(4));
if ~isinf(radius)
% Set radius of circular aperture:
glUniform1f(glGetUniformLocation(gratingShader, 'Radius'), radius);
% Apply sigma:
glUniform1f(glGetUniformLocation(gratingShader, 'Sigma'), sigma);
% Apply useAlpha:
glUniform1f(glGetUniformLocation(gratingShader, 'useAlpha'), useAlpha);
% Apply method:
glUniform1f(glGetUniformLocation(gratingShader, 'Method'), method);
end
% Apply contrast premultiplier:
glUniform1f(glGetUniformLocation(gratingShader, 'contrastPreMultiplicator'), contrastPreMultiplicator);
glUseProgram(0);
% Create a purely virtual procedural texture 'gaborid' of size width x height virtual pixels.
% Attach the GaborShader to it to define its appearance:
gratingid = Screen('SetOpenGLTexture', windowPtr, [], 0, GL.TEXTURE_RECTANGLE_EXT, width, height, 1, gratingShader);
% Query and return its bounding rectangle:
gratingrect = Screen('Rect', gratingid);
% Ready!
return;
|