Новый пример OpenGL

This commit is contained in:
Sun Serega 2022-03-01 08:21:26 +02:00
parent 28854f2864
commit 6211fe2275
14 changed files with 3850 additions and 1137 deletions

View file

@ -1,244 +1,172 @@
{$reference System.Windows.Forms.dll}
{$reference System.Drawing.dll}
// Данный пример демонстрирует запуск простейшей программы с модулем OpenGL
// Примите во внимание, что методы из gl_gdi. - могут служить только временной заменой в серьёзной программе
// (По крайней мере в данной версии. В будущем, возможно, gl_gdi будет улучшено)
// Они использованы тут, чтоб пример был проще
uses System.Windows.Forms;
uses System.Drawing;
uses System;
uses OpenGL;
{$apptype windows} // убирает консоль
var gl: OpenGL.gl<PlWin>;
{$region Shader}
function InitShader(fname: string; st: ShaderType): gl_shader;
begin
Result := gl.CreateShader(st);
var source := ReadAllText(fname);
gl.ShaderSource(Result, 1,
new string[](source),
nil
);
gl.CompileShader(Result);
// получаем состояние успешности компиляции
// 1=успешно
// 0=ошибка
var comp_ok: integer;
gl.GetShaderiv(Result, ShaderParameterName.COMPILE_STATUS, comp_ok);
if comp_ok = 0 then
begin
// узнаём нужную длинную строки
var l: integer;
gl.GetShaderiv(Result, ShaderParameterName.INFO_LOG_LENGTH, l);
// выделяем достаточно памяти чтоб сохранить строку
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
// получаем строку логов
gl.GetShaderInfoLog(Result, l, IntPtr.Zero, ptr);
// преобразовываем в управляемую строку
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
Writeln(log);
// и в конце обязательно освобождаем, чтобы не было утечек памяти
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
gl.DeleteShader(Result);
Result := gl_shader.Zero;
end;
end;
{$endregion Shader}
{$region Program}
function InitProgram(vertex_shader, fragment_shader: gl_shader): gl_program;
begin
Result := gl.CreateProgram;
gl.AttachShader(Result, vertex_shader);
if fragment_shader<>gl_shader.Zero then gl.AttachShader(Result, fragment_shader);
gl.LinkProgram(Result);
// всё то же самое что и у шейдеров
var link_ok: integer;
gl.GetProgramiv(Result, ProgramPropertyARB.LINK_STATUS, link_ok);
if link_ok = 0 then
begin
var l: integer;
gl.GetProgramiv(Result, ProgramPropertyARB.INFO_LOG_LENGTH, l);
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
gl.GetProgramInfoLog(Result, l, IntPtr.Zero, ptr);
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
Writeln(log);
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
gl.DeleteProgram(Result);
Result := gl_program.Zero;
end;
end;
{$endregion Program}
const dy = -Sin(Pi / 6) / 2;
begin
// Создаёт и настраиваем окно
var f := new Form;
f.StartPosition := FormStartPosition.CenterScreen;
f.ClientSize := new Size(500, 500);
f.FormBorderStyle := FormBorderStyle.Fixed3D;
// Если окно закрылось - надо сразу завершить программу
f.Closed += (o,e)->Halt();
// Настраиваем поверхность рисования
var hdc := gl_gdi.InitControl(f);
// Настраиваем перерисовку
gl_gdi.SetupControlRedrawing(f, hdc, EndFrame->
begin
{$region Настройка глобальных параметров OpenGL}
// При создании экземпляра OpenGL.gl инициализируются некоторые функции
// Это необходимо для всех функций из OpenGL1.2 и выше, потому что они локальны для контекста OpenGL
gl := new OpenGL.gl<PlWin>;
{$endregion Настройка глобальных параметров OpenGL}
{$region Инициализация переменных}
var vertex_pos_buffer: gl_buffer;
gl.CreateBuffers(1, vertex_pos_buffer);
gl.NamedBufferData(
vertex_pos_buffer,
new IntPtr(3*sizeof(Vec2f)),
ArrGen(3, i->
begin
var rot := i * Pi * 2 / 3;
Result := new Vec2f(
Sin(rot),
Cos(rot) + dy
);
end),
VertexBufferObjectUsage.STATIC_DRAW
);
var vertex_clr_buffer: gl_buffer;
gl.CreateBuffers(1, vertex_clr_buffer);
gl.NamedBufferData(
vertex_clr_buffer,
new IntPtr(3*sizeof(Vec3f)),
new Vec3f[](
new Vec3f(1,0,0),
new Vec3f(0,1,0),
new Vec3f(0,0,1)
),
VertexBufferObjectUsage.STATIC_DRAW
);
var element_buffer: gl_buffer;
gl.CreateBuffers(1, element_buffer);
gl.NamedBufferData(
element_buffer,
new IntPtr(3*sizeof(byte)),
new byte[](
0,1,2
),
VertexBufferObjectUsage.STATIC_DRAW
);
var vertex_shader := InitShader('Rot Triangle 1.vertex.glsl', ShaderType.VERTEX_SHADER);
// var fragment_shader := InitShader('fragment.glsl', ShaderType.FRAGMENT_SHADER);
var sprog := InitProgram(vertex_shader, gl_shader.Zero {fragment_shader});
var uniform_rot_k := gl.GetUniformLocation(sprog, 'rot_k');
var attribute_position := gl.GetAttribLocation(sprog, 'position');
var attribute_color := gl.GetAttribLocation(sprog, 'color');
var t := new Stopwatch;
t.Start;
{$endregion Инициализация переменных}
while true do
begin
// очищаем окно в начале перерисовки
gl.Clear(ClearBufferMask.COLOR_BUFFER_BIT);
gl.UseProgram(sprog);
gl.Uniform1f(uniform_rot_k, Cos( t.Elapsed.Ticks * 0.0000002 ) );
gl.BindBuffer(BufferTargetARB.ARRAY_BUFFER, vertex_pos_buffer);
gl.VertexAttribPointer(
attribute_position,
2,
VertexAttribPointerType.FLOAT,
false,
8,
IntPtr.Zero
);
gl.EnableVertexAttribArray(attribute_position);
gl.BindBuffer(BufferTargetARB.ARRAY_BUFFER, vertex_clr_buffer);
gl.VertexAttribPointer(
attribute_color,
3,
VertexAttribPointerType.FLOAT,
false,
12,
IntPtr.Zero
);
gl.EnableVertexAttribArray(attribute_color);
gl.BindBuffer(BufferTargetARB.ELEMENT_ARRAY_BUFFER, element_buffer);
gl.DrawElements(
PrimitiveType.TRIANGLES,
3,
DrawElementsType.UNSIGNED_BYTE,
IntPtr.Zero
);
gl.DisableVertexAttribArray(attribute_position);
gl.DisableVertexAttribArray(attribute_color);
// получаем тип последней ошибки
var err := gl.GetError;
// и если ошибка есть - выводим её
if err<>ErrorCode.NO_ERROR then Writeln(err);
gl.Finish;
// EndFrame меняет местами буферы и ждёт vsync
EndFrame;
end;
end);
Application.Run(f);
{$reference System.Windows.Forms.dll}
{$reference System.Drawing.dll}
// Данный пример демонстрирует запуск простейшей программы с модулем OpenGL
// Примите во внимание, что методы из gl_gdi. - могут служить только временной заменой в серьёзной программе
// (По крайней мере в данной версии. В будущем, возможно, gl_gdi будет улучшено)
// Они использованы тут, чтоб пример был проще
uses System.Windows.Forms;
uses System;
uses OpenGL;
uses Common in '..\Common';
{$apptype windows} // убираем консоль
const dy = -Sin(Pi / 6) / 2;
begin
// Создаём и настраиваем окно
var f := new Form;
f.StartPosition := FormStartPosition.CenterScreen;
f.ClientSize := new System.Drawing.Size(500, 500);
f.FormBorderStyle := FormBorderStyle.Fixed3D;
// Если окно закрылось - надо сразу завершить программу
f.Closed += (o,e)->Halt();
// Настраиваем поверхность рисования
var hdc := gl_gdi.InitControl(f);
// Настраиваем перерисовку
gl_gdi.SetupControlRedrawing(f, hdc, EndFrame->
begin
{$region Настройка глобальных параметров OpenGL}
// При создании экземпляра OpenGL.gl инициализируются некоторые функции
// Это необходимо для всех функций из OpenGL1.2 и выше, потому что они локальны для контекста OpenGL
gl := new OpenGL.gl<PlWin>;
{$endregion Настройка глобальных параметров OpenGL}
{$region Инициализация переменных}
var vertex_pos_buffer: gl_buffer;
gl.CreateBuffers(1, vertex_pos_buffer);
gl.NamedBufferData(
vertex_pos_buffer,
new IntPtr(3*sizeof(Vec2f)),
ArrGen(3, i->
begin
var rot := i * Pi * 2 / 3;
Result := new Vec2f(
Sin(rot),
Cos(rot) + dy
);
end),
VertexBufferObjectUsage.STATIC_DRAW
);
var vertex_clr_buffer: gl_buffer;
gl.CreateBuffers(1, vertex_clr_buffer);
gl.NamedBufferData(
vertex_clr_buffer,
new IntPtr(3*sizeof(Vec3f)),
new Vec3f[](
new Vec3f(1,0,0),
new Vec3f(0,1,0),
new Vec3f(0,0,1)
),
VertexBufferObjectUsage.STATIC_DRAW
);
var element_buffer: gl_buffer;
gl.CreateBuffers(1, element_buffer);
gl.NamedBufferData(
element_buffer,
new IntPtr(3*sizeof(byte)),
new byte[](
0,1,2
),
VertexBufferObjectUsage.STATIC_DRAW
);
var vertex_shader := InitShader('Крутящийся треугольник.vert', ShaderType.VERTEX_SHADER);
var sprog := InitProgram(vertex_shader{, другиеейдеры});
var uniform_rot_k := gl.GetUniformLocation(sprog, 'rot_k');
var attribute_position := gl.GetAttribLocation(sprog, 'position');
var attribute_color := gl.GetAttribLocation(sprog, 'color');
var t := new Stopwatch;
t.Start;
{$endregion Инициализация переменных}
// Для поиска, где возникла ошибка
// var RaiseIfError := procedure->
// begin
//
// // получаем тип последней ошибки
// var err := gl.GetError;
// // и если ошибка есть - выводим её
// if err<>ErrorCode.NO_ERROR then raise new System.Exception(err.ToString);
//
// end;
while true do
begin
// очищаем окно в начале перерисовки
gl.Clear(ClearBufferMask.COLOR_BUFFER_BIT);
gl.UseProgram(sprog);
gl.Uniform1f(uniform_rot_k, Cos( t.Elapsed.Ticks * 0.0000002 ) );
gl.BindBuffer(BufferTarget.ARRAY_BUFFER, vertex_pos_buffer);
gl.VertexAttribPointer(
attribute_position,
2,
VertexAttribPointerType.FLOAT,
false,
8,
IntPtr.Zero
);
gl.EnableVertexAttribArray(attribute_position);
gl.BindBuffer(BufferTarget.ARRAY_BUFFER, vertex_clr_buffer);
gl.VertexAttribPointer(
attribute_color,
3,
VertexAttribPointerType.FLOAT,
false,
12,
IntPtr.Zero
);
gl.EnableVertexAttribArray(attribute_color);
gl.BindBuffer(BufferTarget.ELEMENT_ARRAY_BUFFER, element_buffer);
gl.DrawElements(
PrimitiveType.TRIANGLES,
3,
DrawElementsType.UNSIGNED_BYTE,
IntPtr.Zero
);
gl.DisableVertexAttribArray(attribute_position);
gl.DisableVertexAttribArray(attribute_color);
// получаем тип последней ошибки
var err := gl.GetError;
// и если ошибка есть - выводим её
if err<>ErrorCode.NO_ERROR then Writeln(err);
gl.Finish;
// EndFrame меняет местами буферы и ждёт vsync
EndFrame;
end;
end);
Application.Run(f);
end.

View file

@ -1,7 +1,7 @@
#version 460
attribute vec2 position;
attribute vec3 color;
in vec2 position;
in vec3 color;
uniform float rot_k;
void main()

View file

@ -0,0 +1,87 @@
unit Common;
uses System;
uses OpenGL;
var gl: OpenGL.gl<PlWin>;
{$region Shader}
function InitShader(fname: string; st: ShaderType): gl_shader;
begin
Result := gl.CreateShader(st);
var source := ReadAllText(fname);
gl.ShaderSource(Result, 1,
new string[](source),
nil
);
gl.CompileShader(Result);
// Получаем состояние успешности компиляции
// 1=успешно
// 0=ошибка
var comp_ok: integer;
gl.GetShaderiv(Result, ShaderParameterName.COMPILE_STATUS, comp_ok);
if comp_ok = 0 then
begin
// Узнаём нужную длинную строки
var l: integer;
gl.GetShaderiv(Result, ShaderParameterName.INFO_LOG_LENGTH, l);
// Выделяем достаточно памяти чтоб сохранить строку
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
// Получаем строку логов
gl.GetShaderInfoLog(Result, l, IntPtr.Zero, ptr);
// Преобразовываем в управляемую строку
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
// И в конце обязательно освобождаем, чтобы не было утечек памяти
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
gl.DeleteShader(Result);
raise new System.ArgumentException($'{fname}:{#10}{log}');
end;
end;
{$endregion Shader}
{$region Program}
function InitProgram(params shaders: array of gl_shader): gl_program;
begin
Result := gl.CreateProgram;
foreach var sh in shaders do
gl.AttachShader(Result, sh);
gl.LinkProgram(Result);
// Всё то же самое что и у шейдеров
var link_ok: integer;
gl.GetProgramiv(Result, ProgramPropertyARB.LINK_STATUS, link_ok);
if link_ok = 0 then
begin
var l: integer;
gl.GetProgramiv(Result, ProgramPropertyARB.INFO_LOG_LENGTH, l);
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
gl.GetProgramInfoLog(Result, l, IntPtr.Zero, ptr);
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
gl.DeleteProgram(Result);
raise new System.ArgumentException(log);
end;
end;
{$endregion Program}
end.

View file

@ -0,0 +1,5 @@
#version 460 core
// Ничего не делаем
// Какую точку передали - такую и вернёт
void main() { }

View file

@ -0,0 +1,97 @@
#version 460 core
// Подсматривал сюда:
// https://stackoverflow.com/questions/44354589/optimizing-mandelbrot-fractal
noperspective in vec2 logic_pos;
layout(location = 0) uniform int point_count;
layout(binding = 0) buffer points_in {
dvec2 data[];
} points;
layout(location = 1) uniform double camera_aspect_ratio;
layout(location = 2) uniform double camera_scale;
layout(location = 3) uniform dvec2 camera_pos;
/**
layout(binding = 1) buffer temp_otp {
dvec2 data[3];
} temp;
/**/
out vec3 color;
// Радиус белой точки
// (хотел сделать взаимодействие с основным рисунком, но и так лагает)
const float point_r = 5;
// Скорость затемнения при отдалении от точки
const float fall_speed = 0.6;
const float extra_scale = 0.01;
const int MAX_DEPTH = 128;
int CalculateMandelbrotDepth(dvec2 c, dvec2 start) {
c *= extra_scale;
//start *= extra_scale;
if (dot(start,start)>2) return 0;
dvec2 prev[MAX_DEPTH];
dvec2 curr = start;
for (int depth = 1; depth < MAX_DEPTH; ++depth) {
prev[depth-1] = curr;
curr = dvec2( curr.x*curr.x - curr.y*curr.y, 2*curr.x*curr.y )+c;
if (dot(curr,curr)>2) return depth;
for (int i = depth-1; i>=0; --i)
if (curr == prev[i]) return depth;
}
return MAX_DEPTH;
}
// All components are in the range [0…1], including hue.
vec3 hsv2rgb(float hue)
{
vec3 c = {hue, 0.75, 0.5};
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
// Считаем в фрагментном шейдере, потому что интерполяция
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
pos *= camera_scale;
pos += camera_pos;
for (int i=0; i<point_count; ++i) {
dvec2 dp = pos-points.data[i];
if (dot(dp,dp)<point_r*point_r) {
color = vec3(1);
return;
}
}
int depth = CalculateMandelbrotDepth(pos, dvec2(0));
/**
for (int i=0; i<point_count; ++i) {
//TODO Какое-то взаимодействие
}
/**/
if (depth == 0)
color = vec3(0); else
{
//color = vec3( float(depth) / MAX_DEPTH ); // Чёрно-белый рисунок
//color = hsv2rgb( float(depth) / MAX_DEPTH * 0.8 + 0.1 ); // Все цвета радуги
color = hsv2rgb( fract(float(depth) / MAX_DEPTH*radians(180)) ); // Псевдо-случайные цвета
}
}

View file

@ -0,0 +1,23 @@
#version 460 core
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
out vec2 logic_pos;
void SendVertex(float coord1, float coord2) {
logic_pos = vec2(coord1, coord2);
gl_Position = vec4(coord1, coord2, 0, 1);
EmitVertex();
}
// На вход приходит одна точка без данных
// На выход посылаем 2 треугольника, покрывающих весь экран
// При чём передаём логическую позицию на экране в виде logic_pos
// В фрагментном шейдере значения logic_pos интерполируются с noperspective
void main() {
SendVertex(-1,-1);
SendVertex(-1,+1);
SendVertex(+1,-1);
SendVertex(+1,+1);
EndPrimitive();
}

View file

@ -0,0 +1,55 @@
#version 460 core
noperspective in vec2 logic_pos;
layout(location = 0) uniform int point_count;
layout(binding = 0) buffer points_in {
dvec2 data[];
} points;
layout(location = 1) uniform double camera_aspect_ratio;
layout(location = 2) uniform double camera_scale;
layout(location = 3) uniform dvec2 camera_pos;
/**
layout(binding = 1) buffer temp_otp {
dvec2 data[3];
} temp;
/**/
out vec3 color;
// Радиус красной точки
const float point_r = 5;
// Скорость затемнения при отдалении от точки
const float fall_speed = 0.6;
void main() {
// Считаем в фрагментном шейдере, потому что интерполяция
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
pos *= camera_scale;
pos += camera_pos;
for (int i=0; i<point_count; ++i) {
dvec2 dp = pos-points.data[i];
if (dot(dp,dp)<point_r*point_r) {
color = vec3(0.7, 0, 0);
return;
}
}
double res = 0;
for (int i=0; i<point_count; ++i) {
double curr = distance(pos, points.data[i]) / 10;
curr = fract(curr);
res += curr;
}
res /= point_count;
//res = fract(res);
color = vec3( res );
}

View file

@ -0,0 +1,62 @@
#version 460 core
noperspective in vec2 logic_pos;
layout(location = 0) uniform int point_count;
layout(binding = 0) buffer points_in {
dvec2 data[];
} points;
layout(location = 1) uniform double camera_aspect_ratio;
layout(location = 2) uniform double camera_scale;
layout(location = 3) uniform dvec2 camera_pos;
layout(binding = 1) buffer temp_otp {
dvec2 data[3];
} temp;
out vec3 color;
// Радиус красной точки
const float point_r = 5;
const float point_r_sqr = point_r*point_r;
// Скорость затемнения при отдалении от точки
const float fall_speed = 0.3;
void main() {
// Считаем в фрагментном шейдере, потому что интерполяция
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
pos *= camera_scale;
pos += camera_pos;
// Храним и сравниваем квадраты расстояний,
// чтобы не считать корень лишний раз - он дорогой
double min_r_sqr = double(1)/0;
for (int i=0; i<point_count; ++i) {
dvec2 dp = pos - points.data[i];
double r_sqr = dot(dp, dp);
if (r_sqr<point_r_sqr) {
color = vec3(0.7, 0, 0);
return;
}
if (r_sqr<min_r_sqr) min_r_sqr = r_sqr;
}
// Функция формы y=1/x, но маштабированная чтобы y(point_r)=1
double fall_sqr = point_r_sqr/min_r_sqr;
// И в данном случае результат в квадрате, поэтому дальше степень делим на 2
float fall = pow(float(fall_sqr), fall_speed/2);
// Ступеньки из оттенков серого
if (false) {
int color_count = 64;
fall = round(fall*color_count) / color_count;
}
color = vec3( fall );
}

View file

@ -0,0 +1,65 @@
#version 460 core
noperspective in vec2 logic_pos;
layout(location = 0) uniform int point_count;
layout(binding = 0) buffer points_in {
dvec2 data[];
} points;
layout(location = 1) uniform double camera_aspect_ratio;
layout(location = 2) uniform double camera_scale;
layout(location = 3) uniform dvec2 camera_pos;
/**
layout(binding = 1) buffer temp_otp {
double data[3];
} temp;
/**/
out vec3 color;
const int arm_count = 5;
void main() {
// Считаем в фрагментном шейдере, потому что интерполяция
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
pos *= camera_scale;
pos += camera_pos;
double total_ang = 0;
double total_r = 0;
for (int i=0; i<point_count; ++i) {
dvec2 dp = pos - points.data[i];
dp /= point_count;
// Угол от 0 до 1
float angle = atan(
float(dp.y/abs(dp.x)), float(sign(dp.x))
)/radians(360)+0.5;
total_ang += double(angle)*arm_count;
double radius = sqrt(dot(dp,dp));
radius = log2(float(radius));
total_r += radius;
}
//total_r = log2(float(total_r));
double res = total_ang + total_r;
/**
if (distance(pos, dvec2(temp.data[0],temp.data[1])) < camera_scale/500)
temp.data[2] = res;
/**/
//res /= point_count;
res = fract(res);
color = vec3( res );
}

View file

@ -0,0 +1,86 @@
#version 460 core
noperspective in vec2 logic_pos;
layout(location = 0) uniform int point_count;
layout(binding = 0) buffer points_in {
dvec2 data[];
} points;
layout(location = 1) uniform double camera_aspect_ratio;
layout(location = 2) uniform double camera_scale;
layout(location = 3) uniform dvec2 camera_pos;
/**
layout(binding = 1) buffer temp_otp {
dvec2 data[3];
} temp;
/**/
out vec3 color;
// Радиус красной точки
const float point_r = 5;
// Скорость затемнения при отдалении от точки
const float fall_speed = 0.6;
const bool fall_range_debug = false;
void main() {
// В идеале это надо считать в геометрическом шейдере,
// или даже на CPU... Но для этого надо менять код, общий с другими примерами
float min_r_sum = 0;
dvec2 d_center = {0,0};
for (int i=0; i<point_count; ++i) d_center += points.data[i];
d_center /= point_count;
vec2 center = vec2( d_center );
for (int i=0; i<point_count; ++i)
min_r_sum += distance(center, vec2(points.data[i]));
// Считаем в фрагментном шейдере, потому что интерполяция
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
pos *= camera_scale;
pos += camera_pos;
if (fall_range_debug && distance(pos, d_center)<point_r) {
color = vec3(0, 0, 0.7);
return;
}
float r_sum = 0;
for (int i=0; i<point_count; ++i) {
dvec2 dp = pos - points.data[i];
float r = sqrt(float(dot(dp, dp)));
if (r<point_r) {
color = vec3(0.7, 0, 0);
return;
}
r_sum += r;
}
// Функция формы y=1/x, но маштабированная чтобы y(min_r_sum)=1
double d_fall = min_r_sum/r_sum;
float fall = pow(float(d_fall), fall_speed);
// Ступеньки из оттенков серого
if (true) {
int color_count = 64;
fall = round(fall*color_count) / color_count;
}
// center всё же не всегда имеет наименьшую сумму расстояний
// Поэтому min_r_sum может считаться неправильно и fall оказываться >1
// Наверное, нужен итерационный алгоритм на стороне CPU, чтобы адекватно это сделать
// Напишите если есть идеи лучше
if (fall_range_debug && fall>1) {
color = vec3(0.8, 0, 0.8);
return;
}
color = vec3( fall );
}

View file

@ -0,0 +1,285 @@
{$reference System.Windows.Forms.dll}
{$reference System.Drawing.dll}
// Примеры фрагментных шейдеров
//
// ========== Управление ==========
// ЛКМ: Поставить точку
// СКМ: Удалить ближайшую точку
// ПКМ: Схватить и тащить ближайшую точку
// Колёсико мыши: Маштаб
// Пробел: Вернуть камеру
// Цифры от 1 до 5: Переключение шейдеров
uses System.Windows.Forms;
uses System;
uses OpenGL;
uses Common in '..\Common';
{$apptype windows}
type
CameraData = record
aspect := real.NaN;
scale := 500.0;
pos := default(Vec2d);
end;
begin
var f := new Form;
f.WindowState := FormWindowState.Maximized;
// f.ControlBox := false;
// Моментальное закрытие при Alt+F4 и Esc
f.Closed += (o,e)->Halt();
f.KeyUp += (o,e)->
case e.KeyCode of
Keys.Escape: Halt;
end;
var camera := new CameraData;
var need_resize := false;
var ev_resized_render_complete := new System.Threading.AutoResetEvent(false);
f.Shown += (o,e)->
begin
need_resize := true;
f.Resize += (o,e)->
begin
need_resize := true;
// Чтобы не мигало - ждём завершения одной перерисовки
// в потоке формы, то есть блокируя отсыл информации системе
ev_resized_render_complete.WaitOne;
end;
end;
var curr_fragment_shader := 0;
var point_count := 0;
var points := new Vec2d[4];
var mouse_pos := new Vec2d(real.NaN, real.NaN);
{$region Управление}
var points_updated := 0;
begin
var CoordsFromScreen := function(X,Y: integer): Vec2d ->
begin
var logic_pos := new Vec2d(X/f.ClientSize.Width-0.5, 0.5-Y/f.ClientSize.Height)*2;
var pos := new Vec2d(logic_pos.val0*camera.aspect, logic_pos.val1);
Result := pos*camera.scale + camera.pos;
end;
var ClosestPointInd := function(pos: Vec2d): integer ->
begin
Assert(point_count<>0);
var min_r_sqr := (pos-points[0]).SqrLength;
Result := 0;
for var i := 1 to point_count-1 do
begin
var r_sqr := (pos-points[i]).SqrLength;
if r_sqr < min_r_sqr then
begin
min_r_sqr := r_sqr;
Result := i;
end;
end;
end;
f.MouseWheel += (o,e)->
begin
var pos := CoordsFromScreen(e.X, e.Y);
var pow := 1 - Sign(e.Delta)*0.1;
camera.scale := camera.scale * pow;
camera.pos := camera.pos + (pos-CoordsFromScreen(e.X, e.Y));
end;
f.KeyDown += (o,e)->
case e.KeyCode of
Keys.Space:
begin
var n_camera := new CameraData;
camera.scale := n_camera.scale;
camera.pos := n_camera.pos;
end;
Keys.D1..Keys.D5: curr_fragment_shader := integer(e.KeyCode)-integer(Keys.D1);
end;
var grabbed_point_ind := default(integer?);
f.MouseDown += (o,e)->
case e.Button of
MouseButtons.Left:
begin
if point_count=points.Length then
// По хорошему буфер OpenGL тоже надо ресайзить
// Но сейчас он полностью пересоздаётся
SetLength(points, points.Length*2);
points[point_count] := CoordsFromScreen(e.X,e.Y);
point_count += 1;
System.Threading.Interlocked.Exchange(points_updated, 1);
end;
MouseButtons.Middle:
if point_count<>0 then
begin
var pos := CoordsFromScreen(e.X,e.Y);
var ind := ClosestPointInd(pos);
point_count -= 1;
if ind<>point_count then points[ind] := points[point_count];
System.Threading.Interlocked.Exchange(points_updated, 1);
end;
MouseButtons.Right:
if point_count<>0 then
begin
var pos := CoordsFromScreen(e.X,e.Y);
var ind := ClosestPointInd(pos);
grabbed_point_ind := ind;
points[ind] := pos;
System.Threading.Interlocked.Exchange(points_updated, 1);
end;
end;
f.MouseMove += (o,e)->
begin
var pos := CoordsFromScreen(e.X,e.Y);
mouse_pos := pos;
if grabbed_point_ind<>nil then
begin
points[grabbed_point_ind.Value] := pos;
System.Threading.Interlocked.Exchange(points_updated, 1);
end;
end;
f.MouseUp += (o,e)->
case e.Button of
MouseButtons.Right:
if grabbed_point_ind<>nil then
begin
points[grabbed_point_ind.Value] := CoordsFromScreen(e.X,e.Y);
grabbed_point_ind := nil;
System.Threading.Interlocked.Exchange(points_updated, 1);
end;
end;
end;
{$endregion Управление}
gl_gdi.SetupControlRedrawing(f, gl_gdi.InitControl(f), EndFrame->
try
gl := new OpenGL.gl<PlWin>;
var sprog: gl_program;
var last_fragment_shader := -1;
var uniform_point_count := 0;
var buffer_points: gl_buffer;
gl.CreateBuffers(1, buffer_points);
gl.BindBufferBase(BufferTarget.SHADER_STORAGE_BUFFER, 0, buffer_points);
var uniform_aspect := 1;
var uniform_scale := 2;
var uniform_pos := 3;
// Для дебага
// var buffer_temp: gl_buffer;
// gl.CreateBuffers(1, buffer_temp);
// gl.NamedBufferData(buffer_temp, new IntPtr(3*sizeof(real)), IntPtr.Zero, VertexBufferObjectUsage.DYNAMIC_READ);
// gl.BindBufferBase(BufferTarget.SHADER_STORAGE_BUFFER, 1, buffer_temp);
var timings_max_count := 30;
var timings := new Queue<TimeSpan>(timings_max_count);
var total_time := TimeSpan.Zero;
var sw := new Stopwatch;
while true do
begin
var need_set_resize_ev := false;
if need_resize then
begin
var w_size := f.ClientSize;
gl.Viewport(0,0, w_size.Width,w_size.Height);
need_set_resize_ev := true;
camera.aspect := w_size.Width/w_size.Height;
end;
if last_fragment_shader<>curr_fragment_shader then
begin
last_fragment_shader := curr_fragment_shader;
gl.DeleteProgram(sprog);
sprog := InitProgram(
InitShader('Empty.vert', ShaderType.VERTEX_SHADER),
InitShader('SinglePointToScreen.geom', ShaderType.GEOMETRY_SHADER),
InitShader(|
'Минимум расстояний.frag',
'Сумма расстояний.frag',
'Волны.frag',
'Спирали.frag',
'Mandelbrot.frag'
|[last_fragment_shader], ShaderType.FRAGMENT_SHADER)
);
// Применяем вне цикла, потому что это единственная шейдерная программа
gl.UseProgram(sprog);
points_updated := 1;
end;
if System.Threading.Interlocked.Exchange(points_updated, 0)<>0 then
begin
gl.Uniform1i(uniform_point_count, point_count);
// Плохо - пересоздаём всё тело буфера при каждой перерисовке
// Но по сравнению с фрагментным шейдером - это капля в море
gl.NamedBufferData(buffer_points, new IntPtr(point_count*sizeof(vec2d)), points, VertexBufferObjectUsage.DYNAMIC_READ);
// Записать point_count точек из массива можно так:
// gl.NamedBufferSubData(buffer_points, IntPtr.Zero, new IntPtr(point_count * sizeof(Vec2d)), points);
end;
sw.Restart;
// Копируем все данные сразу в локальную переменную
var camera := camera;
// И затем в юниформы
gl.Uniform1d(uniform_aspect, camera.aspect);
gl.Uniform1d(uniform_scale, camera.scale);
gl.Uniform2dv(uniform_pos, 1, camera.pos);
// gl.NamedBufferSubData(buffer_temp, new IntPtr(0*sizeof(real)), new IntPtr(2*sizeof(real)), mouse_pos);
// Вызываем шейдерную программу один раз, не передавая данные
// Геометричейский шейдер сделает из этого
gl.DrawArrays(PrimitiveType.POINTS, 0,1);
// var temp_data := new real[1];
// gl.GetNamedBufferSubData(buffer_temp, new IntPtr(2*sizeof(real)), new IntPtr(1*sizeof(real)), temp_data);
var err := gl.GetError;
if err<>ErrorCode.NO_ERROR then MessageBox.Show(err.ToString);
gl.Finish;
if need_set_resize_ev then
ev_resized_render_complete.Set;
var curr_time := sw.Elapsed;
if timings.Count=timings_max_count then
total_time := total_time-timings.Dequeue;
total_time := total_time+curr_time;
timings += curr_time;
var fps := timings.Count/total_time.TotalSeconds;
var form_text := $'{fps:N2} fps, {1000/fps:N2} mspf, {point_count} points, scale={camera.scale}, pos={camera.pos}';
// form_text := $'temp_data={_ObjectToString(temp_data)}, '+form_text;
f.Text := form_text;
EndFrame;
end;
except
on e: Exception do
begin
MessageBox.Show(e.ToString);
Halt;
end;
end);
System.Windows.Forms.Application.Run(f);
end.

View file

@ -1,12 +1,12 @@

//*****************************************************************************************************\\
// Copyright (©) Cergey Latchenko ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
// Copyright (©) Sergey Latchenko ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
// This code is distributed under the Unlicense
// For details see LICENSE file or this:
// https://github.com/SunSerega/POCGL/blob/master/LICENSE
//*****************************************************************************************************\\
// Copyright (©) Сергей Латченко ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
// Этот код распространяется с лицензией Unlicense
// Данный код распространяется с лицензией Unlicense
// Подробнее в файле LICENSE или тут:
// https://github.com/SunSerega/POCGL/blob/master/LICENSE
//*****************************************************************************************************\\

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff