Как вы можете заметить выше, я иногда беру код с
ShaderToy для того, чтобы изменить его в той или иной степени или же переписать под Ren'Py для дальнейшего использования. Так вот о последнем вам и хочу рассказать:
как взять (почти) любой шейдер с ShaderToy и перенести его в Ren'Py. Заранее предупрежу, что каждый шейдер на ShaderToy
защищён авторским правом и их использование возможно лишь с разрешения автора или же в соответствии с CC BY-NC-SA 3.0.
Начну с базы:
как сделать шейдер?
Для этого используется функция
renpy.register_shader(), в аргументы которой вам нужно вписать как минимум название и фрагментный код, а при необходимости также переменные и вершинный код.
Простейший шейдер выглядит так:
init python:
renpy.register_shader("None", fragment_300="""gl_FragColor=gl_FragColor;""")Тот просто присваивает каждому пикселю его текущий цвет, что эквивалентно отсутствию эффекта. fragment это обозначение того, какую часть шейдера мы пишем (кроме fragment есть vertex), _300 это то, с каким приоритетом будет применена эта часть кода относительно других шейдеров - чем больше, тем позже. Также есть префикс _functions и аргумент variables, про которые ниже.
Один из способов применения:
label kek:
show sprite:
shader "None"Поскольку вышеописанное сильно отличается от любого другого шейдерного кода, что вы могли видеть, то я обязан пояснить:
часть кода Ren'Py генерирует за вас.
Если вы в вашей новелле сделаете
config.log_gl_shaders = True то у вас в log.txt будет чистый текст того, какой именно код Ren'Py сделал для вас. И по нему вы можете увидеть, что как минимум есть отдельный vertex None renpy.geometry и отдельный fragment None renpy.geometry, и что ни одну строку из vertex мы не писали, т.к. позиционированием картинок движок управляет сам и соответственно ставит каждому шейдеру своё позиционирование. В fragment же, в void main конкретно мы можем увидеть тот gl_FragColor=gl_FragColor; что и писали ранее.
Собственно в этом и вся связь между кодом шейдера на Ren'Py и ShaderToy: нужно перенести нужные части кода в нужные аргументы
renpy.register_shader(). Потому далее расскажу на
примере.
В указанном шейдере до void mainImage есть другой код, который относится к fragment_functions, потому можно его напрямую вставить в fragment_functions=""" """. Затем в void mainImage мы видим основной код, который соответствует fragment с любым приоритетом, соответственно передаём его туда.
Но так как ShaderToy и Ren'Py используют разные версии OpenGL и разные названия для базовых входных данных (uniforms), то нужно внести изменения.
Начнём с простейшего:
коррекция разницы между версиями.
Достаточно изменить fragCoord на gl_FragCoord, fragColor на gl_FragColor, чтобы исправить разницу в версиях в том шейдере. Также в других шейдерах для работы с текстурами нужно поменять texture на texture2D.
Теперь надо изменить и прописать
переменные (uniforms).
В ShaderToy чаще всего используются как переменные iTime, iResolution, iChannel0, ..., iChannel3, iMouse, но существуют и другие, которые используются реже.
Для того, чтобы прописать переменную необходимо писать в аргумент variables. Допустим хотим вписать переменную времени с момента текущего показа. Тогда пишем: variables="""uniform float u_time;"""", где float это тип переменной. Соответственно при адаптировании надо поменять iTime на u_time везде. Аналогом vec3 iResolution можно назвать vec2 u_virtual_size, т.к. первые два элемента векторов соответствуют друг другу, и его заменяем. И на этом всё в целом. Мы адаптировали тот пример под Ren'Py, но продолжу немного про другие аналоги.
Аналогами для названий текстур iChannel0 к tex0, ..., iChannel3 к tex3, и соответственно при использовании каждой надо добавлять sampler2D tex0 в variables. iMouse же отвечает за позицию курсора в момент нажатия, и т.к. мы можем получать позицию курсора из
renpy.get_mouse_pos(), то создание аналога усложняется до того, что мы должны создать либо
DynamicDisplayable либо
screen, в котором мы будем каждый кадр через
timer передавать координаты курсора в u_mouse_pos,