Pull to refresh

Comments 56

Отличная программа. Мы с группой энтузиастов с SQL.RU (много флуда) перевели ее на десяток разных языков программирования, чтобы сравнить вычислительную скорость. Есть проект на гитхабе с результатами.


Один из промежуточных более-менее полных результатов



В результате победила с небольшим перевесом версия на С со вставками SSE4 инструкций.

где питон, где VB?
По незнакомым ссылкам не перехожу.

Но с незнакомцами в интернете всё же разговариваете! :)

Вы знаете питон, vb, но не знаете гитхаб? Оо
Вы их, как Столман — заказывайте по email.
Ох уж эта советская дебильная манера говорить «не умеем переходить?», «проходим», «ноги вытираем»
В ней даже нет и тени попытки быть вежливым с собеседником.
Питон в последней строке, версии на VB еще не было.
Запустил версию на ПХП, работала час, вывела другую картинку, там не «PIXAR» написано.
у нас этот же алгоритм, но были другие исходные данные матрицы.

пых, как и родной питон работали примерно с такой вот «скоростью»
Любопытно будет ли ускорение, если применить typehints на входные и выходные параметры, а ещё pow заменить на две звёздочки. Попробую позже, если не забуду.
А разве Python использует typehints для ускорения? Я что-то такое слышал применительно к Cython, мол они будут использовать стандартные typehints вместо своих типов, но когда-то в будущем. Может быть я ошибаюсь. Напишите, что получилось. Спасибо!
Я про PHP говорил.

Там написано "ПОСОНЫ НЕ КАЧАЙТЕ ТАМ ВИРУС"?)))))))))))

Класс! Удивляет внезапное проседание у C# и такая же внезапная близость Java к скорости C.
Непонятно правда почему был выбран PyPy вместо эталонной реализации?
Жаль что переводчик к русскому языку относится намного небрежнее, чем автор к языку программы.

На ипхоне тоже не отображается

Между тем в оригинале используется HTML-элемент PICTURE с fallback-изображением в формате PNG.
UFO just landed and posted this here
W3C
Не может же быть так, что все изображения работают «просто потому что»
Должен же быть какой-то минимальный список необходимых к поддержке форматов аудио и видео.
В стандарте HTML не определён список обязательных для поддержки форматов изображений, и каждая реализация поддерживает собственный набор форматов.
MDN

Сейчас Firefox Beta показывает WebP, в этом месяце выйдет стабильная версия с его поддержкой.

В этом способе из источников испускаются тысячи лучей на пиксель и программа следит за ними, надеясь, что он найдут источник освещения.

Поясните плз. Из источника в источник?

Из камеры (точки, из которой смотрит зритель).

Он снова сделал это = ему снова удалось.
Vantablack лакировать? Серьезно? Вы, наверное перепутали с Varnish black.
Титанический труд по обрезанию кода.
Чисто теоретически, его можно запустить на мк с внешней памятью?

А можно покрутить сцену? Интересно, что наверху и с другой стороны :)

Там лишь стены и решетка, камера в углу — так что смотреть особо не на что.


Гифки под спойлером




(Извиняюсь за размеры и fps. Первую гифку habrastorage не пропускает, пришлось ужимать. А вторую и третью надоело ждать пока отрендерятся.)

Отправил автору оригинального текста письмо с косяками в его тексте.
Посмотрим как он ответит.
Косяки следующие:
Функция testSphere() возвращает -1 для точки A (то есть она снаружи) и 1 для B (то есть она внутри). Знаки у расстояний — это просто трюк, позволяющий получить два элемента информации вместо одного в случае одного значения. Подобный тип функции можно написать и для описания параллелограмма (именно это и выполняется в функции function BoxTest).

Судя по коду и комменту к нему, это всё же расстояние до сферы, если быть точнее, до её поверхности.
// Signed distance point(p) to sphere(c,r)
  float testSphere(Vec p, Vec c, float r) {
    Vec delta = c - p;
    float distance = sqrtf(delta%delta);
    return radius - distance;
  }  

Ну и наконец о том, что тут коммент лучше названия:
// Sample the world using Signed Distance Fields.
float QueryDatabase(Vec position, int &hitType) {

Ибо это действительно функция которая считает Distance Field.
А ещё, есть сильные подозрения что…
В коде нет вершин. Всё выполняется с помощью функций CSG.

Что CSG тут не причём. А всё выполняется с помощью SDF функций.
По поводу testSphere() каюсь что затупил. Причиной тому стало: -1, 1, слова про две информации, и наконец что знак это просто трюк. Почему-то понял это как два возможных значения. Просто точки A и B в примере неудачные, что значения такие получились.
Знак при этом вовсе не трюк, а побочный эффект простой реализации функции. Кроме того он играет роль при комбинировании моделей.

Просто картинка при 4096 сэмплах, не зря же ноут 10 часов мне воздух грел :)
Хотя я от картинки с 2048 сэмплами из статьи разницы не увидел


Тыц

image

Отличается. Причем в лучшую сторону. Но 10 часов. У меня сейчас 2048, FullHD делается.
Я не настоящий сварщик, но может кто-нибудь про этот момент рассказать или ссылку дать:
char letters[15*4+1] =               // 15 two points lines
          "5O5_" "5W9W" "5_9_"         // P (without curve)
          "AOEO" "COC_" "A_E_"         // I
          "IOQ_" "I_QO"                // X
          "UOY_" "Y_]O" "WW[W"         // A
          "aOa_" "aWeW" "a_e_" "cWiO"; // R (without curve)

Сами знаете какое слово хочется написать.

Коды символов используются как координаты. Можно заменить на массив целых. x0 y0 — начало линии и x1 y1 — конец.


int letters[15*4+1] = {
  // (x0, y0), (x1, y1)
  // P (without curve)
  53, 79, 53, 95,
  53, 87, 57, 87,
  53, 95, 57, 95,
  // I
  65, 79, 69, 79,
  67, 79, 67, 95,
  65, 95, 69, 95,
  // X
  73, 79, 81, 95,
  73, 95, 81, 79,
  // A
  85, 79, 89, 95,
  89, 95, 93, 79,
  87, 87, 91, 87,
  // R (without curve)
  97, 79, 97, 95,
  97, 87, 101, 87,
  97, 95, 101, 95,
  99, 87, 105, 79
};

Вот здесь можете экспериментировать:
https://codepen.io/anon/pen/bOYjNJ

Там же в комментариях написано without curve. Дугу из линий не построить, они дорисовываются вот тут:


// Two curves (for P and R in PixaR) with hard-coded locations.
  Vec curves[] = {Vec(-11, 6), Vec(11, 6)};
  for (int i = 2; i--;) {
    Vec o = f + curves[i] * -1;
    distance = min(distance,
                   o.x > 0 ? fabsf(sqrtf(o % o) - 2)
                           : (o.y += o.y > 0 ? -2 : 2, sqrtf(o % o))
               );
  }
Это понятно, но где ноль? Какие-бы координаты не вводил, получается фигня. Я даже думал об относительных координатах, но и они не работают.

x от 53 до 105 (слева направо)
y от 79 до 95 (снизу вверх)


53, 79, 105, 79, // линия у пола
53, 95, 105, 95, // верх надписи

Ну да, чем ещё заниматься 1го января русскому программисту ^_^

Портировать это на 1С? =)

Держите на Rust:


Cargo.toml
name = "pixar"
version = "0.1.0"
authors = ["AndrewKensler <xxx@xxx.xx>"]
edition = "2018"

[dependencies]
rand = "*"
png = "*"
indicatif = "*"
num_cpus = "*"

[profile.dev]
opt-level = 3

main.rs
const IS_HABR: bool = false;
const WIDTH: u32 = 960;
const HEIGHT: u32 = 540;
const SAMPLES_POW: u32 = 4;
const BOUNCE_COUNT: u32 = 3;

const HIT_NONE: u8 = 0;
const HIT_LETTER: u8 = 1;
const HIT_WALL: u8 = 2;
const HIT_SUN: u8 = 3;

#[derive(Copy, Clone)]
struct Vec3 {
    x: f64,
    y: f64,
    z: f64,
}

impl Vec3 {
    fn new(x: f64, y: f64, z: f64) -> Vec3 {
        Vec3 { x, y, z }
    }
    fn repeat(v: f64) -> Vec3 {
        Vec3::new(v, v, v)
    }
    fn zeros() -> Vec3 {
        Vec3::repeat(0.)
    }
    #[allow(dead_code)]
    fn rotate_x(self, c: f64) -> Vec3 {
        Vec3::new(
            self.x,
            self.y * c.cos() - self.z * c.sin(),
            self.y * c.sin() + self.z * c.cos(),
        )
    }
    #[allow(dead_code)]
    fn rotate_y(self, c: f64) -> Vec3 {
        Vec3::new(
            self.z * c.sin() + self.x * c.cos(),
            self.y,
            self.z * c.cos() - self.x * c.sin(),
        )
    }
    #[allow(dead_code)]
    fn rotate_z(self, c: f64) -> Vec3 {
        Vec3::new(
            self.x * c.cos() - self.y * c.sin(),
            self.x * c.sin() + self.y * c.cos(),
            self.z,
        )
    }
}

use ::std::ops::{Add, Mul, Not, Rem};

impl Add for Vec3 {
    type Output = Vec3;
    fn add(self, other: Vec3) -> Vec3 {
        Vec3 {
            x: self.x + other.x,
            y: self.y + other.y,
            z: self.z + other.z,
        }
    }
}
impl Add<f64> for Vec3 {
    type Output = Vec3;
    fn add(self, other: f64) -> Vec3 {
        Vec3 {
            x: self.x + other,
            y: self.y + other,
            z: self.z + other,
        }
    }
}
impl Mul for Vec3 {
    type Output = Vec3;
    fn mul(self, other: Vec3) -> Vec3 {
        Vec3 {
            x: self.x * other.x,
            y: self.y * other.y,
            z: self.z * other.z,
        }
    }
}
impl Mul<f64> for Vec3 {
    type Output = Vec3;
    fn mul(self, other: f64) -> Vec3 {
        Vec3 {
            x: self.x * other,
            y: self.y * other,
            z: self.z * other,
        }
    }
}
impl Rem for Vec3 {
    type Output = f64;
    fn rem(self, other: Vec3) -> f64 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }
}
impl Not for Vec3 {
    type Output = Vec3;
    fn not(self) -> Vec3 {
        self * (1. / (self % self).sqrt())
    }
}

// Rectangle CSG equation. Returns minimum signed distance from
// space carved by
// lowerLeft vertex and opposite rectangle vertex upperRight.
fn box_test(position: Vec3, lower_left: Vec3, upper_right: Vec3) -> f64 {
    let lower_left = position + lower_left * -1.;
    let upper_right = upper_right + position * -1.;
    -lower_left
        .x
        .min(lower_left.y)
        .min(lower_left.z)
        .min(upper_right.x)
        .min(upper_right.y)
        .min(upper_right.z)
}

fn min(a: f64, b: f64) -> f64 {
    a.min(b)
}

// Sample the world using Signed Distance Fields.
fn query_database(position: Vec3, hit_type: &mut u8) -> f64 {
    let mut distance = 1_000_000_000.0_f64;
    let mut f = position; // Flattened position (z=0)
    f.z = 0.;
    if IS_HABR {
        let letters = [
            // X
            (57, 79, 65, 95),
            (57, 95, 65, 79),
            // A
            (69, 79, 73, 95),
            (73, 95, 77, 79),
            (71, 87, 75, 87),
            // B (without curves)
            (81, 79, 81, 95),
            (81, 79, 85, 79),
            (81, 87, 85, 87),
            (81, 95, 85, 95),
            // R (without curve)
            (93, 79, 93, 95),
            (93, 87, 97, 87),
            (93, 95, 97, 95),
            (95, 87, 101, 79),
        ];

        for letter in letters.iter() {
            let begin = Vec3::new(letter.0 as f64 - 79., letter.1 as f64 - 79., 0.) * 0.5;
            let e = Vec3::new(letter.2 as f64 - 79., letter.3 as f64 - 79., 0.) * 0.5 + begin * -1.;
            let o = f + (begin + e * (-((begin + f * -1.) % e / (e % e)).min(0.)).min(1.)) * -1.;
            distance = distance.min(o % o); // compare squared distance.
        }
    } else {
        let letters = [
            // 15 two points lines
            "5O5_", "5W9W", "5_9_", // P (without curve)
            "AOEO", "COC_", "A_E_", // I
            "IOQ_", "I_QO", // X
            "UOY_", "Y_]O", "WW[W", // A
            "aOa_", "aWeW", "a_e_", "cWiO", // R (without curve)
        ];

        for letter in letters.iter() {
            let points = letter.as_bytes();
            let begin = Vec3::new(points[0] as f64 - 79., points[1] as f64 - 79., 0.) * 0.5;
            let e = Vec3::new(points[2] as f64 - 79., points[3] as f64 - 79., 0.) * 0.5 + begin * -1.;
            let o = f + (begin + e * (-((begin + f * -1.) % e / (e % e)).min(0.)).min(1.)) * -1.;
            distance = distance.min(o % o); // compare squared distance.
        }
    }
    distance = distance.sqrt(); // Get real distance, not square distance.

    // Two curves (for P and R in PixaR) with hard-coded locations.
    let curves = if IS_HABR {
        vec![Vec3::new(3., 6., 0.), Vec3::new(3., 2., 0.), Vec3::new(9., 6., 0.)]
    } else {
        vec![Vec3::new(-11., 6., 0.), Vec3::new(11., 6., 0.)]
    };
    for curve in curves.iter() {
        let mut o = f + *curve * -1.;
        distance = distance.min(if o.x > 0. {
            ((o % o).sqrt() - 2.).abs()
        } else {
            o.y += if o.y > 0. { -2. } else { 2. };
            (o % o).sqrt()
        });
    }
    distance = (distance.powf(8.) + position.z.powf(8.)).powf(0.125) - 0.5;
    *hit_type = HIT_LETTER;

    let room_dist = min(
        // min(A,B) = Union with Constructive solid geometry
        //-min carves an empty space
        -min(
            // Lower room
            box_test(
                position,
                Vec3::new(-30., -0.5, -30.),
                Vec3::new(30., 18., 30.),
            ),
            // Upper room
            box_test(
                position,
                Vec3::new(-25., 17., -25.),
                Vec3::new(25., 20., 25.),
            ),
        ),
        box_test(
            // Ceiling "planks" spaced 8 units apart.
            Vec3::new(position.x.abs() % 8., position.y, position.z),
            Vec3::new(1.5, 18.5, -25.),
            Vec3::new(6.5, 20., 25.),
        ),
    );
    if room_dist < distance {
        distance = room_dist;
        *hit_type = HIT_WALL;
    };

    let sun = 19.9 - position.y; // Everything above 19.9 is light source.
    if sun < distance {
        distance = sun;
        *hit_type = HIT_SUN;
    }

    return distance;
}

fn ray_marching(origin: Vec3, direction: Vec3, hit_pos: &mut Vec3, hit_norm: &mut Vec3) -> u8 {
    let mut hit_type = HIT_NONE;
    let mut no_hit_count = 0;

    // Signed distance marching
    let mut total_d = 0.;
    while total_d < 100. {
        *hit_pos = origin + direction * total_d;
        let d = query_database(*hit_pos, &mut hit_type); // distance from closest object in world.
        if d < 0.01 || no_hit_count > 99 {
            *hit_norm = !Vec3::new(
                query_database(*hit_pos + Vec3::new(0.01, 0., 0.), &mut no_hit_count) - d,
                query_database(*hit_pos + Vec3::new(0., 0.01, 0.), &mut no_hit_count) - d,
                query_database(*hit_pos + Vec3::new(0., 0., 0.01), &mut no_hit_count) - d,
            );
            return hit_type;
        }
        no_hit_count += 1;
        total_d += d;
    }
    return 0;
}

fn trace(origin: Vec3, direction: Vec3) -> Vec3 {
    let mut origin = origin;
    let mut direction = direction;
    let mut sampled_position = Vec3::zeros();
    let mut normal = Vec3::zeros();
    let mut color = Vec3::zeros();
    let mut attenuation = Vec3::repeat(1.);

    let light_direction = !Vec3::new(0.6, 0.6, 1.0);

    let bounce_count = BOUNCE_COUNT;
    for _ in 0..bounce_count {
        let hit_type = ray_marching(origin, direction, &mut sampled_position, &mut normal);
        if hit_type == HIT_NONE {
            break;
        } // No hit. This is over, return color.
        if hit_type == HIT_LETTER {
            // Specular bounce on a letter. No color acc.
            direction = direction + normal * (normal % direction * -2.);
            origin = sampled_position + direction * 0.1;
            attenuation = attenuation * 0.2; // Attenuation via distance traveled.
        }
        if hit_type == HIT_WALL {
            // Wall hit uses color yellow?
            let incidence = normal % light_direction;
            let p = 6.283185 * random_val();
            let c = random_val();
            let s = (1. - c).sqrt();
            let g = if normal.z < 0. { -1. } else { 1. };
            let u = -1. / (g + normal.z);
            let v = normal.x * normal.y * u;
            direction = Vec3::new(v, g + normal.y * normal.y * u, -normal.y) * p.cos() * s
                + Vec3::new(1. + g * normal.x * normal.x * u, g * v, -g * normal.x) * p.sin() * s
                + normal * c.sqrt();
            origin = sampled_position + direction * 0.1;
            attenuation = attenuation * 0.2;
            if incidence > 0.
                && ray_marching(
                    sampled_position + normal * 0.1,
                    light_direction,
                    &mut sampled_position,
                    &mut normal,
                ) == HIT_SUN
            {
                color = color + attenuation * Vec3::new(500., 400., 100.) * incidence;
            }
        }
        if hit_type == HIT_SUN {
            //
            color = color + attenuation * Vec3::new(50., 80., 100.);
            break; // Sun Color
        }
    }
    return color;
}

fn random_val() -> f64 {
    rand::random()
}

fn get_sampling_points(pow: u32) -> Vec<(f64, f64)> {
    let mut points = vec![];
    let max = 1 << pow;
    for a in 0..max {
        let mut b = 0;
        for i in 0..pow {
            let mask_a = 1 << i;
            let bit = (mask_a & a) != 0;
            if bit {
                let mask_b = 1 << (pow - i - 1);
                b = b | mask_b;
            }
        }
        points.push(((a as f64 + 0.5) / max as f64, (b as f64 + 0.5) / max as f64));
    }
    points
}
use std::time::Instant;
use std::thread;

extern crate indicatif;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};

fn main() {
    let w = WIDTH as i32;
    let h = HEIGHT as i32;
    let samples_power = SAMPLES_POW;
    let samples_count = 1 << samples_power;

    let now = Instant::now();
    let m = MultiProgress::new();
    let sty = ProgressStyle::default_bar()
        .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {eta_precise}")
        .progress_chars("=>-");

    let get_promise = move |y0, y1, pb: ProgressBar| -> Vec<u8> {
        let sampling_points = &get_sampling_points(samples_power);
        let position = Vec3::new(-22., 5., 25.);
        let goal = !(Vec3::new(-3., 4., 0.) + position * -1.);

        let left = Vec3::new(goal.z, 0., -goal.x) * (1. / w as f64);
        let up = Vec3::new(
            goal.y * left.z - goal.z * left.y,
            goal.z * left.x - goal.x * left.z,
            goal.x * left.y - goal.y * left.x,
        );

        let mut image_data: Vec<u8> = Vec::with_capacity((w * h * 3) as usize);

        for y in y0..y1 {
            for x in 0..w {
                let x = (w / 2 - x) as f64;
                let y = (h / 2 - y) as f64;
                let mut color = Vec3::zeros();
                for p in sampling_points {
                    let x = x + p.0;
                    let y = y + p.1;
                    color = color + trace(position, !(goal + left * x + up * y));
                }
                color = color * (1. / samples_count as f64) + 14. / 241.;
                let o = color + 1.;
                color = Vec3::new(color.x / o.x, color.y / o.y, color.z / o.z) * 255.;

                image_data.push(color.x.max(0.).min(255.) as u8);
                image_data.push(color.y.max(0.).min(255.) as u8);
                image_data.push(color.z.max(0.).min(255.) as u8);
            }
            pb.inc(1);
        }
        pb.finish_with_message("done");
        image_data
    };

    println!("Rendering...");
    let mut promices = vec![];
    let num_logical_cores = num_cpus::get() as i32;
    for n in 0..num_logical_cores {
        let fragment_size = h / num_logical_cores;
        let y0 = fragment_size * n;
        let y1 = if n == num_logical_cores - 1 {
            h
        } else {
            fragment_size * (n + 1)
        };
        let length = (y1 - y0) as u64;

        let pb = m.add(ProgressBar::new(length));
        pb.set_style(sty.clone());

        let promice = thread::spawn(move || {
            get_promise(y0, y1, pb)
        });

        promices.push(promice);
    }

    m.join_and_clear().unwrap();
    let mut image_data = Vec::new();
    for promice in promices {
        let mut thread_image_data = promice.join().unwrap();
        image_data.append(&mut thread_image_data);
    }

    println!("{} sec", now.elapsed().as_secs());

    let name = format!("{}_{:04}.png", if IS_HABR { "habr" } else { "pixar" }, samples_count);
    let path = std::path::Path::new(&name);
    let file = std::fs::File::create(path).unwrap();
    let ref mut writer_1 = std::io::BufWriter::new(file);

    let mut encoder = png::Encoder::new(writer_1, w as u32, h as u32);
    use png::HasParameters;
    encoder.set(png::ColorType::RGB).set(png::BitDepth::Eight);
    let mut writer = encoder.write_header().unwrap();
    writer.write_image_data(&image_data).unwrap();
}
Например учебник Русского языка почитать. Чтоб 1го не проскакивало. Хотя… да какая разница в новогодние каникулы.
Sign up to leave a comment.

Articles