Write a GLSL TOP
Overviewedit
The official GLSL documentation can be found at this address.
TouchDesigner's main supported version of GLSL is 4.60. Support for versions of GLSL 3.30 and earlier have been removed due to the switch to Vulkan. A shader written for 3.30 should work fine when targeting newer GLSL though.
A shader written for the GLSL TOP is generally a image based operation. It does essentially no geometry based work. For users who are familiar with writing 3D GLSL shaders, a GLSL TOP is simply a shader applied to a single quad that is drawn to cover up the entire viewport (also known as a full-screen-aligned quad). To simplify the subject, this guide will avoid the extra complexities involved in 3D rendering, and present the topic of writing a GLSL shader in a 2D world only, dealing only with pixels.
The shader code in a GLSL TOP is run once for every pixel that is getting output. It's the job of the shader writer to:
- Sample the pixel(s) of the inputs, if any.
- Do whatever math is needed to create the pixel color.
- Output the pixel color.
Conceptsedit
Output Swizzleedit
To ensure cross-platform support between Windows and macOS, any color written to a texture should be first passed through vec4 TDOutputSwizzle(vec4). This function will ensure the correct channels go in the correct output channels depending on the destination texture format. For example alpha-only textures do not actually exist, and are stored as red-only textures. When they are sampled a swizzle is automatically applied to they output (0, 0, 0, R). vec4 TDOutputSwizzle() will place the alpha value into the output red channel in that case. When this texture is used else where the value will correctly appear in the 'alpha' channel, using the previously mentioned swizzle.
Outputting Coloredit
Pixel Shaderedit
Usually you will only need a pixel shader to create a functioning GLSL TOP.
A simple shader to start with is one that just sets every pixel to red.
layout(location = 0) out vec4 fragColor;
void main()
{
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
fragColor = TDOutputSwizzle(color);
}
Simply place this code into a DAT and set the GLSL TOPs Pixel Shader parameter to this DAT.
Notice that fragColor is defined by the shader writer as the location where the color is output. This is different from GLSL 1.20 where you used the built in variable gl_FragColor.
Compute Shaderedit
For compute shaders the output textures will be defined for you, do not define it in your shader code.
You should write and read from the textures using these functions. The uvec3 versions are there for convivence, but do the same as the ivec3 versions. If the dimension of the output texture doesn't require 3 coordinates, then the extra coordinate is ignored. This is different than the workflow before 2025.30000 series of builds, which used sTDComputeOutputs. This has changed because writing and reading from sRGB encoded textures requires special handling with imageStore()/imageLoad(), so this is handled automatically for you via these functions.
You also do ***not*** need to apply TDOutputSwizzle() to the color before using these functions, it will apply it automatically for you internally.
These functions also do range-checking for you, to ensure coordinates that are out of range are sent into imageStore/imageLoad.
Although GLSL states these functions are safe for out-of-bounds access, Vulkan does not, so they need to be range-checked for safety.
void TDImageStoreOutput(uint index, ivec3 coord, vec4 color);
void TDImageStoreOutput(uint index, uvec3 coord, vec4 color);
vec4 TDImageLoadOutput(uint index, ivec3 coord);
vec4 TDImageLoadOutput(uint index, uvec3 coord);
To write to the output, use the GLSL function TDImageStoreOutput()
void main()
{
vec4 color = vec4(1, 0, 0, 1);
TDImageStoreOutput(0, ivec3(gl_GlobalInvocationID.xy, 0), color);
}
Sampling Inputsedit
The next thing you will likely want to do is sample the pixels of the input TOP(s). The following line will sample an input TOP using the texture() function:
vec4 inputColor = texture(sTD2DInputs[0], vUV.st);
Pixel Shadersedit
By default in a pixel shader the input variable vUV is declared/set for you and will contain the texture coordinate of the pixel. This variable is only given if you don't supply a vertex shader. If you supply your own vertex shader than it is up to you to pass the texture coordinate through to the pixel shader. The values will smoothly interpolate across the entire 2D image. so when your drawing the middle pixel the value of vUV.st will be (0.5, 0.5). Input sampler variables are declared for you as arrays. Samplers are split based on their dimensions (2D, 3D, 2DArray, Cube). The sampler that refers to the TOP containing the first 2D texture sTD2DInputs[0]. Similarly, the 2nd 2D input would be called sTD2DInputs[1] and so on for any number of 2D inputs (the GLSL Multi TOP has unlimited inputs, however your video card has a limited number of textures that can be used in a shader).
The line texture(sTD2DInputs[0], vUV.st), samples the texture sTD2DInputs[0], at texture coordinate vUV.st. Since vUV.st changes for every pixel, we'll be sampling a different pixel from the input each time.
To visualize the values for vUV.st, try putting this shader into the GLSL TOP.
layout(location = 0) out vec4 fragColor;
void main()
{
fragColor = vec4(vUV.s, vUV.t, 0.0, 1.0);
}
Compute Shadersedit
Compute shaders can sample inputs using the same texture() functions just like pixel shaders. However there is no vUV coordinate available, so coordinates will need to be manually calculated using the gl_GlobalInvocationID and the input texture resolution, available in it's TDTexInfo structure. Alternatively texelFetch can be used which access integer texture coordinates ranging from [0, width - 1] and [0, height - 1].
Samplersedit
Samplers are GLSL's name for a texture. Samplers are given to your GLSL program as arrays, split based on the texture's dimensionality (2D, 3D, 2DArray, Cube etc.). You can find out how many of each type are connected to the TOP using these constants:
TD_NUM_2D_INPUTS
TD_NUM_3D_INPUTS
TD_NUM_2D_ARRAY_INPUTS
TD_NUM_CUBE_INPUTS
If you change the number/type of inputs connected to your GLSL TOP, then the shader will recompile with new values for the above defines and below arrays. Regardless of which input a TOP is connected to, it will be collapse into an array of samplers based on it's dimensionality. The arrays are defined as follows (you don't need to declare these in your shader):
uniform sampler2D sTD2DInputs[TD_NUM_2D_INPUTS];
uniform sampler3D sTD3DInputs[TD_NUM_3D_INPUTS];
uniform sampler2DArray sTD2DArrayInputs[TD_NUM_2D_ARRAY_INPUTS];
uniform samplerCube sTDCubeInputs[TD_NUM_CUBE_INPUTS];
So for example say you have 5 inputs connected to your GLSL TOP, in this order: a 2D TOP, a 3D TOP, a 2D TOP, a Cube TOP, a 2D Array TOP. Then
TD_NUM_2D_INPUTS = 2
TD_NUM_3D_INPUTS = 1
TD_NUM_2D_ARRAY_INPUTS = 1
TD_NUM_CUBE_INPUTS = 1
And you can reference your inputs like this:
texture(sTD2DInputs[0], vUV.st); // first 2D input
texture(sTD2DInputs[1], vUV.st); // second 2D input, NOT the second input connected to the TOP though
texture(sTD3DInputs[0], vUV.stp); // first 3D input
texture(sTDCubeInputs[0], vUV.stp); // first cube input
texture(sTD2DArrayInputs[0], vUV.stp); // first 2D array input
Non-Dynamically Uniform Sampler Accessedit
By default accessing an array of samplers (or image outputs) must be done with what is known as a dynamically uniform expression. This essentially means the array index should be a compile time constant, something the compiler can reduce down to a constant at compile time, or a few other shader inputs, outlined in the previous linked article. A for-loop always going from 0 to 5 for example is something that the compiler knows at compile time, and is thus dynamically uniform. Similarly, a uniform integer value passed into the shader is also dynamically uniform. For GLSL MATs, your Instance ID (TDInstanceID()) is not dynamically uniform.
If you want to look up into your inputs using something that is decided during shader execution, such as deciding on a different input based on the current UV value, you need to wrap your index in nonuniformEXT() to tell the compiler the index is non-uniform.
E.g
int inputIndex;
if (vUV.s > 0.5)
inputIndex = 0;
else
inputIndex = 1;
vec4 col = sTD2DInputs[nonuniformEXT(inputIndex)], vUV);
Built In Samplersedit
For convenience the following samplers are provided for you to use as needed:
uniform sampler2D sTDNoiseMap; // A 256x256 8-bit Red-only channel texture that has random data.
uniform sampler1D sTDSineLookup; // A Red-only texture that goes from 0 to 1 in a sine shape.
POP Attributesedit
attribType TDBuffer_AttribName(uint elementIndex, uint arrayIndex); //access to POP attribute buffer declared on Buffer Page
TDBuffer_AttribName(uint elementIndex) = TDBuffer_AttribName(elementIndex, 0); //arrayIndex is optional, defaults to 0
const uint TDBufferLength_AttribName(); //length of buffer
const uint cTDBufferArraySize_AttribName; //constant with size of array for array attributes
Uniformsedit
A uniform is a value that stays the same for every pixel that is drawn. They are set using the Vectors 1 and Vectors 2 pages in the GLSL TOP. To use a uniform inside your shader, declare a uniform of the same name and size as the parameters you have set on the Vectors pages of the GLSL TOP. For example, lets say we want to make a shader that will create an image that is one solid color, but you don't want it hard coded into the shader (as we did in the first example).
layout(location = 0) out vec4 fragColor;
uniform vec4 uColor;
void main()
{
fragColor = uColor;
}
You can now set the Value parameters on the GLSL TOP for the Uniform Name uColor however you see fit (ie. export to them, use expressions, or set them by hand). The GLSL TOP will automatically update and create a new image to match the changing values.
Built in Uniformsedit
The GLSL TOP has built-in uniforms that may come in useful depending on the shader you are writing. You do not need to declare this uniforms, they are declared for you.
There are many arrays of this structure that gives information about input/output textures such as their resolution. The structure is defined as:
struct TDTexInfo
{
// contains (1.0 / width, 1.0 / height, width, height)
vec4 res;
// contains (1.0 / depth, depth, depthOffset, undefined)
// depthOffset is between [0,1] for 3D textures and between [0, depth - 1] for 2D texture Arrays.
vec4 depth;
};
For each of the input sampler arrays (2D, 3D, 2DArray etc.), there is a parallel array of the above structure containing the information about each sampler. For the output info of the texture, there is uTDOutputINfo. You can get the resolution the TOP is going to output at using this uniform. No need to try to pass it in manually via a custom uniform. The depthOffset value will always be 0 though.
uniform TDTexInfo uTD2DInfos[TD_NUM_2D_INPUTS];
uniform TDTexInfo uTD3DInfos[TD_NUM_3D_INPUTS];
uniform TDTexInfo uTD2DArrayInfos[TD_NUM_2D_ARRAY_INPUTS];
uniform TDTexInfo uTDCubeInfos[TD_NUM_CUBE_INPUTS];
// Information about the output of the TOP such as it's resolution.
uniform TDTexInfo uTDOutputInfo;
So for example to get the width of the first 2D input, you could type:
float width = uTD2DInfos[0].res.z;
When the input is a texture that has depth (3D or 2D Array), then the depth variable will contain the depth, and the depthOffset. The depthOffset is the offset from the texture coordinate at the front of the texture to the texture coordinate of the slice of the input that was most recently updated. So if you wanted a TOP that always output the newest slice of a 3D texture use this shader
layout(location = 0) out vec4 fragColor;
void main()
{
// The center of the first slice is not located at 0, but rather halfway between 0 (the start of the first slice)
// and 1.0 / depth (the end of the first slice)
float firstSlice = uTD3DInfos[0].depth.x * 0.5;
// now add the offset
firstSlice += uTD3DInfos[0].depth.z;
// now sample the texture
fragColor = texture(sTD3DInputs[0], vec3(vUV.st, firstSlice));
}
For 3D textures the depthOffset is always between 0 and 1. For 2D Arrays the offset is between 0 and (depth - 1), and will always be an integer.
When outputting a 3D or 2D Array texture, this uniform holds the slice index that you are currently rendering to.
// Refer to [[#3D Textures and 2D Texture Arrays |3D Textures and 2D Texture Arrays]]
uniform int uTDCurrentDepth;
When using the "Num Passes" parameter on the Common page of the GLSL TOP, it is often useful to know which pass you are currently rendering in the shader. You can do this by looking at the uniform
// The current render pass in the GLSL TOP, starts at 0 and counts up.
uniform int uTDPass;
Atomic Countersedit
Atomic counters are global unsigned integers that can have atomic operations performed on them, namely increment and decrement. They can be used in the shaders at any stage of the pipeline (vertex, fragment/pixel, compute) and can be used to track all sorts of things such as number of vertices, number of red pixels, and more. All shader executions will be using the same variable, which allows them to share information between each other.
Below is a simple example of a pixel shader where the atomic counter is incremented each time (ie. once per pixel), converted to a float, and then put into the red channel. Visually, pixels that are rendered first will be a darker red than those rendered last.
uniform atomic_uint ac;
out vec4 fragColor;
void main()
{
uint c = atomicCounterIncrement(ac);
float r = (c/255)/255.f;
fragColor = vec4(r,0,0,1);
}
They can also be declared and initialized as arrays by declaring them as:
uniform atomic_uint ac[10];
Note that although many online examples will prefix the declaration with a binding location such as layout (binding = 0), it is more efficient to omit that and let our compiler assign the binding automatically.
Specialization Constantsedit
Specialization Constants are a new feature in Vulkan that allow code to be re-optimized based on integer constant values, without doing a full recompilation of the shader code. These are useful to set the value for rarely changing values such as 'modes' in shader, or selection of particular code paths that are doing via a switch() or if() statement. In the past this may have been done with a #define statement, or a uniform.
A specialized version of a shader will be cached and re-used, but takes up GPU resources. So they should not be used for constantly changing values, but instead for values that are only changed sometimes, within a limited range of values.
To define a specialization constant, declare a constant with an extra layout() qualifier.
layout(constant_id = 0) const int SomeMode = 0;
Then you can use SomeMode just as you would any other variable. If you don't want it to be = 0, you can assign a different value on the 'Constants' page of the GLSL TOP, GLSL MAT etc.
You can declare multiple specialization constants, you just need to give each one it's own unique constant_id value (0, 1, 2, etc.).
Built-in Functionsedit
These are TouchDesigner specific functions which are made available for use within the shader.
Output Swizzleedit
// Any color value being written to a texture through <code>out</code>
// variable should be passed through this function to ensure the color channels go to the correct outputs color channels.
// <code>TDImageStoreOutput</code> automatically handles this for you.
vec4 TDOutputSwizzle(vec4 c);
Ditheringedit
// Applies a small random noise to the color to help avoid banding
// in some cases.
vec4 TDDither(vec4 color);
Common Functionsedit
Math Functionsedit
// TDMath
// Creates a rotation matrix that rotates starting from looking down +Z, to the 'forward' vector direction.
// The 'forward' and 'up' vectors passed to this function do not need to be normalized.
mat3 TDRotateToVector(vec3 forward, vec3 up);
// Creates a rotation matrix that rotates around the 'axis', the given number of 'radians'
// The 'axis' vector must already be normalized before being passed to this function.
mat3 TDRotateOnAxis(float radians, vec3 axis);
// Creates a rotation matrix that rotates around the +X, +Y and +Z axis repectively.
mat3 TDRotateX(float radians);
mat3 TDRotateY(float radians);
mat3 TDRotateZ(float radians);
// Creates a scale matrix for the given 3 scale values.
mat3 TDScale(float x, float y, float z);
// Creates a translation matrix for the given 3 translation values.
mat4 TDTranslate(float x, float y, float z);
// A function that gives a half-sine ramp from 0 to 1.
// Sampling it with a coordinate outside the (0, 1) range will return 0 for anything below 0 and 1 for anything above 1.
// It's possibly faster than using the GLSL sin() function, depending on the hardware.
float TDSineLookup(float v);
// Creates a rotation matrix to rotate from vector 'from' to vector 'to'. The solution isn't particularly stable, but useful in some cases.
// The 'from' and 'to' vectors must already be normalized before being passed to this function.
mat3 TDCreateRotMatrix(vec3 from, vec3 to);
// Takes a surface normal, the tangent to the surface, and a handedness value (either -1 or 1)
// Returns a matrix that will convert vectors from tangent space, to the space the normal and tangent are in
// Both the normal and the tangent must be normalized before this function is called.
// The w coordinate of the T attribute created by the [[Attribute Create SOP]] contains the handedness
// that should be passed in as-is.
mat3 TDCreateTBNMatrix(vec3 N, vec3 T, float handedness);
// Converts between RGB and HSV color space
vec3 TDRGBToHSV(vec3 c);
vec3 TDHSVToRGB(vec3 c);
// Converts a 0-1 equirectangular texture coordinate into cubemap coordinates.
// A 0 for the U coordinate corresponds to the middle of the +X face. So along the vec3(1, Y, 0) plane.
// As U rises, equirectangular coordinates rotate from +X, to +Z, then -X and -Z.vec3 TDEquirectangularToCubeMap(vec2 mapCoord);
vec3 TDEquirectangularToCubeMap(vec2 mapCoord);
// Converts from cubemap coordinates to equirectangular
// cubemapCoord MUST be normalized before calling this function.
vec2 TDCubeMapToEquirectangular(vec3 envMapCoord)
// Available in builds 2019.18140
// This version will also output a mipmap bias float. This float should be passed in the 'bias'
// parameter of texture(), to help select the mipmap level. This helps avoids seams at the edge
// of equirectangular map.
vec2 TDCubeMapToEquirectangular(vec3 envMapCoord, out float mipMapBias)
// Converts from RGB to monochrome using the standard weights of luminance (0.2126, 0.7152, 0.0722)
float TDLuminance(vec3 c);
// Returns a looping value between low and high
float TDLoop(float val, float low, float high);
// Returns a value that zigzags between low and high
float TDZigZag(float val, float low, float high);
// Returns the average between the two values.
float TDAverage(float valA, float valB);
// Like the remap feature in the Math CHOP, will remap 'value' frmo thew range [low1, high1] to the range [low2, high2].
float TDRemap(float value, float low1, float high1, float low2, float high2);
vec2 TDRemap(vec2 value, vec2 low1, vec2 high1, vec2 low2, vec2 high2);
vec3 TDRemap(vec3 value, vec3 low1, vec3 high1, vec3 low2, vec3 high2);
vec4 TDRemap(vec4 value, vec4 low1, vec4 high1, vec4 low2, vec4 high2);
// Triplanar blend function. Blends between the 3 orthogonal color values weighted by the normal vector. <code>blendPower</code> argument is the exponent taken by the weight function to control the falloff.
vec4 TDTriplanarBlend(vec3 normal, vec4 xcolor, vec4 ycolor, vec4 zcolor, int blendPower);
// Performs Tricubic and bicubic sampling/interlolation on the given sampler.
// texSize and invTexSize should be the resolution and 1.0 / resolution of the texture.
vec4 TDBicubicInterpolation(sampler2D tex, vec2 texCoord, vec2 texSize, vec2 invTexSize);
vec4 TDBicubicInterpolation(sampler2D tex, vec2 texCoord);
vec4 TDTricubicInterpolation(sampler3D tex, vec3 texCoord, vec3 texSize, vec3 invTexSize);
vec4 TDTricubicInterpolation(sampler3D tex, vec3 texCoord);
// Generate 2D coordinates to look up into a sphere-map from 3D environment coordinates.
vec2 TDTexGenSphere(vec3 envMapCoord);
// TDQuaternionMath
// Builds a quaternion representing a rotation around an axis, using an angle in radians.
vec4 TDAxisAngleToQuaternion(vec3 axis, float angle) ;
// Builds a 3×3 rotation matrix from a quaternion.
mat3 TDQuaternionToRotMatrix(vec4 q);
// Extracts a quaternion representing the rotation of a 3×3 matrix.
vec4 TDRotMatrixToQuaternion(mat3 rotMat);
// Rotates a vector or a point using a quaternion rotation.
vec3 TDRotateFromQuaternion(vec3 v, vec4 q);
// Combines two quaternions to apply their rotations sequentially.
vec4 TDQuaternionMultiply(vec4 Q1, vec4 Q2);
// Performs spherical linear interpolation between two quaternions.
vec4 TDSlerpQuaternions(vec4 q0, vec4 q1, float t);
// Extracts the pure rotation component from a 4×4 transform matrix.
mat3 TDExtractRotation(mat4 m);
// Interpolates smoothly between two rotation matrices via quaternion slerp.
mat3 TDSlerpRotationMatrices(mat3 m0, mat3 m1, float t);
// Interpolates translation, rotation, and scale between two transform matrices.
mat4 TDInterpolateTransformMatrices(mat4 m0, mat4 m1, float t);
// Computes the quaternion that rotates one vector direction into another.
vec4 TDQuaternionFromTo(vec3 from, vec3 to);
Noise Functionsedit
// Returns <code>t*t*t*(t*(t*6.0-15.0)+10.0);</code>.
float TDFade(float t);
// Perlin 2D,3D,4D noise functions
float TDPerlinNoise(vec2 P);
float TDPerlinNoise(vec3 P);
float TDPerlinNoise(vec4 P);
// Simplex 2D,3D,4D noise functions
float TDSimplexNoise(vec2 P);
float TDSimplexNoise(vec3 P);
float TDSimplexNoise(vec4 P);
// Derivative of the TDFade() function. Returns <code>30*t*t*(t*(t-2)+1)</code>
float TDFadeDeriv(float t);
// Gradients of the TDPerlinNoise() functions. The gradient is an analytical derivative, calculated in the noise space.
vec4 TDPerlinNoiseDeriv(vec2 P);
vec4 TDPerlinNoiseDeriv(vec3 P);
vec4 TDPerlinNoiseDeriv(vec4 P, out float outNoise);
vec4 TDPerlinNoiseDeriv(vec4 P);
// Gradients of the TDSimplexNoise() functions. The gradient is an analytical derivative, calculated in the noise space.
vec4 TDSimplexNoiseDeriv(vec2 P);
vec4 TDSimplexNoiseDeriv(vec3 P);
vec4 TDSimplexNoiseDeriv(vec4 P, out float outNoise);
vec4 TDSimplexNoiseDeriv(vec4 P);
Color Space Functionsedit
The TDTransfer* functions will convert between a linear and non-linear transfer for the color data. If a vec4 is provided then the RGB will be un-premultipled by Alpha before applying the operation, and then then mulitpled by alpha again at the end.
// TDBasicColorSpace
vec3 TDTransferLinearToSRGB(vec3 c);
vec4 TDTransferLinearToSRGB(vec4 c);
vec3 TDTransferSRGBToLinear(vec3 c);
vec4 TDTransferSRGBToLinear(vec4 c);
// TDColorSpace
vec3 TDTransferLinearToRec709(vec3 c);
vec3 TDTransferRec709ToLinear(vec3 c);
vec3 TDTransferLinearToGamma1_8(vec3 c);
vec3 TDTransferLinearToGamma2_2(vec3 c);
vec3 TDTransferLinearToGamma2_4(vec3 c);
vec3 TDTransferLinearToGamma2_6(vec3 c);
vec3 TDTransferLinearToGamma2_8(vec3 c);
vec3 TDTransferACESproxyToLinear(vec3 c);
vec3 TDTransferLinearToACESproxy(vec3 c);
vec3 TDTransferGamma1_8ToLinear(vec3 c);
vec3 TDTransferGamma2_2ToLinear(vec3 c);
vec3 TDTransferGamma2_4ToLinear(vec3 c);
vec3 TDTransferGamma2_6ToLinear(vec3 c);
vec3 TDTransferGamma2_8ToLinear(vec3 c);
// referenceWhiteNits denotes what brightness in nits a value of 1.0 will be for the linear data.
vec3 TDTransferLinearToST2084PQ(vec3 color, float referenceWhiteNits);
vec3 TDTransferST2084PQToLinear(vec3 color, float referenceWhiteNits);
vec3 TDTransferBT2100HLGToLinear(vec3 color, float peakNits, float referenceWhiteNits);
vec3 TDTransferLinearToBT2100HLG(vec3 color, float peakNits, float referenceWhiteNits);
vec3 TDGamutRec709ToRec2020(vec3 c);
vec3 TDGamutRec2020ToRec709(vec3 c);
Sampling more than one pixeledit
It some shaders you may want to sample more than one pixel from the input TOP (when creating a Blur shader for example). This is done simply with multiple calls to texture(), while offsetting the values of vUV (or your own texture coordinate).
In texture coordinate terms, the value difference between one pixel and the pixel directly to the right of it is (1.0 / width). Similarly, the value difference between one pixel and the pixel directly below it is -(1.0 / height). The following function is helpful in calculating the correct texture coordinates for neighboring pixels:
// This function is not provided for you, you need to declare it yourself.
vec2 input2DOffset(int texIndex, int xOffset, int yOffset)
{
return vec2(vUV.s + (float(xOffset) * uTD2DInfos[texIndex].res.s),
vUV.t + (float(yOffset) * uTD2DInfos[texIndex].res.t));
}
There is however a new function is GLSL, textureOffset() which does this work for you. It has a limited range it can sample from the starting coordinate though, so it can't be used to get an arbitrary sample offset from a coordinate.
Here is a very simple blur shader that will sample a 3x3 grid around each pixel and output the average value of all 9 pixels. It's manually calculating the offsets instead of using textureOffset, although textureOffset would work fine in this example since the offsets are only 1 pixel.
vec2 input2DOffset(int texIndex, int xOffset, int yOffset)
{
return vec2(vUV.s + (float(xOffset) * uTD2DInfos[texIndex].res.s),
vUV.t + (float(yOffset) * uTD2DInfos[texIndex].res.t));
}
layout(location = 0) out vec4 fragColor;
void main()
{
vec4 colorSum = vec4(0.0);
colorSum += texture(sTD2DInputs[0], input2DOffset(0, 0, 0));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, -1, -1));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, 0, -1));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, 1, -1));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, 1, 0));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, 1, 1));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, 0, 1));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, -1, 1));
colorSum += texture(sTD2DInputs[0], input2DOffset(0, -1, 0));<br>
fragColor = colorSum / 9.0;
}
3D Textures and 2D Texture Arraysedit
Pixel Shadersedit
When creating a 3D Texture or a 2D Texture Array, your shader will be rendered once for every depth slice that is created. It's like rendering a bunch of 2D textures.
Along with the different input samplers you'll get, you also have access to a few uniforms to help you decide what to create for each slice.
uniform int uTDCurrentDepth; // Is the 0-based index of the slice that's currently being created.
Compute Shadersedit
When creating a 3D Texture or a 2D Texture Array with a compute shader, the shader is still only ran once. The entire output texture is available to be written to using TDImageStoreOutput, and should be filled as desired, possibly with a Z dispatch size equal to the depth of the texture.
Outputting to Multiple Color Buffersedit
In the same pixel shader you can output to multiple identical size/format buffers at the same time. To do this first turn up the "# of Color Buffers" parameter in the GLSL TOP to the number of outputs you need.
The output connector on the GLSL TOP will always output the color for the first color buffer. To get the other color buffers use a Render Select TOP and point it to the GLSL TOP, then select your color buffer index you want.
Pixel Shadersedit
In your shader declare your other other output locations. For example if your plan to output to 3 different buffers you could declare them like this:
layout(location = 0) out vec4 fragColor;
layout(location = 1) out vec4 otherColor;
layout(location = 2) out vec4 extraInfo;
Now you can write to fragColor, otherColor and extraInfo to write to the 3 color buffers that your are outputting to. If you don't write to all of your outputs in all cases, the resulting pixel value is undefined. Don't avoid writing a value to try to keep last frame's value in the buffer.
Compute Shadersedit
The sTDComputeOutputs[] uniform will be sized equal to the number of color buffers being output.
Vertex Shaderedit
In most cases you will not need to provide a vertex shader to the GLSL TOP. If you decide to provide a vertex shader, it's most basic form would be:
out vec3 texCoord;
void main()
{
texCoord = uv[0];
gl_Position = TDSOPToProj(vec4(P, 1.0));
}
It is very important that you do not manipulate the vertex position, as it will cause the quad to not be aligned with the TOP output. Also, notice how we declare our own output variable for the texture coordinate here. vUV will not be automatically available to us in the pixel shader if we supply a vertex shader, so we use this variable instead;
layout (location = 0) out vec4 fragColor;
in vec3 texCoord;
void main()
{
fragColor = texture(sTD2DInputs[0], texCoord.st);
}
Debugging Crashesedit
With the changeover to Vulkan, it's much easier for incorrectly written GLSL to cause a full application crash. For more information about this, refer to this article.
Other Notesedit
#version statementedit
TouchDesigner will automatically put a #version statement at the start of the shaders when compiling them, so you should make sure your shaders don't have a #version statement. You will get an error if they do.