Comments 65
Кармак пишет 0x5f3759df, я же пишу 1/sqrt(x)И правильно, на современных процессорах и компиляторах такой «хак» уже не нужен.
In terms of C standards, reinterpreting a floating point value as an integer by dereferencing a casted pointer to it is considered undefined behavior
Может кто-нибудь пояснить, почему это стало неопределенным поведением (при условии, что размеры типов совпадают) и в идеале дать ссылку на пункт стандарта?
An object shall have its stored value accessed only by an lvalue expression that has one of the following types — a type compatible with the effective type of the object,
А если в uint8_t* конвертить? Примерно вот так:
double f;
uint8_t *p;
p=(uint8_t*)&f;
§ 6.7.1.8 There are three floating-point types: float, double, and long double. The type double provides at least as much precision as float, and the type long double provides at least as much precision as double. The set of values of the type float is a subset of the set of values of the type double; the set of values of the type double is a subset of the set of values of the type long double. The value representation of floating-point types is implementation-defined.
Можно даже ссылку в комменте оставить как в этой статье. Программисты умные, смогут скопировать в браузер.
А приучать писать тупо и не давать даже узнать о таких хаках, ИМХО, не верно.
Еще я не понял, что такое вот эти магические значения:
ofs << "P6\n" << w << " " << h << "\n255\n";
В этом коммите, что вообще происходит в функции
texture_column
? Почему колонка, где нам рассказывали про колонки? Что-то умножается, делится на два, в итоге получается колонка. Пока не ясно. В последнем коммите, что это за чиселки на 97 строке? Почему они такие, а не любые другие?
0) скажите, пожалуйста, в какой структуре данных вы предлагаете хранить изображение?
1)
Еще я не понял, что такое вот эти магические значения:
ofs << «P6\n» << w << " " << h << "\n255\n";
посмотрите, пожалуйста, сами что это за значения. Я в статье явно указал формат изображения, найти в вики его описание должно быть несложно.
2)
В этом коммите, что вообще происходит в функции texture_column? Почему колонка, где нам рассказывали про колонки? Что-то умножается, делится на два, в итоге получается колонка. Пока не ясно.
Этап 11 довольно подробно рассказывает про колонки. А в функции texture_column на два не делится ничего.
3)
В последнем коммите, что это за чиселки на 97 строке? Почему они такие, а не любые другие?
Ссылку в студию, а то я не понимаю, про какой файл вы говорите.
1) можно и посмотреть. А можно не смотреть и написать прямо тут, если вы и правда для новичков пишете.
2) у меня все равно не получилось связать рассказ с кодом
3) вроде разобрался, это просто данные, а не какое-то преобразование
Покажите мне, пожалуйста, объявление двумерного массива в куче? (кстати да, в куче можно объявить именно двумерный массив, а не массив указателей).
Массив указателей нужно а) инициализировать б) суметь скормить SDL и в) отдать потом обратно операционке. Это сразу десятка два-три строчек кода, которые трудно понимать, которые фрагментируют память и которые практически не решают никакой задачи.
Короче, я лучшего решения не нашёл, если вы найдёте, присылайте пулл реквест.
ssloy@daffodil:~/tmp$ cat test.cpp
#include <cstdint>
int main() {
uint32_t array[ 1920 ][ 1680 ];
return array[344][563];
}
ssloy@daffodil:~/tmp$ g++ -O0 test.cpp -o test && ./test
Segmentation fault
Откуда взялся сегфолт?
Пожалуйста, убедитесь, что вы понимаете, откуда там взялся косинус.
Можно пояснение? )
В любом другом другом состоянии фиолетового луча разница углов будет компенсировать расстояние от текстуру тем самым сглаживая эффект рыбьего глаза.
habr.com/ru/post/439720/#comment_19740388
Однако меня интересует вопрос: Почему в функции unpack_color происходит деление на 255?
r = (color >> 0) & 255;
g = (color >> 8) & 255;
b = (color >> 16) & 255;
a = (color >> 24) & 255;
Поэтому я беру беззнаковый тип, который имеет размер четыре байта, uint32_t. Теперь встаёт вопрос, как конвертировать одно в другое. В одну сторону очень просто:
uint32_t color = r + 256*g + 256*256*b + 256*256*256*a
Почему так? Попробуйте записать каждую компоненту в двоичном виде, и вспомните, что умножение на 256 — это побитовый сдвиг влево на 8 бит. А вот как обратно? Да точно так же. Давайте возьмём чистый зелёный цвет, чему будет равно значение color?
uint32_t color = 0 + 256*255 + 256*256*0 + 256*256*256*0
То есть, 65280. Мы стартуем от этого числа и пытаемся найти значения индивидуальных каналов.
uint8_t r = color % 256;
uint8_t g = (color/256) % 256;
uint8_t b = (color/(256*256)) % 256;
uint8_t a = (color/(256*256*256)) % 256;
Ваш вопрос, зачем мне остаток от деления на 256. Попробуйте сами на бумажке: мой цвет — 65280. Проще всего это видно, если записать в двоичной системе. 65280 в двоичной системе как выглядит? Это тридцать два бита (четыре группы по восемь бит, каждая группа — наш цветовой канал):
00000000 00000000 11111111 00000000
a b g r
Чтобы получить красную компоненту, мне нужно взять число 00000000 00000000 11111111 00000000 и оттуда вытащить младшие 8 бит. Я сделаю побитовое сравнение с 255 (в двоичной системе это восемь битов единичек):
00000000 00000000 11111111 00000000 & // цвет, 65280 в десятичной
00000000 00000000 00000000 11111111 // маска, 255 в десятичной
Побитовое И мне даст просто 0, что и требовалось доказать. Оно откинет старшие, ненужные мне биты. Да, забыл. Остаток от деления на (2 в степени n) — это то же самое, что и побитовое И с числом ((2 в степени n)-1). А обычное целочисленное деление на (2 в степени n) — это побитовый сдвиг вправо на n бит.
Теперь хочу вытащить зелёный канал. Я сначала сдвину вправо на восемь бит (поделю на 256), а затем сделаю побитовое И с числом 255, чтобы оставшиеся синий и альфа мне не мешали:
00000000 00000000 11111111 00000000 // цвет, 65280 в десятичной
(00000000 00000000 11111111 00000000>>8) // цвет, поделенный на 256
00000000 00000000 00000000 11111111 // результат деления
00000000 00000000 00000000 11111111 // маска, 255 в десятичной
Ну и получим нашу зелёную компоненту как (65280/256)%256 = 255.
Пожалуйста, убедитесь, что вы понимаете, откуда там взялся косинус.
В принципе, всё понятно разьяснено «на пальцах» в этом комменте, спасибо!
Я попробовал набросать на JS на скорую руку по описанию — но возникает иное, волнообразное искажение при взгляде на стены под углом (у меня чересчур завышен множитель высоты стен + «широкоформатное» окно вывода, поэтому это проявляется в большой степени) (видно на скрине на левой стене):
На гифке в статье они тоже присутствуют, но не в такой степени (см. правую стену):
Это от того, что функция косинуса не в полной мере точно «моделирует» данное искажение? Или есть некие иные причины?
А под прямым углом линии взгляда на стену всё корректно, без подушечности:
Если уменьшить шаг скольжения вдоль луча, не поможет ли? Вообще косинус должен давать идеальную коррекцию подушки, другой вопрос, что у нас и подушка приблизительная. Обратите внимание на искривленные ступеньки по центру экрана, когда вы смотрите прямо на стену.
В чём цель включения Gitpod в статью? Т.е. что конкретно я должен там сделать?
Вот это по ссылке Gitpod второго этапа:
Вопрос мой в том, в чём разница-то? Я вижу один и тот же README, где-то что-то собирается. Какой профит в этих ссылках gitpod?
Не знаете, как это вылечить?
Почему бы в образовательных целях не использовать более простой и понятный для новичков вариант исходного кода для Этапа 1(сохранение картинки на диск)? Например такой:
#include <fstream>
using namespace std;
class color
{
public:
int r;
int g;
int b;
};
int main()
{
int h = 512;
int w = 512;
ofstream out("./mypict.ppm");
out << "P3\n" << h << ' ' << w << '\n'
<< 255 << '\n';
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
color pixel = { 255 * i / h, 255 * j / w, 0 };
out << pixel.r << ' ' << pixel.g << ' '
<< pixel.b << ' ' << '\n';
}
}
out.close();
}
Да, выходной файл больше, так как используется текстовый вариант формата .ppm, код более медленный. Зато нет заморочек с битами, код понятен для новичка.
Ну а упаковка цветов спрятана внутри функций, которые легко объясняются по первой же ссылке из поискового запроса rgba pack int. Мне не кажется, что это должно быть сложно.
Кроме того, очень важный момент: я даю мой код только как пример, а вовсе не как эталон. Я предполагаю, что вы пишете свой код, а с моим только иногда сравниваете, так что пишите как вам удобно!
С точки зрения потребления памяти ваш метод хранения изображения экономный, спору нет. Использовать int с 4 байтами на канал, пожалуй, расточительно, char тут больше подходит, соглашусь. Вы правы, каждый пишет так, как ему удобнее, но с педагогической точки зрения, неплохо было бы сказать при описании Этапа 1 про расход памяти, чтобы читателю был понятен выбор более сложного метода.
Я почему так первый фрагмент кода подробно разбираю, попытался ваш код в VS2019 выполнить и вместо градиентной заливки выдает вот такую картинку:
.
Причину найти не смог. Мой упрощенный код родился в попытке реализовать ваш алгоритм. С ним картинка формируется правильно.
А какая разница где он поставит скобки в операциях, где есть только умножение и деление?
255×2/5 <> 255x(2/5)?
Ну мы же о компьютерах говорим, а не об арифметике. 2/5 (целочисленное деление) равно нулю. То есть, 255*(2/5) равно нулю. 255*2 равно 510, так что (255*2)/5 равно десяти.
А это зависит от значений переменных и от типа переменных.
Если у вас там int (или его вариации), то да.
Ну речь-то шла конкретно об этом коде
В своё время писал рейкастер на java по этому циклу статей. Субъективно, даёт лучший результат, но ощутимо сложнее для понимания, а тут - приятно и легко, делюсь своими наработками :)
Введение в программирование: простой 3D-шутер с нуля за выходные, часть 1