Shaders and triangle filling
This commit is contained in:
parent
282ad80e46
commit
1ccc18e574
@ -15,6 +15,6 @@ add_executable(
|
|||||||
src/painter.cpp
|
src/painter.cpp
|
||||||
src/painter.hpp
|
src/painter.hpp
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/shaders.hpp
|
||||||
src/shaders.cpp
|
src/shaders.cpp
|
||||||
)
|
)
|
@ -61,4 +61,79 @@ void draw_line(Frame *frame, int x1, int y1, int x2, int y2, COLOR color) {
|
|||||||
frame->set_pixel(x, y, color);
|
frame->set_pixel(x, y, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fill_triangle(Frame *f, int x0, int y0, int x1, int y1, int x2, int y2, Shader const *shader) {
|
||||||
|
// Отсортируем точки таким образом, чтобы выполнилось условие: y0 < y1 < y2
|
||||||
|
if (y1 < y0) {
|
||||||
|
std::swap(y1, y0);
|
||||||
|
std::swap(x1, x0);
|
||||||
|
}
|
||||||
|
if (y2 < y1) {
|
||||||
|
std::swap(y2, y1);
|
||||||
|
std::swap(x2, x1);
|
||||||
|
}
|
||||||
|
if (y1 < y0) {
|
||||||
|
std::swap(y1, y0);
|
||||||
|
std::swap(x1, x0);
|
||||||
|
}
|
||||||
|
// Определяем номера строк пикселей, в которых располагаются точки треугольника
|
||||||
|
int Y0 = (int) (y0 + 0.5f);
|
||||||
|
int Y1 = (int) (y1 + 0.5f);
|
||||||
|
int Y2 = (int) (y2 + 0.5f);
|
||||||
|
// Отсечение невидимой части треугольника
|
||||||
|
if (Y0 < 0) Y0 = 0;
|
||||||
|
else if (Y0 >= f->height) Y0 = f->height;
|
||||||
|
if (Y1 < 0) Y1 = 0;
|
||||||
|
else if (Y1 >= f->height) Y1 = f->height;
|
||||||
|
if (Y2 < 0) Y2 = 0;
|
||||||
|
else if (Y2 >= f->height) Y2 = f->height;
|
||||||
|
// Рисование верхней части треугольника
|
||||||
|
for (float y = Y0 + 0.5f; y < Y1; y++) {
|
||||||
|
int X0 = (int) ((y - y0) / (y1 - y0) * (x1 - x0) + x0 + 0.5f);
|
||||||
|
int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f);
|
||||||
|
if (X0 > X1) std::swap(X0, X1);
|
||||||
|
if (X0 < 0) X0 = 0;
|
||||||
|
if (X1 > f->width) X1 = f->width;
|
||||||
|
for (int x = X0; x < X1; x++) {
|
||||||
|
f->set_pixel(x, y, shader->get_color(x + 0.5f, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Рисование нижней части треугольника
|
||||||
|
for (float y = Y1 + 0.5f; y < Y2; y++) {
|
||||||
|
int X0 = (int) ((y - y1) / (y2 - y1) * (x2 - x1) + x1 + 0.5f);
|
||||||
|
int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f);
|
||||||
|
if (X0 > X1) std::swap(X0, X1);
|
||||||
|
if (X0 < 0) X0 = 0;
|
||||||
|
if (X1 > f->width) X1 = f->width;
|
||||||
|
for (int x = X0; x < X1; x++) {
|
||||||
|
f->set_pixel(x, y, shader->get_color(x + 0.5f, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
COLOR ColorFromHSV(double hue, double saturation, double value) {
|
||||||
|
int hi = int(floor(hue / 60)) % 6;
|
||||||
|
double f = hue / 60 - floor(hue / 60);
|
||||||
|
value = value * 255;
|
||||||
|
const auto v = (unsigned char) (value);
|
||||||
|
const auto p = (unsigned char) (value * (1 - saturation));
|
||||||
|
const auto q = (unsigned char) (value * (1 - f * saturation));
|
||||||
|
const auto t = (unsigned char) (value * (1 - (1 - f) * saturation));
|
||||||
|
switch (hi) {
|
||||||
|
case 0:
|
||||||
|
return COLOR{v, t, p};
|
||||||
|
case 1:
|
||||||
|
return COLOR{q, v, p};
|
||||||
|
case 2:
|
||||||
|
return COLOR{p, v, t};
|
||||||
|
case 3:
|
||||||
|
return COLOR{p, q, v};
|
||||||
|
case 4:
|
||||||
|
return COLOR{t, p, v};
|
||||||
|
default:
|
||||||
|
return COLOR{v, p, q};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -114,4 +114,66 @@ void draw_polyline(Frame *frame, COLOR color, point_t ...points) {
|
|||||||
draw_polyline_<point_t...>::draw(frame, color, points...);
|
draw_polyline_<point_t...>::draw(frame, color, points...);
|
||||||
draw_line(frame, draw_polyline_<point_t...>::last(points...), draw_polyline_<point_t...>::first(points...), color);
|
draw_line(frame, draw_polyline_<point_t...>::last(points...), draw_polyline_<point_t...>::first(points...), color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Shader {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] virtual COLOR get_color(int x, int y) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MonoShader : public Shader {
|
||||||
|
private:
|
||||||
|
COLOR color;
|
||||||
|
public:
|
||||||
|
explicit MonoShader(COLOR color) : color{color} {}
|
||||||
|
|
||||||
|
[[nodiscard]] COLOR get_color(int x, int y) const override {
|
||||||
|
return this->color;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BarycentricInterpolatorShader : public Shader {
|
||||||
|
private:
|
||||||
|
int x0, y0, x1, y1, x2, y2, S;
|
||||||
|
COLOR C0, C1, C2;
|
||||||
|
public:
|
||||||
|
BarycentricInterpolatorShader(float _x0, float _y0, float _x1, float _y1, float
|
||||||
|
_x2, float _y2, COLOR A0, COLOR A1, COLOR A2) :
|
||||||
|
x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2),
|
||||||
|
S((_y1 - _y2) * (_x0 - _x2) + (_x2 - _x1) * (_y0 - _y2)), C0(A0), C1(A1),
|
||||||
|
C2(A2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
COLOR get_color(int x, int y) const override {
|
||||||
|
// Барицентрическая интерполяция
|
||||||
|
float h0 = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / S;
|
||||||
|
float h1 = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / S;
|
||||||
|
float h2 = 1 - h0 - h1;
|
||||||
|
float r = h0 * C0.red + h1 * C1.red + h2 * C2.red;
|
||||||
|
float g = h0 * C0.green + h1 * C1.green + h2 * C2.green;
|
||||||
|
float b = h0 * C0.blue + h1 * C1.blue + h2 * C2.blue;
|
||||||
|
float a = h0 * C0.alpha + h1 * C1.alpha + h2 * C2.alpha;
|
||||||
|
return COLOR(r, g, b, a);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void fill_triangle(Frame *f, int x0, int y0, int x1, int y1, int x2, int y2, Shader const *shader);
|
||||||
|
|
||||||
|
|
||||||
|
inline void fill_triangle(Frame *f, Point p0, Point p1, Point p2, Shader const *shader) {
|
||||||
|
fill_triangle(f, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void fill_triangle(Frame *f, int x0, int y0, int x1, int y1, int x2, int y2, COLOR color) {
|
||||||
|
MonoShader s{color};
|
||||||
|
fill_triangle(f, x0, y0, x1, y1, x2, y2, &s);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline void fill_triangle(Frame *f, Point p0, Point p1, Point p2, COLOR color) {
|
||||||
|
MonoShader s{color};
|
||||||
|
fill_triangle(f, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, &s);
|
||||||
|
}
|
||||||
|
|
||||||
|
COLOR ColorFromHSV(double hue, double saturation, double value);
|
@ -35,8 +35,42 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Variant3S1Painter : public Painter {
|
||||||
|
public:
|
||||||
|
void draw(PainterState *state, Frame *frame) const override {
|
||||||
|
int W = frame->width, H = frame->height;
|
||||||
|
// Размер рисунка возьмём меньше (7 / 8), чтобы он не касался границ экрана
|
||||||
|
float a = 7.0f / 8 * ((W < H) ? W - 1 : H - 1);
|
||||||
|
if (a < 1) return; // Если окно очень маленькое, то ничего не рисуем
|
||||||
|
float angle = state->angle; // Угол поворота
|
||||||
|
a = a / 2;
|
||||||
|
|
||||||
|
Point center{W / 2, H / 2};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Рисуем описанную окружность
|
||||||
|
draw_circle(frame, center, a, COLOR(0, 0, 0));
|
||||||
|
draw_polyline(
|
||||||
|
frame, COLOR{0, 200, 0},
|
||||||
|
center.moved(0, a).rotated_around(center, 0 + angle),
|
||||||
|
center.moved(0, a).rotated_around(center, +2.0944 + angle),
|
||||||
|
center.moved(0, a).rotated_around(center, -2.0944 + angle)
|
||||||
|
);
|
||||||
|
draw_circle(frame, center, a / 2, COLOR(0, 200, 0));
|
||||||
|
draw_circle(frame, center.moved(0, a *2 / 3).rotated_around(center, angle), a / 6, COLOR(0, 200, 0));
|
||||||
|
draw_circle(frame, center.moved(0, a *2 / 3).rotated_around(center, +2.0944 + angle), a / 6, COLOR(0, 200, 0));
|
||||||
|
draw_circle(frame, center.moved(0, a *2 / 3).rotated_around(center, -2.0944 + angle), a / 6, COLOR(0, 200, 0));
|
||||||
|
|
||||||
|
// Рисуем пиксель, на который кликнул пользователь
|
||||||
|
if (state->clicked_pixel.x >= 0 && state->clicked_pixel.x < W &&
|
||||||
|
state->clicked_pixel.y >= 0 && state->clicked_pixel.y < H)
|
||||||
|
frame->set_pixel(state->clicked_pixel.x, state->clicked_pixel.y, {34, 175, 60}); // Пиксель зелёного цвета
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static auto predefined_painters_ = std::make_tuple(
|
static auto predefined_painters_ = std::make_tuple(
|
||||||
DemoPainter()
|
Variant3S1Painter()
|
||||||
);
|
);
|
||||||
|
|
||||||
Painter const *const predefined_painters[] = {
|
Painter const *const predefined_painters[] = {
|
||||||
|
145
src/shaders.cpp
145
src/shaders.cpp
@ -1,127 +1,26 @@
|
|||||||
#include <utility>
|
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
#include "painter.hpp"
|
#include "shaders.hpp"
|
||||||
|
|
||||||
template<class ShaderClass>
|
COLOR BarycentricInterpolator::get_color(int x, int y) const {
|
||||||
void Triangle(Frame *f, float x0, float y0, float x1, float y1, float x2, float y2, COLOR color, ShaderClass *shader) {
|
|
||||||
// Отсортируем точки таким образом, чтобы выполнилось условие: y0 < y1 < y2
|
|
||||||
if (y1 < y0) {
|
|
||||||
std::swap(y1, y0);
|
|
||||||
std::swap(x1, x0);
|
|
||||||
}
|
|
||||||
if (y2 < y1) {
|
|
||||||
std::swap(y2, y1);
|
|
||||||
std::swap(x2, x1);
|
|
||||||
}
|
|
||||||
if (y1 < y0) {
|
|
||||||
std::swap(y1, y0);
|
|
||||||
std::swap(x1, x0);
|
|
||||||
}
|
|
||||||
// Определяем номера строк пикселей, в которых располагаются точки треугольника
|
|
||||||
int Y0 = (int) (y0 + 0.5f);
|
|
||||||
int Y1 = (int) (y1 + 0.5f);
|
|
||||||
int Y2 = (int) (y2 + 0.5f);
|
|
||||||
// Отсечение невидимой части треугольника
|
|
||||||
if (Y0 < 0) Y0 = 0;
|
|
||||||
else if (Y0 >= f->height) Y0 = f->height;
|
|
||||||
if (Y1 < 0) Y1 = 0;
|
|
||||||
else if (Y1 >= f->height) Y1 = f->height;
|
|
||||||
if (Y2 < 0) Y2 = 0;
|
|
||||||
else if (Y2 >= f->height) Y2 = f->height;
|
|
||||||
// Рисование верхней части треугольника
|
|
||||||
for (float y = Y0 + 0.5f; y < Y1; y++) {
|
|
||||||
int X0 = (int) ((y - y0) / (y1 - y0) * (x1 - x0) + x0 + 0.5f);
|
|
||||||
int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f);
|
|
||||||
if (X0 > X1) std::swap(X0, X1);
|
|
||||||
if (X0 < 0) X0 = 0;
|
|
||||||
if (X1 > f->width) X1 = f->width;
|
|
||||||
for (int x = X0; x < X1; x++) {
|
|
||||||
// f(x + 0.5, y)
|
|
||||||
SetPixel(x, y, shader->getColor(x + 0.5f, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Рисование нижней части треугольника
|
|
||||||
for (float y = Y1 + 0.5f; y < Y2; y++) {
|
|
||||||
int X0 = (int) ((y - y1) / (y2 - y1) * (x2 - x1) + x1 + 0.5f);
|
|
||||||
int X1 = (int) ((y - y0) / (y2 - y0) * (x2 - x0) + x0 + 0.5f);
|
|
||||||
if (X0 > X1) std::swap(X0, X1);
|
|
||||||
if (X0 < 0) X0 = 0;
|
|
||||||
if (X1 > f->width) X1 = f->width;
|
|
||||||
for (int x = X0; x < X1; x++) {
|
|
||||||
// f(x + 0.5, y)
|
|
||||||
SetPixel(x, y, shader->getColor(x + 0.5f, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BarycentricInterpolator {
|
|
||||||
float x0, y0, x1, y1, x2, y2, S;
|
|
||||||
COLOR C0, C1, C2;
|
|
||||||
public:
|
|
||||||
BarycentricInterpolator(float _x0, float _y0, float _x1, float _y1, float
|
|
||||||
_x2, float _y2, COLOR A0, COLOR A1, COLOR A2) :
|
|
||||||
x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2),
|
|
||||||
S((_y1 - _y2) * (_x0 - _x2) + (_x2 - _x1) * (_y0 - _y2)), C0(A0), C1(A1),
|
|
||||||
C2(A2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
COLOR getColor(float x, float y) {
|
|
||||||
// Барицентрическая интерполяция
|
// Барицентрическая интерполяция
|
||||||
float h0 = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / S;
|
float h0 = ((y1 - y2) * (x - x2) + (x2 - x1) * (y - y2)) / S;
|
||||||
float h1 = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / S;
|
float h1 = ((y2 - y0) * (x - x2) + (x0 - x2) * (y - y2)) / S;
|
||||||
float h2 = 1 - h0 - h1;
|
float h2 = 1 - h0 - h1;
|
||||||
float r = h0 * C0.red + h1 * C1.red + h2 * C2.red;
|
float r = h0 * C0.red + h1 * C1.red + h2 * C2.red;
|
||||||
float g = h0 * C0.green + h1 * C1.green + h2 * C2.green;
|
float g = h0 * C0.green + h1 * C1.green + h2 * C2.green;
|
||||||
float b = h0 * C0.blue + h1 * C1.blue + h2 * C2.blue;
|
float b = h0 * C0.blue + h1 * C1.blue + h2 * C2.blue;
|
||||||
float a = h0 * C0.alpha + h1 * C1.alpha + h2 * C2.alpha;
|
float a = h0 * C0.alpha + h1 * C1.alpha + h2 * C2.alpha;
|
||||||
return COLOR(r, g, b, a);
|
return COLOR(r, g, b, a);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class RadialBrush {
|
|
||||||
float cx, cy; // Центр прямоугольника
|
|
||||||
COLOR C0, C1; // Цвета радиальной заливки
|
|
||||||
float angle; // Начальный угол заливки
|
|
||||||
public:
|
|
||||||
RadialBrush(float _x0, float _y0, float _x1, float _y1, COLOR A0, COLOR A1, float
|
|
||||||
_angle) :
|
|
||||||
cx((_x0 + _x1) / 2.0f), cy((_y0 + _y1) / 2.0f),
|
|
||||||
C0(A0), C1(A1), angle(_angle) {
|
|
||||||
}
|
|
||||||
|
|
||||||
COLOR getColor(float x, float y) {
|
|
||||||
double dx = (double) x - cx, dy = (double) y - cy;
|
|
||||||
double radius = sqrt(dx * dx + dy * dy);
|
|
||||||
float h0 = (sin(radius / 10 + angle) + 1.0f) / 2;
|
|
||||||
float h1 = 1 - h0;
|
|
||||||
float r = h0 * C0.red + h1 * C1.red;
|
|
||||||
float g = h0 * C0.green + h1 * C1.green;
|
|
||||||
float b = h0 * C0.blue + h1 * C1.blue;
|
|
||||||
return COLOR(r, g, b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
COLOR ColorFromHSV(double hue, double saturation, double value) {
|
|
||||||
int hi = int(floor(hue / 60)) % 6;
|
|
||||||
double f = hue / 60 - floor(hue / 60);
|
|
||||||
value = value * 255;
|
|
||||||
const auto v = (unsigned char) (value);
|
|
||||||
const auto p = (unsigned char) (value * (1 - saturation));
|
|
||||||
const auto q = (unsigned char) (value * (1 - f * saturation));
|
|
||||||
const auto t = (unsigned char) (value * (1 - (1 - f) * saturation));
|
|
||||||
switch (hi) {
|
|
||||||
case 0:
|
|
||||||
return COLOR{v, t, p};
|
|
||||||
case 1:
|
|
||||||
return COLOR{q, v, p};
|
|
||||||
case 2:
|
|
||||||
return COLOR{p, v, t};
|
|
||||||
case 3:
|
|
||||||
return COLOR{p, q, v};
|
|
||||||
case 4:
|
|
||||||
return COLOR{t, p, v};
|
|
||||||
default:
|
|
||||||
return COLOR{v, p, q};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
COLOR RadialBrush::get_color(int x, int y) const {
|
||||||
|
double dx = (double) x - cx, dy = (double) y - cy;
|
||||||
|
double radius = sqrt(dx * dx + dy * dy);
|
||||||
|
float h0 = (sin(radius / 10 + angle) + 1.0f) / 2;
|
||||||
|
float h1 = 1 - h0;
|
||||||
|
float r = h0 * C0.red + h1 * C1.red;
|
||||||
|
float g = h0 * C0.green + h1 * C1.green;
|
||||||
|
float b = h0 * C0.blue + h1 * C1.blue;
|
||||||
|
return COLOR(r, g, b);
|
||||||
|
}
|
35
src/shaders.hpp
Normal file
35
src/shaders.hpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "painter.hpp"
|
||||||
|
#include "drawing_assets.hpp"
|
||||||
|
|
||||||
|
class BarycentricInterpolator : public Shader {
|
||||||
|
private:
|
||||||
|
float x0, y0, x1, y1, x2, y2, S;
|
||||||
|
COLOR C0, C1, C2;
|
||||||
|
public:
|
||||||
|
inline BarycentricInterpolator(float _x0, float _y0, float _x1, float _y1, float _x2, float _y2, COLOR A0, COLOR A1, COLOR A2) :
|
||||||
|
x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2),
|
||||||
|
S((_y1 - _y2) * (_x0 - _x2) + (_x2 - _x1) * (_y0 - _y2)),
|
||||||
|
C0(A0), C1(A1), C2(A2) {}
|
||||||
|
|
||||||
|
inline BarycentricInterpolator(Point p0, Point p1, Point p2, COLOR A0, COLOR A1, COLOR A2) :
|
||||||
|
BarycentricInterpolator(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, A0, A1, A2) {}
|
||||||
|
|
||||||
|
COLOR get_color(int x, int y) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class RadialBrush : public Shader {
|
||||||
|
private:
|
||||||
|
float cx, cy;
|
||||||
|
COLOR C0, C1;
|
||||||
|
float angle;
|
||||||
|
public:
|
||||||
|
RadialBrush(float _x0, float _y0, float _x1, float _y1, COLOR A0, COLOR A1, float _angle) :
|
||||||
|
cx((_x0 + _x1) / 2.0f), cy((_y0 + _y1) / 2.0f),
|
||||||
|
C0(A0), C1(A1), angle(_angle) {}
|
||||||
|
|
||||||
|
|
||||||
|
COLOR get_color(int x, int y) const override;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user