Skip to content

Instantly share code, notes, and snippets.

@iandol
Created February 18, 2011 20:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iandol/834334 to your computer and use it in GitHub Desktop.
Save iandol/834334 to your computer and use it in GitHub Desktop.
Add a cosine (method = 0) or hermite (method = 1) smoothed edge to a circularly masked sine grating. sigma is the width of the smoothing in pixels to apply to the edge. You can set useAlpha = 0 to modulate only colour, or useAlpha = 1 to modulate alpha.
function [gratingid, gratingrect] = CreateProceduralSineSmoothedGrating(windowPtr, width, height, backgroundColorOffset, radius, contrastPreMultiplicator, sigma, useAlpha, method)
% [gratingid, gratingrect] = CreateProceduralSineGrating(windowPtr, width, height [, backgroundColorOffset =(0,0,0,0)] [, radius=inf][, contrastPreMultiplicator=1])
%
% Creates a procedural texture that allows to draw sine grating stimulus patches
% 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.
%
% 'useAlpha' whether to use colour (0) or alpha (1) for smoothing channel
%
% 'method' whether to use cosine (0) or smoothstep(1) functions
%
% 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:
% g(x,y) = modulatecolor * contrast * contrastPreMultiplicator * sin(x*2*pi*freq + phase) + Offset.
%
% Make sure to use the Screen('DrawTextures', ...); function properly to
% draw many different gratings simultaneously - this is much faster!
%
% History:
% 11/25/2007 Written. (MK)
% 08/09/2010 Add support for optional circular aperture. (MK)
% 09/03/2010 Add 'contrastPreMultiplicator' as suggested by Xiangrui Li (MK).
% Global GL struct: Will be initialized in the LoadGLSLProgramFromFiles
% below:
global GL;
% Make sure we have support for shaders, abort otherwise:
AssertGLSL;
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
p = mfilename('fullpath');
p = [fileparts(p) filesep];
if isinf(radius)
% Load standard grating shader:
gratingShader = LoadGLSLProgramFromFiles('BasicSineGratingShader', 1);
else
% Load grating shader with circular aperture and smoothing support:
gratingShader = LoadGLSLProgramFromFiles({[p 'smoothedaperture.vert.txt'], [p 'smoothedaperture.frag.txt']}, 1);
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;
/*
* File: smoothedaperture.frag.txt
* Shader for drawing of basic parameterized sine grating patches.
* Applies a circular aperture of radius 'Radius'.
*
* (c) 2010 by Mario Kleiner, licensed under GPL.
*
*/
const float halfpi = 0.5 * 3.141592654;
uniform float Radius;
uniform float Sigma;
uniform float useAlpha;
uniform float Method;
uniform vec2 Center;
uniform vec4 Offset;
float Dist;
float Mod = 1.0;
varying vec4 baseColor;
varying float Phase;
varying float FreqTwoPi;
void main()
{
/* Query current output texel position: */
vec2 pos = gl_TexCoord[0].xy;
/* find our distance from center */
Dist = distance(pos, Center);
/* If distance to center (aka radius of pixel) > Radius, discard this pixel: */
if (Dist > Radius) discard;
/* Calculate a smoothing modifier using our distance from radius and a Sigma */
if (Method < 1.0) {
Mod = ((Sigma - (Radius - Dist)) / Sigma);
Mod = clamp(Mod, 0.0, 1.0);
Mod = cos(Mod * halfpi);
}
else {
Mod = smoothstep(Radius,(Radius-Sigma),Dist);
}
/* Evaluate sine grating at requested position, frequency and phase: */
float sv = sin(pos.x * FreqTwoPi + Phase);
/* Multiply/Modulate base color and alpha with calculated sine */
/* values, add some constant color/alpha Offset, assign as final fragment */
/* output color: */
if (useAlpha < 1.0) {
gl_FragColor = (baseColor * (sv * Mod)) + Offset;
}
else {
gl_FragColor = (baseColor * sv) + Offset;
gl_FragColor.a = Mod;
}
}
/*
* File: smoothedaperture.vert.txt
* Shader for drawing of basic parameterized sine grating patches.
*
* This is the vertex shader. It takes the attributes (parameters)
* provided by the Screen('DrawTexture(s)') command, performs some
* basic calculations on it - the calculations that only need to be
* done once per grating patch and that can be reliably carried out
* at sufficient numeric precision in a vertex shader - then it passes
* results of computations and other attributes as 'varying' parameters
* to the fragment shader.
*
* (c) 2007 by Mario Kleiner, licensed under GPL.
*
*/
/* Constants that we need 2*pi: */
const float twopi = 2.0 * 3.141592654;
/* Conversion factor from degrees to radians: */
const float deg2rad = 3.141592654 / 180.0;
/* Constant from setup code: Premultiply to contrast value: */
uniform float contrastPreMultiplicator;
/* Attributes passed from Screen(): See the ProceduralShadingAPI.m file for infos: */
attribute vec4 modulateColor;
attribute vec4 auxParameters0;
/* Information passed to the fragment shader: Attributes and precalculated per patch constants: */
varying vec4 baseColor;
varying float Phase;
varying float FreqTwoPi;
void main()
{
/* Apply standard geometric transformations to patch: */
gl_Position = ftransform();
/* Don't pass real texture coordinates, but ones corrected for hardware offsets (-0.5,0.5) */
gl_TexCoord[0] = (gl_TextureMatrix[0] * gl_MultiTexCoord0) + vec4(-0.5, 0.5, 0.0, 0.0);
/* Contrast value is stored in auxParameters0[2]: */
float Contrast = auxParameters0[2];
/* Convert Phase from degrees to radians: */
Phase = deg2rad * auxParameters0[0];
/* Precalc a couple of per-patch constant parameters: */
FreqTwoPi = auxParameters0[1] * twopi;
/* Premultiply the wanted Contrast to the color: */
baseColor = modulateColor * Contrast * contrastPreMultiplicator;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment