Pull to refresh

Comments 56

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


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



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

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

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

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

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

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

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

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

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

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

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

UFO landed and left these words here
Он снова сделал это = ему снова удалось.
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