Add in documentation

This commit is contained in:
EmeraldLockdown 2026-03-31 21:24:35 -05:00
parent ed3cdeae10
commit ac91a0a379
4 changed files with 696 additions and 0 deletions

View file

@ -0,0 +1,260 @@
---@param item number
---@param with_alpha boolean
---@param only_alpha boolean
---@param hint_single_element boolean
---@return string
local function shader_item_to_str(item, with_alpha, only_alpha, hint_single_element)
if not only_alpha then
if item == SHADER_0 then
return with_alpha and "vec4(0.0, 0.0, 0.0, 0.0)" or "vec3(0.0, 0.0, 0.0)"
elseif item == SHADER_1 then
return with_alpha and "vec4(1.0, 1.0, 1.0, 1.0)" or "vec3(1.0, 1.0, 1.0)"
elseif item >= SHADER_INPUT_1 and item <= SHADER_INPUT_8 then
local idx = item - SHADER_INPUT_1 + 1
local name = "vInput" .. idx
return with_alpha and name or (name .. ".rgb")
elseif item == SHADER_TEXEL0 then
return with_alpha and "texVal0" or "texVal0.rgb"
elseif item == SHADER_TEXEL0A then
if hint_single_element then return "texVal0.a" end
return with_alpha and
"vec4(texVal0.a, texVal0.a, texVal0.a, texVal0.a)" or
"vec3(texVal0.a, texVal0.a, texVal0.a)"
elseif item == SHADER_TEXEL1 then
return with_alpha and "texVal1" or "texVal1.rgb"
elseif item == SHADER_TEXEL1A then
if hint_single_element then return "texVal1.a" end
return with_alpha and
"vec4(texVal1.a, texVal1.a, texVal1.a, texVal1.a)" or
"vec3(texVal1.a, texVal1.a, texVal1.a)"
elseif item == SHADER_COMBINED then
return with_alpha and "texel" or "texel.rgb"
elseif item == SHADER_COMBINEDA then
if hint_single_element then return "texel.a" end
return with_alpha and
"vec4(texel.a, texel.a, texel.a, texel.a)" or
"vec3(texel.a, texel.a, texel.a)"
elseif item == SHADER_NOISE then
return with_alpha and "vec4(noise)" or "vec3(noise)"
end
else
if item == SHADER_0 then
return "0.0"
elseif item == SHADER_1 then
return "1.0"
elseif item >= SHADER_INPUT_1 and item <= SHADER_INPUT_8 then
local idx = item - SHADER_INPUT_1 + 1
return "vInput" .. idx .. ".a"
elseif item == SHADER_TEXEL0 or item == SHADER_TEXEL0A then
return "texVal0.a"
elseif item == SHADER_TEXEL1 or item == SHADER_TEXEL1A then
return "texVal1.a"
elseif item == SHADER_COMBINED or item == SHADER_COMBINEDA then
return "texel.a"
elseif item == SHADER_NOISE then
return "noise.a"
end
end
return "unknown"
end
---@param cmd number[]
---@param do_single boolean
---@param do_multiply boolean
---@param do_mix boolean
---@param with_alpha boolean
---@param only_alpha boolean
---@return string
local function append_formula(cmd, do_single, do_multiply, do_mix, with_alpha, only_alpha)
local base = (only_alpha and 4 or 0)
if do_single then
return shader_item_to_str(cmd[base + 4], with_alpha, only_alpha, false)
elseif do_multiply then
local a = shader_item_to_str(cmd[base + 1], with_alpha, only_alpha, false)
local c = shader_item_to_str(cmd[base + 3], with_alpha, only_alpha, true)
return a .. " * " .. c
elseif do_mix then
local b = shader_item_to_str(cmd[base + 2], with_alpha, only_alpha, false)
local a = shader_item_to_str(cmd[base + 1], with_alpha, only_alpha, false)
local c = shader_item_to_str(cmd[base + 3], with_alpha, only_alpha, true)
return "mix(" .. b .. ", " .. a .. ", " .. c .. ")"
else
local a = shader_item_to_str(cmd[base + 1], with_alpha, only_alpha, false)
local b = shader_item_to_str(cmd[base + 2], with_alpha, only_alpha, false)
local c = shader_item_to_str(cmd[base + 3], with_alpha, only_alpha, true)
local d = shader_item_to_str(cmd[base + 4], with_alpha, only_alpha, false)
return "(" .. a .. " - " .. b .. ") * " .. c .. " + " .. d
end
end
---@param cc ColorCombiner
---@param shaderIndex integer
local function on_vertex_shader_create(cc, shaderIndex)
local vertexShader = {}
table.insert(vertexShader, "#version 150")
table.insert(vertexShader, "in vec4 aVtxPos;")
table.insert(vertexShader, "in vec2 aTexCoord;")
table.insert(vertexShader, "out vec2 vTexCoord;")
table.insert(vertexShader, "in vec4 aFog;")
table.insert(vertexShader, "out vec4 vFog;")
table.insert(vertexShader, "in vec2 aLightMap;")
table.insert(vertexShader, "out vec2 vLightMap;")
for i = 1, CC_MAX_INPUTS do
table.insert(vertexShader, string.format("in vec4 aInput%d;", i))
table.insert(vertexShader, string.format("out vec4 vInput%d;", i))
end
table.insert(vertexShader, "in vec3 aNormal;")
table.insert(vertexShader, "out vec3 vNormal;")
table.insert(vertexShader, "in vec3 aBarycentric;")
table.insert(vertexShader, "out vec3 vBarycentric;")
table.insert(vertexShader, "void main() {")
table.insert(vertexShader, "vTexCoord = aTexCoord;")
table.insert(vertexShader, "vFog = aFog;")
table.insert(vertexShader, "vLightMap = aLightMap;")
for i = 1, CC_MAX_INPUTS do
table.insert(vertexShader, string.format("vInput%d = aInput%d;", i, i))
end
table.insert(vertexShader, "vNormal = aNormal;")
table.insert(vertexShader, "vBarycentric = aBarycentric;")
table.insert(vertexShader, "gl_Position = aVtxPos;")
table.insert(vertexShader, "}")
return table.concat(vertexShader, "\n")
end
---@param cc ColorCombiner
---@param shaderIndex integer
local function on_fragment_shader_create(cc, shaderIndex)
local ccf = gfx_color_combiner_get_features(cc)
local opt_alpha = cc.cm.flags & USE_ALPHA ~= 0
local opt_fog = cc.cm.flags & USE_FOG ~= 0
local opt_texture_edge = cc.cm.flags & TEXTURE_EDGE ~= 0
local opt_2cycle = cc.cm.flags & USE_2CYCLE ~= 0
local opt_light_map = cc.cm.flags & LIGHT_MAP ~= 0
local opt_dither = cc.cm.flags & USE_DITHER ~= 0
local fragmentShader = {}
table.insert(fragmentShader, "#version 150")
table.insert(fragmentShader, "out vec4 fragColor;")
table.insert(fragmentShader, "in vec2 vTexCoord;")
table.insert(fragmentShader, "in vec4 vFog;")
table.insert(fragmentShader, "in vec2 vLightMap;")
for i = 1, CC_MAX_INPUTS do
table.insert(fragmentShader, string.format("in vec4 vInput%d;", i))
end
table.insert(fragmentShader, "in vec3 vNormal;")
table.insert(fragmentShader, "in vec3 vBarycentric;")
if ccf.used_textures[1] then
table.insert(fragmentShader, "uniform sampler2D uTex0;")
table.insert(fragmentShader, "uniform vec2 uTex0Size;")
table.insert(fragmentShader, "uniform bool uTex0Filter;")
end
if ccf.used_textures[2] then
table.insert(fragmentShader, "uniform sampler2D uTex1;")
table.insert(fragmentShader, "uniform vec2 uTex1Size;")
table.insert(fragmentShader, "uniform bool uTex1Filter;")
end
if ccf.used_textures[1] or ccf.used_textures[2] then
table.insert(fragmentShader, "#define TEX_OFFSET(off) texture(tex, texCoord - (off)/texSize)")
table.insert(fragmentShader, "vec4 filter3point(in sampler2D tex, in vec2 texCoord, in vec2 texSize) {")
table.insert(fragmentShader, " vec2 offset = fract(texCoord*texSize - vec2(0.5));")
table.insert(fragmentShader, " offset -= step(1.0, offset.x + offset.y);")
table.insert(fragmentShader, " vec4 c0 = TEX_OFFSET(offset);")
table.insert(fragmentShader, " vec4 c1 = TEX_OFFSET(vec2(offset.x - sign(offset.x), offset.y));")
table.insert(fragmentShader, " vec4 c2 = TEX_OFFSET(vec2(offset.x, offset.y - sign(offset.y)));")
table.insert(fragmentShader, " return c0 + abs(offset.x)*(c1-c0) + abs(offset.y)*(c2-c0);")
table.insert(fragmentShader, "}")
table.insert(fragmentShader, "vec4 sampleTex(in sampler2D tex, in vec2 uv, in vec2 texSize, in bool dofilter, in int filterType) {")
table.insert(fragmentShader, " if (dofilter && filterType == 2) return filter3point(tex, uv, texSize);")
table.insert(fragmentShader, " else return texture(tex, uv);")
table.insert(fragmentShader, "}")
end
if (opt_alpha and opt_dither) or ccf.do_noise then
table.insert(fragmentShader, "uniform float uFrameCount;")
table.insert(fragmentShader, "float random(in vec3 value) {")
table.insert(fragmentShader, " float random = dot(sin(value), vec3(12.9898, 78.233, 37.719));")
table.insert(fragmentShader, " return fract(sin(random) * 143758.5453);")
table.insert(fragmentShader, "}")
end
if opt_light_map then
table.insert(fragmentShader, "uniform vec3 uLightmapColor;")
end
table.insert(fragmentShader, "uniform int uFilter;")
table.insert(fragmentShader, "void main() {")
if (opt_alpha and opt_dither) or ccf.do_noise then
table.insert(fragmentShader, "float noise = floor(random(floor(vec3(gl_FragCoord.xy, uFrameCount))) + 0.5);")
end
if ccf.used_textures[1] then
table.insert(fragmentShader, "vec4 texVal0 = sampleTex(uTex0, vTexCoord, uTex0Size, uTex0Filter, uFilter);")
end
if ccf.used_textures[2] then
if opt_light_map then
table.insert(fragmentShader, "vec4 texVal1 = sampleTex(uTex1, vLightMap, uTex1Size, uTex1Filter, uFilter);")
table.insert(fragmentShader, "texVal0.rgb *= uLightmapColor.rgb;")
table.insert(fragmentShader, "texVal1.rgb = texVal1.rgb * texVal1.rgb + texVal1.rgb;")
else
table.insert(fragmentShader, "vec4 texVal1 = sampleTex(uTex1, vTexCoord, uTex1Size, uTex1Filter, uFilter);")
end
end
table.insert(fragmentShader, (opt_alpha and "vec4 texel = " or "vec3 texel = "))
for i = 0, (opt_2cycle and 1 or 0) do
local cmd = {}
for j = 0, CC_MAX_INPUTS - 1 do
cmd[j + 1] = cc.shader_commands[i * CC_MAX_INPUTS + j + 1]
end
local idx = i * 2
if not ccf.color_alpha_same[i + 1] and opt_alpha then
local color = append_formula(cmd, ccf.do_single[idx + 1], ccf.do_multiply[idx + 1], ccf.do_mix[idx + 1], false, false)
local alpha = append_formula(cmd, ccf.do_single[idx + 2], ccf.do_multiply[idx + 2], ccf.do_mix[idx + 2], true, true)
table.insert(fragmentShader, "vec4(" .. color .. ", " .. alpha .. ")")
else
table.insert(fragmentShader, append_formula(cmd, ccf.do_single[idx + 1], ccf.do_multiply[idx + 1], ccf.do_mix[idx + 1], opt_alpha, false))
end
table.insert(fragmentShader, ";")
if i == 0 and opt_2cycle then table.insert(fragmentShader, "texel = ") end
end
if opt_texture_edge and opt_alpha then
table.insert(fragmentShader, "if (texel.a > 0.3) texel.a = 1.0; else discard;")
end
if opt_fog then
if opt_alpha then
table.insert(fragmentShader, "texel = vec4(mix(texel.rgb, vFog.rgb, vFog.a), texel.a);")
else
table.insert(fragmentShader, "texel = mix(texel, vFog.rgb, vFog.a);")
end
end
if opt_alpha and opt_dither then
table.insert(fragmentShader, "texel.a *= noise;")
end
if opt_alpha then
table.insert(fragmentShader, "fragColor = texel;")
else
table.insert(fragmentShader, "fragColor = vec4(texel, 1.0);")
end
table.insert(fragmentShader, "}")
return table.concat(fragmentShader, "\n")
end
hook_event(HOOK_ON_VERTEX_SHADER_CREATE, on_vertex_shader_create)
hook_event(HOOK_ON_FRAGMENT_SHADER_CREATE, on_fragment_shader_create)
gfx_reload_shaders()

View file

@ -0,0 +1,225 @@
-- name: Inverted Color Shader
-- description: This is an example of a fragment shader!
-- function taken from C
---@param item number
---@param with_alpha boolean
---@param only_alpha boolean
---@param hint_single_element boolean
---@return string
local function shader_item_to_str(item, with_alpha, only_alpha, hint_single_element)
if not only_alpha then
if item == SHADER_0 then
return with_alpha and "vec4(0.0, 0.0, 0.0, 0.0)" or "vec3(0.0, 0.0, 0.0)"
elseif item == SHADER_1 then
return with_alpha and "vec4(1.0, 1.0, 1.0, 1.0)" or "vec3(1.0, 1.0, 1.0)"
elseif item >= SHADER_INPUT_1 and item <= SHADER_INPUT_8 then
local idx = item - SHADER_INPUT_1 + 1
local name = "vInput" .. idx
return with_alpha and name or (name .. ".rgb")
elseif item == SHADER_TEXEL0 then
return with_alpha and "texVal0" or "texVal0.rgb"
elseif item == SHADER_TEXEL0A then
if hint_single_element then return "texVal0.a" end
return with_alpha and
"vec4(texVal0.a, texVal0.a, texVal0.a, texVal0.a)" or
"vec3(texVal0.a, texVal0.a, texVal0.a)"
elseif item == SHADER_TEXEL1 then
return with_alpha and "texVal1" or "texVal1.rgb"
elseif item == SHADER_TEXEL1A then
if hint_single_element then return "texVal1.a" end
return with_alpha and
"vec4(texVal1.a, texVal1.a, texVal1.a, texVal1.a)" or
"vec3(texVal1.a, texVal1.a, texVal1.a)"
elseif item == SHADER_COMBINED then
return with_alpha and "texel" or "texel.rgb"
elseif item == SHADER_COMBINEDA then
if hint_single_element then return "texel.a" end
return with_alpha and
"vec4(texel.a, texel.a, texel.a, texel.a)" or
"vec3(texel.a, texel.a, texel.a)"
elseif item == SHADER_NOISE then
return with_alpha and "vec4(noise)" or "vec3(noise)"
end
else
if item == SHADER_0 then
return "0.0"
elseif item == SHADER_1 then
return "1.0"
elseif item >= SHADER_INPUT_1 and item <= SHADER_INPUT_8 then
local idx = item - SHADER_INPUT_1 + 1
return "vInput" .. idx .. ".a"
elseif item == SHADER_TEXEL0 or item == SHADER_TEXEL0A then
return "texVal0.a"
elseif item == SHADER_TEXEL1 or item == SHADER_TEXEL1A then
return "texVal1.a"
elseif item == SHADER_COMBINED or item == SHADER_COMBINEDA then
return "texel.a"
elseif item == SHADER_NOISE then
return "noise.a"
end
end
return "unknown"
end
-- function taken from C
---@param cmd number[]
---@param do_single boolean
---@param do_multiply boolean
---@param do_mix boolean
---@param with_alpha boolean
---@param only_alpha boolean
---@return string
local function append_formula(cmd, do_single, do_multiply, do_mix, with_alpha, only_alpha)
local base = (only_alpha and 4 or 0)
local function item(idx, hint_single)
return shader_item_to_str(cmd[base + idx + 1], with_alpha, only_alpha, hint_single)
end
if do_single then
return item(3, false)
elseif do_multiply then
return item(0, false) .. " * " .. item(2, true)
elseif do_mix then
return "mix(" .. item(1, false) .. ", " .. item(0, false) .. ", " .. item(2, true) .. ")"
else
return "(" .. item(0, false) .. " - " .. item(1, false) .. ") * " .. item(2, true) .. " + " .. item(3, false)
end
end
---@param cc ColorCombiner
---@param shaderIndex integer
local function on_fragment_shader_create(cc, shaderIndex)
-- egt color combiner info and features
local ccf = gfx_color_combiner_get_features(cc)
local opt_alpha = cc.cm.flags & USE_ALPHA ~= 0
local opt_fog = cc.cm.flags & USE_FOG ~= 0
local opt_texture_edge = cc.cm.flags & TEXTURE_EDGE ~= 0
local opt_2cycle = cc.cm.flags & USE_2CYCLE ~= 0
local opt_light_map = cc.cm.flags & LIGHT_MAP ~= 0
local opt_dither = cc.cm.flags & USE_DITHER ~= 0
-- define a table that will later be turned into a string
local fragmentShader = {}
table.insert(fragmentShader, "#version 150")
table.insert(fragmentShader, "out vec4 fragColor;")
table.insert(fragmentShader, "in vec2 vTexCoord;")
table.insert(fragmentShader, "in vec4 vFog;")
table.insert(fragmentShader, "in vec2 vLightMap;")
for i = 1, CC_MAX_INPUTS do
table.insert(fragmentShader, string.format("in vec4 vInput%d;", i))
end
table.insert(fragmentShader, "in vec3 vNormal;")
table.insert(fragmentShader, "in vec3 vBarycentric;")
if ccf.used_textures[1] then
table.insert(fragmentShader, "uniform sampler2D uTex0;")
table.insert(fragmentShader, "uniform vec2 uTex0Size;")
table.insert(fragmentShader, "uniform bool uTex0Filter;")
end
if ccf.used_textures[2] then
table.insert(fragmentShader, "uniform sampler2D uTex1;")
table.insert(fragmentShader, "uniform vec2 uTex1Size;")
table.insert(fragmentShader, "uniform bool uTex1Filter;")
end
if ccf.used_textures[1] or ccf.used_textures[2] then
table.insert(fragmentShader, "#define TEX_OFFSET(off) texture(tex, texCoord - (off)/texSize)")
table.insert(fragmentShader, "vec4 filter3point(in sampler2D tex, in vec2 texCoord, in vec2 texSize) {")
table.insert(fragmentShader, " vec2 offset = fract(texCoord*texSize - vec2(0.5));")
table.insert(fragmentShader, " offset -= step(1.0, offset.x + offset.y);")
table.insert(fragmentShader, " vec4 c0 = TEX_OFFSET(offset);")
table.insert(fragmentShader, " vec4 c1 = TEX_OFFSET(vec2(offset.x - sign(offset.x), offset.y));")
table.insert(fragmentShader, " vec4 c2 = TEX_OFFSET(vec2(offset.x, offset.y - sign(offset.y)));")
table.insert(fragmentShader, " return c0 + abs(offset.x)*(c1-c0) + abs(offset.y)*(c2-c0);")
table.insert(fragmentShader, "}")
table.insert(fragmentShader, "vec4 sampleTex(in sampler2D tex, in vec2 uv, in vec2 texSize, in bool dofilter, in int filterType) {")
table.insert(fragmentShader, " if (dofilter && filterType == 2) return filter3point(tex, uv, texSize);")
table.insert(fragmentShader, " else return texture(tex, uv);")
table.insert(fragmentShader, "}")
end
if (opt_alpha and opt_dither) or ccf.do_noise then
table.insert(fragmentShader, "uniform float uFrameCount;")
table.insert(fragmentShader, "float random(in vec3 value) {")
table.insert(fragmentShader, " float random = dot(sin(value), vec3(12.9898, 78.233, 37.719));")
table.insert(fragmentShader, " return fract(sin(random) * 143758.5453);")
table.insert(fragmentShader, "}")
end
if opt_light_map then
table.insert(fragmentShader, "uniform vec3 uLightmapColor;")
end
table.insert(fragmentShader, "uniform int uFilter;")
table.insert(fragmentShader, "void main() {")
if (opt_alpha and opt_dither) or ccf.do_noise then
table.insert(fragmentShader, "float noise = floor(random(floor(vec3(gl_FragCoord.xy, uFrameCount))) + 0.5);")
end
if ccf.used_textures[1] then
table.insert(fragmentShader, "vec4 texVal0 = sampleTex(uTex0, vTexCoord, uTex0Size, uTex0Filter, uFilter);")
end
if ccf.used_textures[2] then
if opt_light_map then
table.insert(fragmentShader, "vec4 texVal1 = sampleTex(uTex1, vLightMap, uTex1Size, uTex1Filter, uFilter);")
table.insert(fragmentShader, "texVal0.rgb *= uLightmapColor.rgb;")
table.insert(fragmentShader, "texVal1.rgb = texVal1.rgb * texVal1.rgb + texVal1.rgb;")
else
table.insert(fragmentShader, "vec4 texVal1 = sampleTex(uTex1, vTexCoord, uTex1Size, uTex1Filter, uFilter);")
end
end
table.insert(fragmentShader, (opt_alpha and "vec4 texel = " or "vec3 texel = "))
for i = 0, (opt_2cycle and 1 or 0) do
local cmd = {}
for j = 0, CC_MAX_INPUTS - 1 do
cmd[j + 1] = cc.shader_commands[i * CC_MAX_INPUTS + j + 1]
end
local idx = i * 2
if not ccf.color_alpha_same[i + 1] and opt_alpha then
local color = append_formula(cmd, ccf.do_single[idx + 1], ccf.do_multiply[idx + 1], ccf.do_mix[idx + 1], false, false)
local alpha = append_formula(cmd, ccf.do_single[idx + 2], ccf.do_multiply[idx + 2], ccf.do_mix[idx + 2], true, true)
table.insert(fragmentShader, "vec4(" .. color .. ", " .. alpha .. ")")
else
table.insert(fragmentShader, append_formula(cmd, ccf.do_single[idx + 1], ccf.do_multiply[idx + 1], ccf.do_mix[idx + 1], opt_alpha, false))
end
table.insert(fragmentShader, ";")
if i == 0 and opt_2cycle then table.insert(fragmentShader, "texel = ") end
end
if opt_texture_edge and opt_alpha then
table.insert(fragmentShader, "if (texel.a > 0.3) texel.a = 1.0; else discard;")
end
if opt_fog then
if opt_alpha then
table.insert(fragmentShader, "texel = vec4(mix(texel.rgb, vFog.rgb, vFog.a), texel.a);")
else
table.insert(fragmentShader, "texel = mix(texel, vFog.rgb, vFog.a);")
end
end
if opt_alpha and opt_dither then
table.insert(fragmentShader, "texel.a *= noise;")
end
-- here we invert the texel color
if opt_alpha then
table.insert(fragmentShader, "fragColor = vec4(1.0 - texel.rgb, texel.a);")
else
table.insert(fragmentShader, "fragColor = vec4(1.0 - texel, 1.0);")
end
table.insert(fragmentShader, "}")
return table.concat(fragmentShader, "\n")
end
hook_event(HOOK_ON_FRAGMENT_SHADER_CREATE, on_fragment_shader_create)
-- reload shaders so we can see our new shaders
gfx_reload_shaders()

View file

@ -0,0 +1,42 @@
---@param cc ColorCombiner
---@param shaderIndex integer
local function on_vertex_shader_create(cc, shaderIndex)
local vertexShader = {}
table.insert(vertexShader, "#version 150")
table.insert(vertexShader, "in vec4 aVtxPos;")
table.insert(vertexShader, "in vec2 aTexCoord;")
table.insert(vertexShader, "out vec2 vTexCoord;")
table.insert(vertexShader, "in vec4 aFog;")
table.insert(vertexShader, "out vec4 vFog;")
table.insert(vertexShader, "in vec2 aLightMap;")
table.insert(vertexShader, "out vec2 vLightMap;")
for i = 1, CC_MAX_INPUTS do
table.insert(vertexShader, string.format("in vec4 aInput%d;", i))
table.insert(vertexShader, string.format("out vec4 vInput%d;", i))
end
table.insert(vertexShader, "in vec3 aNormal;")
table.insert(vertexShader, "out vec3 vNormal;")
table.insert(vertexShader, "in vec3 aBarycentric;")
table.insert(vertexShader, "out vec3 vBarycentric;")
table.insert(vertexShader, "void main() {")
table.insert(vertexShader, "vTexCoord = aTexCoord;")
table.insert(vertexShader, "vFog = aFog;")
table.insert(vertexShader, "vLightMap = aLightMap;")
for i = 1, CC_MAX_INPUTS do
table.insert(vertexShader, string.format("vInput%d = aInput%d;", i, i))
end
table.insert(vertexShader, "vNormal = aNormal;")
table.insert(vertexShader, "vBarycentric = aBarycentric;")
table.insert(vertexShader, "gl_Position = vec4(aVtxPos.x * -1.0, aVtxPos.yzw);")
table.insert(vertexShader, "}")
return table.concat(vertexShader, "\n")
end
hook_event(HOOK_ON_VERTEX_SHADER_CREATE, on_vertex_shader_create)
gfx_reload_shaders()

169
docs/lua/guides/shaders.md Normal file
View file

@ -0,0 +1,169 @@
# Shaders
## Resources
- [Default C Shader in Lua](../examples/shader-demo/default-shader.lua)
- [Example Vertex Shader](../examples/shader-demo/mirror-shader.lua)
- [Example Fragment Shader](../examples/shader-demo/invert-color-shader.lua)
## Before Starting...
While this guide does go into a lot of detail, you should still have some basic knowledge on how writing mods in Lua works, and a bit of knowledge on how shaders work will also go a very long way.
When testing shaders, it is recommended to launch your game with the terminal. While technically the game will fallback on default shaders if a shader fails to compile, there are multiple cases that are common enough to where you will appreciate running the game via a terminal since the console will be unavailable/invisible.
## Creating Shaders
Shaders can be created via the `HOOK_ON_VERTEX_SHADER_CREATE` and `HOOK_ON_FRAGMENT_SHADER_CREATE` hooks. These hooks provides a [ColorCombiner](../structs.md#ColorCombiner) and shader index, and expects you to return a vertex shader back. The vertex shader allows for manipulating vertex data, whereas the fragment shader allows for manipulating color data. You do not have to provide both, but if you only use one, you are expected to send/receive the proper data back.
## Vertex Shaders
The bread and butter of vertex shaders are inputs. Inputs are given by the game's code and can be used for getting the vertex data. A list of inputs can be found [here](#Inputs). Something important about inputs is that a fragment shader cannot get inputs from C, so you need to pass inputs from the vertex shader as outputs for the fragment shader. An example of that would be the texture coords.
```lua
in vec2 aTexCoord;
out vec2 vTexCoord;
vTexCoord = aTexCoord;
```
As you can see, the input is gotten in the vertex shader, and the output is created, which will be received in the fragment shader.
```lua
in vec2 vTexCoord;
```
Here, the fragment shader takes the tex coord output we created in the vertex shader.
Getting back on track, for our vertex shader example, we are going to mirror the entire world. First, grab the [default C shader in Lua](../examples/shader-demo/default-shader.lua), specifically the vertex portion. Most of this shader will be passing inputs to the fragment shader, but what we care about is this portion:
```lua
gl_Position = aVtxPos;
```
This sets the vertex position in opengl to our vertex position provided by C. There are 4 main components to this position:
- `x` being the X coordinate
- `y` being the Y coordinate
- `z` being the Z coordinate
- `w` being the depth coordinate
Importantly, this position is not in world space, it is instead in clip space, but that does not matter for our example.
To get our mirror effect, we need to flip the `x` coordinate. So we need to create a vector 4 that flips our `x` component, and passes back our `y`, `z`, and `w` components as-is. This can be done via this syntax:
```lua
gl_Position = vec4(aVtxPos.x * -1.0, aVtxPos.yzw);
```
If you run the mod, you should now have a mirrored game! That's the basic rundown on vertex shaders! If something still isn't working, compare your code with [the example vertex shader](../examples/shader-demo/mirror-shader.lua) and try to figure out what you did wrong.
## Fragment Shaders
Fragment shaders are quite a bit more involved, but I'll try to keep it simple. First, grab the [default C shader in Lua](../examples/shader-demo/default-shader.lua), specifically the fragment shader portion, as well as the `shader_item_to_str` and `append_formula` functions. While we won't be explaining what those do right now, to summarize, they are there to handle the color combiner.
For our example fragment shader, we are going to be inverting the colors. Actually doing this is quite simple, but before we get to that, let's explain what we are working with:
First, unlike the vertex shader, you have to define the output manually in a fragment shader.
```lua
out vec4 fragColor;
```
In the fragment shader, we create an output for the fragment color. Unlike the vertex shader, an output does not go to another shader, instead, it goes to OpenGL to be used for color data.
All the outputs in the vertex shader can be read in the fragment shader as inputs. This allows data to be carried over from one to the other.
For our example, inverting the colors, we only have to look at 2 lines. One for if the color combiner specifies an alpha, and the other if it does not. If we do have an alpha, we want to preserve it when inverting the color. Colors in OpenGL, unlike the rest of sm64, are stored in between `0.0` and `1.0`. That means if I want to represent half of red, I would need to use `0.5` instead of `128`. For inverting a color this is simple. Combining these things, we need to subtract a full color, or `1.0`, by the rgb of `texel`, then pass in the alpha value as normal.
```lua
fragColor = vec4(1.0 - texel.rgb, texel.a);
```
If the color combiner does not have an alpha value, then the alpha should simply be `1.0`, or fully opaque.
```lua
fragColor = vec4(1.0 - texel.rgb, 1.0);
```
And that's it! All colors in your game should now be completely inverted! That's the basic rundown on fragment shaders! If something still isn't working, compare your code with [the example fragment shader](../examples/shader-demo/invert-color-shader.lua) and try to figure out what you did wrong.
## Uniforms
A shader may contain uniforms. As a naming convention, uniform variables typically start with a `u`, for instance, `uFrameCount`. There are multiple different uniforms already updated by C, but you can also update custom uniforms in Lua. First, here is a list of all the uniforms updated in C.
| Uniform Name | Type | Description |
| ---- | ---- | ---- |
| `uTex0` | `sampler2D` | The primary texture |
| `uTex1` | `sampler2D` | The secondary texture |
| `uTex0Size` | `vec2` | The width and height of the primary texture |
| `uTex1Size` | `vec2` | The width and height of the secondary texture |
| `uTex0Filter` | `bool` | True if the first texture is using linear filtering |
| `uTex1Filter` | `bool` | True if the second texture is using linear filtering |
| `uFilter` | `int` | The current global filtering mode (0 = Point, 1 = Linear, 2 = Three-point) |
| `uFrameCount` | `float` | A timer that increases every frame |
| `uLightmapColor` | `vec3` | The RGB color multiplier applied to the environment/lightmap |
| `uModelViewMatrix` | `mat4` | Transforms objects from world space to view space |
| `uProjectionMatrix` | `mat4` | Transforms view space to clip space |
| `uInverseViewMatrix` | `mat4` | The inverse of the view matrix |
For defining a custom uniform in lua, first, define the uniform and use the uniform as you intend in your shader code. Next you're going to want to store the shader index given by the hook. Create a table and store all your shader indexes. Then in lua, in any hook as seen fit, iterate through the list of shader indexes. Now, create a variable and set it to the returned value of `gfx_get_program_id_from_shader_index`. First, use that program with `gfx_use_program`, then get the uniform with `gfx_shader_get_uniform_location`. You should then set it with the appropriate `gfx_shader_set_` function. Here is an example:
```lua
local fogColor = { r = 168, g = 175, b = 195 }
-- iterate through the shader indexes
for _, index in pairs(sShaderIndexes) do
-- get the program from the shader index
local program = gfx_get_program_id_from_shader_index(index)
-- use the program so we can set the uniforms
gfx_use_program(program)
-- get the uniform, which is a color of type vec4
local uFogColor = gfx_shader_get_uniform_location(program, "uFogColor")
-- set the vector 4 at that uniform location
gfx_shader_set_vec4(uFogColor, fogColor.r / 255, fogColor.g / 255, fogColor.b / 255, 1.0)
local uFogIntensity = gfx_shader_get_uniform_location(program, "uFogIntensity")
gfx_shader_set_float(uFogIntensity, 5000.0)
end
```
That allows you to define your own uniforms and set your uniforms in Lua!
Lastly, if you have multiple shaders, you should cleanup the list of shader indexes on a shader refresh. Use the `HOOK_ON_REFRESH_SHADERS` hook to reset the shader indexes table, or do anything you need to when it comes to refreshing shaders.
```lua
hook_event(HOOK_ON_REFRESH_SHADERS, function ()
sShaderIndexes = {}
end)
```
## Inputs
Inputs are passed into the vertex shader for further use. Here is a list of inputs provided by C.
| Input Name | Type | Description |
| ---- | ---- | ---- |
| `aVtxPos` | `vec4` | The vertex position (x, y, z, w) |
| `aTexCoord` | `vec2` | The standard UV mapping for the primary texture |
| `aNormal` | `vec3` | The direction the surface is facing |
| `aFog` | `vec4` | Fog data provided by C |
| `aLightMap` | `vec2` | UV coordinates for a light map |
| `aInputX` | `vec4` | Color/alpha input from the Color Combiner, with X being the input number |
| `aBarycentric` | `vec3` | The barycentric coordinates of the vertex within its triangle |
## Dealing with the HUD and Skybox
If you want to hide the HUD, you can by checking the vertex position. If the vertex position's z is greater than zero, it is not a hud element. By default the vertex position is not included in the fragment shader, so you may need to create a varying variable and send it over.
To check for the skybox, the skybox lives somewhere in between layer 0 and layer 2. You should do a range check to find it, for instance, `aVtxPos.z > 0 && aVtxPos.z < 2`.
Currently where the hud lives below 0 is a bit random, so right now it's best to either hide it all or none of it.
## Limitations
- No more than a single shader can be used at a time. This means that if 2 mods want to use their own shader, only one will be picked.
- Currently you can only do one shader pass. While for most cases a single shader pass works fine, for many types of shaders it is very limiting. This is a unfortunate limitation with the current system.
- There are only so many inputs. While I made sure to include as many as possible, there may still be some missing for your own shaders.
- DirectX and OpenGL Legacy support is non-existent.
While these limitations may improve in the future, this is where we are stuck for right now.