Есть два типа шейдеров: вершинный и фрагментный.
Вершинный шейдер может управлять конкретными вершинами сетки и изменять их. Т.е. на вход мы получаем массив вершин (например 8 штук для куба) и должны на выход отдать его же с новыми параметрами. Для написания таких шейдеров нужно знание тригонометрии и векторов.
Фрагментный шейдер управляет цветом конкретного пикселя на экране. Т.е. на вход мы получаем массив пикселей в формате rgba (красный, зелёный, синий и альфаканал) и должны на выходе отдать изменённый массив. Тут чистая математика как по мне.
В общем ничего там мега-сложного нет. В языке уже есть 100500 готовых функций для работы с векторами например. Основная сложность в том, что сперва придётся очень часто лазить документацию.