#pragma once #include namespace anm2ed::resource::shader { struct Info { const char* vertex; const char* fragment; }; constexpr auto VERTEX = R"( #version 330 core layout (location = 0) in vec2 i_position; layout (location = 1) in vec2 i_uv; out vec2 i_uv_out; uniform mat4 u_transform; void main() { i_uv_out = i_uv; gl_Position = u_transform * vec4(i_position, 0.0, 1.0); } )"; constexpr auto AXIS_VERTEX = R"( #version 330 core layout (location = 0) in vec2 i_position; // full screen line segment: -1..1 uniform vec2 u_origin_ndc; // world origin in NDC uniform int u_axis; // 0 = X axis, 1 = Y axis void main() { vec2 pos = (u_axis == 0) ? vec2(i_position.x, u_origin_ndc.y) // horizontal line across screen : vec2(u_origin_ndc.x, i_position.x); // vertical line across screen; gl_Position = vec4(pos, 0.0, 1.0); } )"; constexpr auto FRAGMENT = R"( #version 330 core out vec4 o_fragColor; uniform vec4 u_color; void main() { o_fragColor = u_color; } )"; constexpr auto TEXTURE_FRAGMENT = R"( #version 330 core in vec2 i_uv_out; uniform sampler2D u_texture; uniform vec4 u_tint; uniform vec3 u_color_offset; out vec4 o_fragColor; void main() { vec4 texColor = texture(u_texture, i_uv_out); texColor *= u_tint; texColor.rgb += u_color_offset; o_fragColor = texColor; } )"; constexpr auto GRID_VERTEX = R"( #version 330 core layout (location = 0) in vec2 i_position; layout (location = 1) in vec2 i_uv; out vec2 i_uv_out; void main() { i_uv_out = i_position; gl_Position = vec4(i_position, 0.0, 1.0); } )"; constexpr auto GRID_FRAGMENT = R"( #version 330 core in vec2 i_uv_out; uniform mat4 u_transform; uniform vec2 u_size; uniform vec2 u_offset; uniform vec4 u_color; out vec4 o_fragColor; void main() { vec4 world4 = u_transform * vec4(i_uv_out, 0.0, 1.0); vec2 world = world4.xy / world4.w; vec2 cell = max(u_size, vec2(1.0)); vec2 grid = (world - u_offset) / cell; vec2 frac = fract(grid); vec2 distToLine = min(frac, 1.0 - frac); float distance = min(distToLine.x, distToLine.y); float fw = min(fwidth(grid.x), fwidth(grid.y)); float alpha = 1.0 - smoothstep(0.0, fw, distance); if (alpha <= 0.0) discard; o_fragColor = vec4(u_color.rgb, u_color.a * alpha); } )"; constexpr auto DASHED_VERTEX = R"( #version 330 core layout (location = 0) in vec2 i_position; out vec2 v_local; uniform mat4 u_transform; void main() { v_local = i_position; gl_Position = u_transform * vec4(i_position, 0.0, 1.0); } )"; constexpr auto DASHED_FRAGMENT = R"( #version 330 core in vec2 v_local; uniform vec4 u_color; uniform vec2 u_axis_x; uniform vec2 u_axis_y; uniform float u_dash_length; uniform float u_dash_gap; uniform float u_dash_offset; out vec4 o_fragColor; void main() { vec2 local = clamp(v_local, 0.0, 1.0); float lengthX = max(length(u_axis_x), 1e-4); float lengthY = max(length(u_axis_y), 1e-4); float dash = max(u_dash_length, 1e-4); float gap = max(u_dash_gap, 0.0); float period = max(dash + gap, 1e-4); vec2 pixel = max(fwidth(v_local), vec2(1e-5)); float bottomMask = 1.0 - smoothstep(pixel.y, pixel.y * 2.0, local.y); float topMask = 1.0 - smoothstep(pixel.y, pixel.y * 2.0, 1.0 - local.y); float leftMask = 1.0 - smoothstep(pixel.x, pixel.x * 2.0, local.x); float rightMask = 1.0 - smoothstep(pixel.x, pixel.x * 2.0, 1.0 - local.x); float perimeterOffset = u_dash_offset; float bottomPos = mod(perimeterOffset + local.x * lengthX, period); if (bottomPos < 0.0) bottomPos += period; float bottomDash = bottomMask * (bottomPos <= dash ? 1.0 : 0.0); float rightPos = mod(perimeterOffset + lengthX + local.y * lengthY, period); if (rightPos < 0.0) rightPos += period; float rightDash = rightMask * (rightPos <= dash ? 1.0 : 0.0); float topPos = mod(perimeterOffset + lengthX + lengthY + (1.0 - local.x) * lengthX, period); if (topPos < 0.0) topPos += period; float topDash = topMask * (topPos <= dash ? 1.0 : 0.0); float leftPos = mod(perimeterOffset + 2.0 * lengthX + lengthY + (1.0 - local.y) * lengthY, period); if (leftPos < 0.0) leftPos += period; float leftDash = leftMask * (leftPos <= dash ? 1.0 : 0.0); float alpha = max(max(bottomDash, topDash), max(leftDash, rightDash)); if (alpha <= 0.0) discard; o_fragColor = vec4(u_color.rgb, u_color.a * alpha); } )"; constexpr auto UNIFORM_AXIS = "u_axis"; constexpr auto UNIFORM_COLOR = "u_color"; constexpr auto UNIFORM_TRANSFORM = "u_transform"; constexpr auto UNIFORM_TINT = "u_tint"; constexpr auto UNIFORM_COLOR_OFFSET = "u_color_offset"; constexpr auto UNIFORM_OFFSET = "u_offset"; constexpr auto UNIFORM_ORIGIN_NDC = "u_origin_ndc"; constexpr auto UNIFORM_SIZE = "u_size"; constexpr auto UNIFORM_MODEL = "u_model"; constexpr auto UNIFORM_RECT_SIZE = "u_rect_size"; constexpr auto UNIFORM_TEXTURE = "u_texture"; constexpr auto UNIFORM_AXIS_X = "u_axis_x"; constexpr auto UNIFORM_AXIS_Y = "u_axis_y"; constexpr auto UNIFORM_DASH_LENGTH = "u_dash_length"; constexpr auto UNIFORM_DASH_GAP = "u_dash_gap"; constexpr auto UNIFORM_DASH_OFFSET = "u_dash_offset"; enum ShaderType { LINE, DASHED, TEXTURE, AXIS, GRID, COUNT }; const Info SHADERS[COUNT] = {{VERTEX, FRAGMENT}, {DASHED_VERTEX, DASHED_FRAGMENT}, {VERTEX, TEXTURE_FRAGMENT}, {AXIS_VERTEX, FRAGMENT}, {GRID_VERTEX, GRID_FRAGMENT}}; } namespace anm2ed::resource { class Shader { public: GLuint id{}; Shader(); Shader(const char*, const char*); Shader& operator=(Shader&&) noexcept; ~Shader(); bool is_valid() const; }; }