Functions - Shader Basics - Shader Learning

Shader Basics

Functions

Task

The starter paints a colorful gradient. On the left half of the screen, main already calls the function grayscale (HLSL: float3 grayscale(float3 color)), but the function doesn't do anything yet: it returns the color it got, unchanged.


Implement grayscale so that it turns a color gray: compute the average of the red, green and blue components and return that average in all three components. Don't change main: the left half should turn gray, and the right half keeps its colors.

Hint 1

Whatever comes after return is what the call grayscale(color) in main gives back. Right now that's the same color it received. Only the body of the function needs to change.

Hint 2

Compute one number first: the average of three numbers is their sum divided by 3.0. Then build a color with that number in every component. "Filling a vector with one number" in the theory shows a shortcut (in HLSL, write the number three times).

Theory

A function is a named piece of code you can run again and again. You've already written one: main is a function that the GPU calls for every pixel.


Writing a function

float brightness(vec3 color) {
    return (color.r + color.g + color.b) / 3.0;
}
  • float - the return type: what kind of value the function gives back;
  • brightness - the name;
  • (vec3 color) - the parameters: the values the function needs, each with a type and a name. Several parameters are separated by commas: (vec2 uv, float size);
  • return - ends the function and gives the value back.

Calling a function


Write its name and pass the values in brackets, in the same order as the parameters:

vec3 c = vec3(0.9, 0.3, 0.6);
float b = brightness(c); // (0.9 + 0.3 + 0.6) / 3.0 = 0.6

void


A function that gives nothing back has the return type void. That's why it's void main() in GLSL: main doesn't return anything, it writes the color into gl_FragColor.


Order matters


The compiler reads the code from top to bottom, so a function must be written above the code that calls it. If you'd like to keep main at the top, put a declaration (just the first line with a ;) above it and the full function below:

float brightness(vec3 color); // declaration

void main() {
    // brightness can be used here
}

float brightness(vec3 color) {
    return (color.r + color.g + color.b) / 3.0;
}

That's what the code of the very first task in this module did.


Parameters are copies


A function gets copies of the values. Changing a parameter inside the function doesn't change the variable you passed in:

float twice(float x) {
    x = x * 2.0;  // changes only the copy
    return x;
}

Filling a vector with one number


A handy shortcut for the task: a vector built from a single number gets it in every component.

vec3 gray = vec3(0.5); // same as vec3(0.5, 0.5, 0.5)

In HLSL write all three: float3(g, g, g).


HLSL


Functions look the same, with HLSL types: float brightness(float3 color) { ... }. The order rule is the same too.