Китайские мемы 2018 года — Магазета

2019-01-22 18:41:34

Китайские мемы 2018 года — Магазета

Китайский интернет – это фонтан неожиданных шуток и игры слов. Мемы уходящего года как нельзя лучше иллюстрируют настроения китайских пользователей, особенности и соль (盐粒儿) их юмора. Забытые произведения обретают новую жизнь, а модные персонажи – вирусную популярность. Магазета подготовила свой список мемов 2018 года в китайском интернете.

确认过眼神 | quèrèn guò yǎnshén

«确认过眼神,我遇上对的人» (по глазам вижу, что я встретил правильного человека) – это строчка из песни, впервые звучавшей в альбоме сингапурского поп-исполнителя Линь Цзюньцзе (林俊杰) ещё в 2008 году.

В канун нового 2018 года пользователь Вэйбо под ником 玩网小学生 опубликовал пост под названием «确认过眼神,你是广东人», в котором высмеял привычку гуандунцев в Новый Год дарить хунбао с неприлично маленькими суммами. Этим он вызвал горячее обсуждение в сети и дал вторую жизнь музыкальной композиции с этими словами.

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

隐形贫困人口 | yǐnxíng pínkùn rénkǒu

Фраза «невидимое бедное население» (隐形贫困人口) стала популярной в начале 2018 года. В мягкой форме она обозначает людей, которые на первый взгляд имеют еду и кров, а на самом деле нищенствуют.

Особенно остро присутствие такой социальной группы ощущается после 11.11, когда во время скидок люди тратят последние деньги на модные девайсы и обновки, а после – считают копейки. В отношении таких людей интернет пользователи говорят: «你这么会花钱,一定很穷吧» (ты так умело тратишь деньги, наверняка бедняк). В целом можно сравнить с нашими шутками про айфоны и Доширак.

土味情话 | tǔ wèi qínghuà

土味情话 – означает «пошлые любовные речи», а именно слащавые речи с местным колоритом, то есть ухаживания не самого высокого стиля. Обычно так «подкатывает» к девушкам сальный чел. Вот пример диалога:

— 我觉得你今天有点奇怪哦 / Чувствую, ты сегодня какая-то странная.
— 奇怪?怎么了?/ Странная? Как это?
— 怪好看的!/ Странно красивая!

Обычно предполагается, что в ответ звучит нечто вроде:

糟了!是心动的感觉 / Ой всё! Чувствую, аж растрогалась

土味情话又双叒叕地来了!/ Ой, опять начались пошлые речи!

小猪佩奇 | Xiǎozhū Pèijī

Свинка Пеппа (小猪佩奇) стала звездой мемов в уходящем году. Пользователи с ума сходили, задаваясь вопросами, где купить сувениры с символикой этого мультфильма, знаменитости обклеивали себя переводными картинками с героями мультсериала, носили детские часы и прочие забавные мелочи с изображением свинок. Кто-то даже делал себе причёску с силуэтом свинки Пеппы или пытался набить настоящую татуировку.

По информации китайских видеохостингов, мультсериал с Пеппой набрал сотни миллиардов просмотров в совокупности. Таким образом, Свинка Пеппа стала негласным знаком того, что тот или иной человек «в теме» и следит за трендами.

Неуместный ажиотаж привёл к блокировке тега, связанного с Пеппа-манией, то есть с вирусным распространением изображений мультяшных героев популярными личностями на китайском видеосервисе DouYin. Мультфильмы с Пеппой по-прежнему могут посмотреть все желающие, а сувениры с символикой мультсериала свободно продаются в магазинах.

你个杠精儿!| nǐgè gàngjīngr

В комментариях к моментам или статьям в Вичате, в обсуждениях в Вэйбо и на форумах появляются в бессчётном количестве они – спорщики. Ещё их можно назвать склочники или те, кто любит препираться без причины. 杠精 происходит от диалектного 抬杠 – ругаться по пустякам.

Немало китайских интернет-словечек происходят из диалектов. Этот мем, тем не менее, не является прямым эквивалентом нашим «диванным войскам», зачастую претендующим на экспертное мнение. Путём бессмысленных и беспощадных споров китайские юзеры пытаются разогнать тоску, и 你个杠精儿! употребляется скорее в отношении тех, кому очевидно просто нечем заняться.

太skr了 | tài skr le

Буквосочетание пришло из английского сленга, в котором обозначает нечто невразумительное, неожиданное и даже безумное. Поставщик локальных мемов — популярный актёр и исполнитель Крис У (吴亦凡) — использовал это слово в своей пресс-конференции в начале второго сезона телешоу «Рэп в Китае» (中国有嘻哈). Словом «skr» он охарактеризовал выступления конкурсантов. В сочетании с другим популярным мемом: «确认过眼神,是会说 skr 的人»

大猪蹄子 | dà zhūtízi

Мем пришёл из сериала «История дворца Яньси» (延禧攻略), дословно переводится как «большие свиные копыта». Дело в том, что по-китайски «главная мужская роль» — 男主角 nán zhǔjiǎo – созвучно 男猪脚 nán zhūjiǎo, что переводится как «мужские свиные копыта». Так, 大猪蹄子 – имеет похожее значение, но слегка преувеличенное, чтобы подчеркнуть ненависть, выразить неудовольствие непостоянством мужского персонажа.

Впервые «мужские свиные копыта» или «все мужчины – свиные копыта» появились в плавающих комментариях, которые пользователи могут отправлять во время стриминга на платформах. Впоследствии фраза стала использоваться и на других платформах в китайской сети, где женщины критикуют патриархальный строй или недовольны мужским поведением в целом.

pick一下 | pick yīxià

На смену прошлогоднему «打call» пришёл свежий «pick一下». Начали этот мем употреблять на корейском реалити-шоу «Produce101», особенно популярном в Китае.

За мемом не стоит какой-то особой истории или игры слов, поэтому смысл его понять легко – «выбери меня» или просто «выбери». Употребляется в китайских предложениях вместо глагола «выбирать» (挑选, 选择). Не путать с другим созвучным мемом — 皮一下.

C位出道 | C wèi chūdào

出道 переводится как «начать карьеру; выйти в свет; дебютировать». В свою очередь C位 является сокращением по разным версиям от center или carry и обозначает «почётное место влиятельного в шоу-бизнесе человека» (大咖位). Так, все вместе можно собрать в «центр начинает» или «звезда дебютирует», впрочем, в мемах часто используется в смысле «ну, погнали» или «дорогу новичкам».

По одной версии, фраза пришла от геймеров, которые перед началом выполнения задания собираются на базе (в «центре»), а фраза служит сигналом к началу действий. По другой версии, мем появился на репетиции выступления поп-группы, когда режиссёр скомандовал перед началом – «ну, центр, начинай». Пример использования в предложении: «让嘉宾占C位» — пусть почётные гости встанут в центр.

转发锦鲤 | zhuǎnfā jǐnlǐ

Карп кои (锦鲤) традиционно считается символом удачи, но в этом году он заполонил соцсети. В интернете его используют для мемов, стикеров и поздравительных открыток. Массовая пересылка друг другу изображения карпов и образовала мем — 转发锦鲤. Эти декоративные рыбки-долгожители популярны по всему миру, но в Китае им приписывают особые свойства приносить удачу и долголетие. Так что, если вы хотите быть в тренде, то пришлите своим друзьям изображение этой рыбки, а лучше совместите с фото известного и, наверняка, удачливого человека.

Для заглавной иллюстрации использовано фото sohu.com.

Вам понравилась наша статья? Поделитесь ею в соцсетях (достаточно кликнуть на иконку внизу страницы).

Если вы хотите быть в курсе наших публикаций, подписывайтесь на страницу Магазеты в facebookvkinstagramtelegram и наш аккаунт в WeChat — magazeta_com.

Взято отсюда

к вопросу о грамотности и академической подготовке китаеведов: ivan_zuenko

2019-01-16 23:48:00

к вопросу о грамотности и академической подготовке китаеведов

Пожалуй, все восточники давным-давно привыкли к грубым ошибкам обывателей во всем, что касается предмета их изучения. Мы даже научились относиться к этому философско-снисходительно. Действительно, знание всех тонкостей и нюансов, касающихся стран Востока, и составляет то «сакральное» знание, которое отличает квалифицированного специалиста от неспециалиста. Впрочем, зачастую им уделяется недостаточное внимание, в результате чего даже выпускники профильных вузов сплошь и рядом работают с грубыми ошибками. Без особых надежд на исправление этой ситуации, выкладываю сюда ряд практических соображений - наиболее распространенные синологические «штучки», знание которых отличает специалиста от любителя, и в которых мы, к огромному сожалению, так часто делаем ошибки.

отличие от корейского, например, где существуют противоречия). Называется принятая в русском языке система – «транскрипция Палладия»,  в русско-китайских словарях она использовалась задолго до триумфального шествия пиньина по планете, и полную таблицу соответствий пиньина и Палладия можно посмотреть здесь – см. статью в русскоязычной википедии «транскрипционная система Палладия». Хочется добавить, что она не всегда корректно передает китайские звуки, но в русском тексте мы обязаны использовать именно ее и только ее.

Наиболее заметные «фишки» этой системы: звук «you» (по-китайски звучит скорее как «ёу») по-русски нужно передавать как «ю» (поэтому слово «друг» русской транскрипцией следует записывать как «пэнъю»); звук «е» (гортанный «э») всегда необходимо передавать как «э» и ни в коем случае не «е» и не «ы» (например, Мао Цзэдун, а никак иначе); инициали «z» и «j» следует передавать как «цз», а никак не «дз», и наконец, финали «..-n» и «…-ng» мы на русском передаем как «…-н» и «…-нь», несмотря на то, что китайцы на самом деле не смягчают звук ни в первом, ни во втором случае (в случае с «…-ng» используется гортанный звук).

Придыхание, характерное для многих китайских согласных, в транскрипции на русский язык не указывается!

Если после слога, заканчивающегося на твердую согласную, дальше идет слог, начинающийся на гласную, необходимо использовать твердый знак (пример, «Чанъаньцзе»)!

Звук «hui» для благозвучия передается в русской транскрипции как «хуэй», и в случае с провинцией Аньхой как «хой».

Практически все китайские топонимы в России принято транскрибировать, используя систему Палладия, исходя из тех географических названий, которые приняты в КНР в настоящий момент.
Исключения составляют: названия столиц Пекин и Нанкин (а не Бэйцзин и Наньцзин, как следовало бы, исходя из логики транскрибирования), большинство маньчжурских топонимов, издавна употребляемых русскими: Харбин, Хинган и Цицикар и др. (а не «Хаэрбинь», «Синъань» и «Цицихаэр», как в китайском языке), а также еще ряд топонимов, относящихся к местам компактного проживания нацменьшинств. При переводе топонимов Внутренней Монголии следует опираться на монгольские, а не на ханьские термины, если таковые имеются (например, «Хух-Хото», а не «Хухэхаотэ», но «Баотоу», что верно в обоих случаях). То же касается и Синьцзяна («Урумчи» и «Кашгар» вместо «Улумуци» и «Каши») и Тибета («Лхаса», а не «Ласа»). В Нинся и Гуанси китайский язык повсеместно вытеснил местные языки, поэтому топонимы этих автономных районов совпадают с китайскими и для перевода можно смело пользоваться китайской картой.

Использование форм транскрибирования «фын» и «мынь» (вместо «фэн» и «мэнь») в топонимах Кайфын (Кайфэн), Аомынь (Аомэнь) и Сямынь (Сямэнь) является устаревшей, но допустимой (в отличие от имен, где уже не употребляется).

Переводя городские топонимы, следует помнить, что в России принято транскрибировать, а не давать дословный перевод (например, «площадь Тяньаньмэнь» вместо «площадь Ворот небесного спокойствия» и «улица Чжуншаньдацзе» вместо «улица Сунь Ятсена»).
При этом в переводе названия улицы китайский термин, указывающий, что это именно улица («цзе», «дацзе», «лу», «далу», «дао»), сохраняется, и все слово пишется целиком, без пробелов и дефисов (например, «улица Чанъаньцзе» или «проспект Дунфанхундалу»). А при переводе названия площади или микрорайона такой термин опускается (например, «площадь Жэньмин» вместо «площадь Жэньминьгуанчан» или «Народная площадь», «район Наньган» вместо «район Наньганцюй»); исключения составляют названия площадей, относящиеся к датам (например, «площадь 4 мая» вместо «площадь Усы»).

Те же принципы касаются и перевода названий китайских компаний. В России принято транскрибировать название, а не давать дословный перевод, если мы говорим об организацией, имеющей именно название, а не просто определение, чем она занимается. Например, правильно переводить  «корпорация «Цзиньбу», а не «корпорация «Прогресс», при этом допустимо передать дословный перевод названия в скобках. В то же время правильно давать дословный перевод организации, если оно состоит из определений – например, «Деревообрабатывающая компания города Суйфэньхэ», а не «Суйфэньхэши муе гунсы»). Говоря о государственных и общественных организациях, правильным является только перевод, а не транскрипция (например, «Всекитайское собрание народных представителей», а не «Цюаньго жэньминь дайбяо дахуй»).

Китайские имена записываются в два слова: первое является фамилией, второе – именем. Причем, если имя состоит из двух слогов, они пишутся слитно, а никак не через дефис (данная норма является устаревшей) и не двумя разными словами (как до последнего времени при транскрибировании корейских имен).

Большинство китайских фамилий состоят из одного слога, однако есть и исключения – наиболее распространенными являются фамилии «Оуян», «Сыма» и «Чжугэ». Соответственно, их нужно иметь в виду и перевести имя легендарного героя «Троецарствия» как «Чжу Гэлян» вместо «Чжугэ Лян» - это грубая ошибка.

Говоря о видных деятелях Гоминьдана, необходимо помнить, что их имена были восприняты русскими в форме, характерной для южных диалектов (в период 1910-20-х гг. Коминтерн сотрудничал именно с гоминьдановским правительством в Гуанчжоу, а не с бэйянскими милитаристами в Пекине). Поэтому имена «Сунь Ятсен» и «Чан Кайши» на путунхуа неизвестны – на пекинском диалекте это соответственно «Сунь Чжуншань» и «Цзян Цзеши» (кстати, имя его сына Цзян Цзиньго, который долгие годы руководил Тайванем, мы знаем уже в варианте путунхуа).

При склонении китайских имен необходимо помнить, что склоняется все имя целиком, если мы имеем дело с именем-фамилией. Ни в коем случае нельзя склонять и имя, и фамилию (например, нельзя сказать «реформы Дэна Сяопина» - необходимо говорить «реформы Дэн Сяопина»). Данное правило не имеет исключений!

Если есть только фамилия, она склоняется по нормам русского языка – то есть склоняется, если она принадлежит мужчине («фильмы Чжана», «реформы Дэна») и не склоняется, если принадлежит женщине («олимпийские победы Чжан»).

При переводе имен китайцев, проживающих на Тайване, следует сначала установить соответствие имени пиньину, а потом передавать его на русский язык, в соответствии с принятыми нормами транскрипции. (Дело в том, что на Тайване повсеместно распространены иные системы транскрипции, отличные от пиньина, поэтому многие имена выглядят необычно – например, Lieu Hsien, что на самом деле «всего лишь» Лю Сянь).

То же касается и кантонского диалекта, для которого характерен ряд уникальных фишек, которые нужно можно только знать, поскольку логике они не подчиняются – например, слог «Ng» соответствует «нормальной» китайской фамилии «У»).

Один из самых запутанных вопросов в прикладном китаеведении – это вопрос склонения топонимов. Конкретно – как склонять название города или провинции, в соответствии с нормами склонения для мужского или женского рода. Зачастую ответ очевиден – Пекин является «мальчиком», не только потому что это город (он), но и потому что название заканчивается на твердую согласную. Соответственно склонять его нужно «по-мужски». Однако существуют и неочевидные примеры, в случае если слово заканчивается на мягкую согласную – скажем, Сиань, Сямэнь или Хунань. В целом, как мне кажется, следует отталкиваться от того, что именно данный топоним обозначает. Если город, то нужно считать, что он относится к мужскому роду, если провинцию, то к женскому. Примеры, «город Сиань» - мужской род; соответственно можно привести такие примеры употребления, «я уехал из Сианя», «я побывал в Сиане» (а не «я уехал из Сиани», «побывал в Сиани»); «провинция Хунань» - женский род, соответственно, нужно говорить «кухня Хунани», «мне понравилось в Хунани» (а не «кухня Хунаня» и «понравилось в Хунане»). Впрочем, в академической литературе сплошь и рядом встречаются исключения, что лично я склонен объяснять отсутствием общеизвестных рекомендаций на данную тему. В случае с реками, горами и озерами, если они заканчиваются на твердую согласную, следует считать, что данные топонимы относятся к мужскому роду («вода из Чжуцзяна», «красоты Хингана», «купался в Кукуноре» и т.д.) В остальных случаях лучше избегать склонения, дабы окончательно не запутаться.

Касательно истории Китая заблуждений, по которым специалиста можно отличить от профана, еще больше, и, безусловно, данная тема заслуживает отдельного материала. Поэтому остановлюсь лишь на нескольких, наиболее распространенных примерах.

Де-факто Китайская народная республика (основана в 1949 г., материковая часть Китая) и Китайская республика (основана в 1912 г., о. Тайвань) являются независимыми государствами. Однако де-юре и в Пекине, и в Тайбэе отрицают суверенитет другого государства и провозглашают наличие единого и неделимого Китая; а гражданская война между КПК, который руководит КНР, и Гоминьданом, который руководит Китайской республикой, официально не завершилась. Российская федерация, являясь геостратегическим партнером КНР, на официальном уровне полностью признает и разделяет позицию Китайской народной республики. Следовательно, Вы можете иметь какую угодно личную позицию по данной проблеме, однако при переводе или организации официальных мероприятий гражданам России необходимо придерживаться официально разделяемого нашей страной курса.

В истории Китая насчитывается сразу несколько периодов, когда страна не была единой, а на части ее территории существовали некитайские государства (государства тюрок, тангутов, киданей, чжурчжэней и т.д.) Официальная китайская пропаганда в настоящий момент считает их историю частью своей национальной истории (как если бы мы точно также относились к истории Золотой Орды, например), однако следует помнить, что эти государства, как правило, были враждебны по отношению к ханьскому Китаю, и считать их «китайскими» - это грубая ошибка.

Дважды в истории Китая страна полностью захватывалась соседними народами. При этом на территории Китая существовало единое государство, но китайским по своей сути оно не было, и называться таким не может. В период владычества монголов на территории Китая существовала Империя Юань, а период владычества маньчжуров – Цинская (или Дайцинская) империя. Кстати, территория нынешних северо-восточных провинций КНР (бывшая Маньчжурия) вошла в состав Китая именно в цинский период – то есть формально не они были присоединены  к Китаю, а Китай присоединен к ним. Более грамотно называть эти земли «северо-восточные провинции КНР», а не «северо-восточный Китай», так как традиционно ареал китайской цивилизации на севере заканчивался в районе г. Шаньхайгуань (примерно граница провинций Хэбэй и Ляонин) и на Ляонин, Цзилинь и Хэйлунцзян не распространялся.

Территория российского Дальнего Востока никогда не входила в состав китайского государства, хотя и частично находилась в составе государственных образований тунгусо-маньчжурских народов, занимающих часть территории Китая (чжурчжэньской империи Цзинь и маньчжурской империи Цин). Современная китайская пропаганда, как я уже говорил, склонна воспринимать историю своих соседей-завоевателей, как часть национальной истории, поэтому на многих исторических картах территория российского Дальнего Востока обозначается как «зависимые от Китая земли», что по сути является искажением истории (примерно с тем же успехом Монголия могла бы обосновывать территориальные претензии на большую части России).

Справедливости ради следует заметить, что после подписания договора о демаркации российско-китайской границы 2001 г. (по которому Россия уступила КНР половину Большого Уссурийского и остров Тарабаров) официально Китайская народная республика не имеет никаких территориальных претензий по отношению к России.

Великая китайская стена никогда не существовала как единое фортификационное сооружение. Под данным термином следует понимать систему различных, построенных в разное время стен, укреплений и таможенных постов, построенных к северу от традиционного ареала китайской цивилизации. Хорошо сохранившиеся участки «великой китайской стены» к северу от Пекина официально были построены в последние десятилетия династии Мин – первые десятилетия династии Цин (т.е. 17 век, намного позднее фортификационных сооружений Владимирской Руси, например), однако впоследствии неоднократно достраивались и перестраивались, в том числе и для использования в туристических целях во второй половине 20 века.

Империя Цинь – первая объединенная китайская империя, основанная Цинь Шихуаном, существовала до нашей эры. Империя Цин – последняя империя, существовавшая на территории Китая, прекратила свое существование в 20 веке. Путать эти два государства – большая ошибка.
Взято отсюда

Трехмерный движок внутри запроса SQL / Хабр

2019-01-11 19:18:13

Трехмерный движок внутри запроса SQL

Несколько лет назад на форуме SQL.ru решили провести сравнение реализаций трассировщиков лучей на разных языках программирования. К сожалению, моя заявка не может участвовать т.к. она не выводит надпись «PIXAR», поэтому публикую ее здесь.

Для чистоты эксперимента я использовал SQLite без расширений. Оказалось, что там нет даже функции SQRT.

WITH RECURSIVE numbers AS (SELECT 0 AS n UNION ALL SELECT n+1 FROM numbers WHERE n<89),
pixels AS (SELECT rows.n as row, cols.n as col FROM numbers as rows CROSS JOIN
numbers as cols WHERE rows.n > 4 AND rows.n < 38 AND cols.n > 9 AND cols.n < 89),
rawRays AS (SELECT row, col, -0.9049 + col * 0.0065 + row * 0.0057 as x,
-0.1487 + row * -0.0171 as y, 0.6713 + col * 0.0045 + row * -0.0081 as z FROM pixels),
norms AS (SELECT row, col, x, y, z, (1 + x * x + y * y + z * z) / 2 as n FROM rawRays),
rays AS (SELECT row, col, x / n AS x, y / n AS y, z / n AS z FROM norms),
iters AS (SELECT row, col, 0 as it, 0 as v FROM rays UNION ALL
SELECT rays.row, rays.col, it + 1 AS it, v + MAX(ABS(0.7+v*x) - 0.3,
ABS(0.7+v*y) - 0.3, ABS(-1.1+v*z) - 0.3, -((0.7+v*x) * (0.7+v*x) +
(0.7+v*y) * (0.7+v*y) + (-1.1+v*z) * (-1.1+v*z)) * 1.78 + 0.28) AS v
FROM iters JOIN rays ON rays.row = iters.row AND rays.col = iters.col WHERE it < 15),
lastIters AS (SELECT it0.row, it0.col, it0.v AS v0, it1.v AS v1, it2.v AS v2
FROM iters as it0 JOIN iters AS it1 ON it0.col = it1.col AND it0.row = it1.row
JOIN iters AS it2 ON it0.col = it2.col AND it0.row = it2.row
WHERE it0.it = 15 AND it1.it = 14 AND it2.it = 13),
res AS (SELECT col, (v0 - v1) / (v1 - v2) as v FROM lastIters)
SELECT group_concat(
substr('$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. ',
round(1 + max(0, min(66, v * 67))), 1) || CASE WHEN col=88 THEN X'0A' ELSE '' END, '')
FROM res;



Здесь можно покрутить кубик

Под катом построчный разбор запроса. Как обычно, достаточно знания основ SQL и школьной математики.


Disclaimer: я далек от мира БД, поэтому буду раз замечаниям в личку.

Версия для Postgres (UPD: благодаря флагам стало работать на порядок быстрее, UPD2: еще ряд улучшений, теперь время выполнения 150мс)

Для понимания терминологии и принципа работы алгоритма рекомендуется ознакомиться со статьей про ray marching для Excel.

Общая структура


Список промежуточных таблиц:



Относительно того, как они друг от друга зависят все просто: каждая следующая таблица использует только предыдущую, а финальный запрос использует только таблицу res.

Все таблицы (за исключением numbers и iters) содержат по 81 x 29 строк (по одной на каждый «пиксель»), колонки row и col индексируют их координаты. Таблица iters содержит 81 x 29 x 15 строк (по одной на каждую итерацию ray marching для каждого «пикселя»). Номер итерации содержится в колонке it.

Финальный запрос выдает таблицу из одной строки и колонки с текстом, все остальные таблицы содержат только вещественные числа (при этом колонки row, col и it – целые неотрицательные).

Получается, если опустить вспомогательные таблицы, очень простая структура запроса:

WITH RECURSIVE
 numbers AS (SELECT ...),
 pixels AS (SELECT ...),
 rawRays AS (SELECT ...),
 normsSq AS (SELECT ...),
 norms AS (SELECT ...),
 rays AS (SELECT ...),
 iters AS (SELECT ...),
 lastIters AS (SELECT ...),
 res AS (SELECT ...)

SELECT group_concat(substr('$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. ', round(1 + max(0, min(66, v * 67))), 1) || CASE WHEN col=88 THEN X'0A' ELSE '' END, '') FROM res;

Рекурсивные запросы


Вот стандартный способ получить таблицу, содержащую числа от 0 до 89:

WITH RECURSIVE numbers AS (
 SELECT 0 AS n
 UNION ALL
 SELECT n+1
  FROM numbers
  WHERE n<89
) ...


Извлекаем квадратный корень


Мы будем использовать метод Герона (вавилонский метод) вычисления корня. Допустим, мы хотим вычислить $\sqrt{S}$. Мы строим ряд $x_0, x_1, \ldots$ по следующей формуле:

$x_{n+1} = \frac{x_n + \frac{S}{x_n}}{2}$



Логика метода очень простая: $\sqrt{S}$ всегда лежит между $x$ и $\frac{S}{x}$ для любого числа $x$. Поэтому естественно взять середину отрезка между этими числами как приближение.

Геометрически это можно изобразить так:



Каждое следующее значение все лучше приближает корень, за один шаг погрешность уменьшается как минимум в два раза.

Начальное значение $x_0$ может быть любым положительным числом, например 1. В игре Quake для этого использовалась магическая константа 0x5f3759df (точнее, она использовалась для инвертированного квадратного корня, тем не менее аналогичный метод может быть придуман и для обычного корня), но, к сожалению, в SQL нет доступа к двоичному представлению чисел с плавающей запятой.

В этой статье корень нужен в двух местах:

  • при нормировании векторов, выходящих из камеры: ray marching сильно зависит от расстояний, а для того, чтобы их откладывать, нужен вектор длины 1.
  • при вычислении расстояния до границы сферы, которая вырезается из квадрата.


В первом случае значения находятся в узком диапазоне $[0.7, 1.5]$ и начальное приближение 1 идеально подходит. Во втором случае, собрав статистику по вызовам, получил среднее значение $0.28$, которое было взято в качестве начального.

Оказалось, что при правильном начальном значении достаточно одной итерации! То есть в нашем случае корень приближается линейной функцией:

$sqrt_1(x) = \frac{1 + x}{2}$


$sqrt_2(x) = \frac{0.28 + \frac{x}{0.28}}{2} = 0.14 + 1.78 x$



Вычисляем лучи из камеры


Задача первых четырех таблиц — каждому «пикселю» сопоставить трехмерный вектор длины 1, выходящий из камеры и проходящий через соответствующую точку экрана.

Сначала необходимо получить таблицу с нужной структурой, то есть с ячейками, для которых указаны номер строки и номер столбца. Для этого берется декартово произведение набора чисел от 0 до 89 и из него вырезаются пустые строки и столбцы:

...
pixels AS (
 SELECT rows.n as row, cols.n as col
 FROM numbers as rows
  CROSS JOIN numbers as cols
 WHERE rows.n >= 5 AND rows.n < 38 AND cols.n >= 10 AND cols.n < 89
),
...

Далее мы находим ненормированные вектора. В общем виде у них длинная формула из тригонометрических функций. Чтобы не усложнять запрос, я зафиксировал камеру и предрассчитал коэффициенты:

...
rawRays AS (
 SELECT
  row, col,
  -0.9049 + col * 0.0065 + row * 0.0057 as x,
  -0.1487 + row * -0.0171 as y,
  0.6713 + col * 0.0045 + row * -0.0081 as z
 FROM pixels
),
...

После этого мы должны посчитать (приблизительные) длины этих векторов по формуле $sqrt_1$:

...
norms AS (
 SELECT
  row, col, x, y, z,
  (1 + x * x + y * y + z * z) / 2.0 AS n
 FROM rawRays
),
...

Осталось разделить координаты векторов на их длину для получения векторов длины 1:

...
rays AS (SELECT row, col, x / n AS x, y / n AS y, z / n AS z FROM norms),
...

Итерации ray marching


Здесь используется чуть более сложная конструкция с рекурсивными запросами, содержащая JOIN. Мы хотим произвести 15 итераций алгоритма ray marching для каждого пикселя. Если при рекурсивном вычислении таблицы numbers каждый раз таблица содержала по одной строке, которые потом объединялись, здесь промежуточные таблицы содержат по 81 x 29 строк и вычисляются 15 раз.

Вся трехмерная геометрия содержится в формуле

$SDF\begin{pmatrix}x\\ y \\ z\end{pmatrix} = \max\left(|x| - 0.3, |y| - 0.3, |z| - 0.3, -\left(sqrt_2(x^2 + y^2 + z^2) - 0.42\right)\right)$


  • функция $\max$ означает пересечение
  • $|x| - 0.3, |y| - 0.3, |z| - 0.3$ задают три пары полуплоскостей, образующих куб со стороной $0.3 \cdot 2$
  • $-\left(sqrt_2(x^2 + y^2 + z^2) - 0.42\right)$ — наружная часть сферы радиуса $0.42$. Радиус взят больше видимого, чтобы компенсировать неточность приближения квадратного корня.


Далее нам надо просто вычислить последовательность $0 = v_0, v_1, v_2 \ldots, v_{15}$ для каждого пикселя по формуле:

$v_{n+1} = v_n + SDF\left( \begin{pmatrix}camX\\camY\\camZ\end{pmatrix} + v_n \begin{pmatrix}x\\y\\z\end{pmatrix} \right)$


где $x, y, z$ — координаты нормированного вектора. Так как координаты камеры повторяются несколько раз, я округлил их до одного десятичного знака.

...
iters AS (
 SELECT
  row, col,
  0 as it,
  0 as v
 FROM rays
 UNION ALL
 SELECT
  rays.row,
  rays.col,
  it + 1 AS it,
  v + MAX(
   ABS(0.7+v*x) - 0.3,
   ABS(0.7+v*y) - 0.3,
   ABS(-1.1+v*z) - 0.3,
   -(
    (0.7+v*x) * (0.7+v*x) + (0.7+v*y) * (0.7+v*y) + (-1.1+v*z) * (-1.1+v*z)
   ) * 1.78 + 0.28
  ) AS v
 FROM iters
  JOIN rays
   ON rays.row = iters.row AND rays.col = iters.col
 WHERE it < 15
),
...

Получаем интенсивности «пикселей»


Здесь используется та же формула, что и в Excel, которая аппроксимирует компоненту diffuse из затенения по Фонгу:

$intensity = \max\left(0, \min\left(1, \frac{v_{15} - v_{14}}{v_{14} - v_{13}}\right)\right) $



Чтобы ее вычислить, необходимо предварительно сделать таблицу с тремя последними итерациями ray marching:

...
lastIters AS (
 SELECT
  it0.row,
  it0.col,
  it0.v AS v0,
  it1.v AS v1,
  it2.v AS v2
 FROM iters as it0
  JOIN iters AS it1
   ON it0.col = it1.col AND it0.row = it1.row
  JOIN iters AS it2
   ON it0.col = it2.col AND it0.row = it2.row
 WHERE it0.it = 15 AND it1.it = 14 AND it2.it = 13
),
...

И, собственно, сама формула (операции $\max$ и $\min$ будут применены в финальном запросе):

...
res AS (SELECT row, col, (v0 - v1) / (v1 - v2) as v FROM lastIters)
...

Генерируем «ascii-арт»


...
SELECT group_concat(
 substr(
  '$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. ',
  round(1 + max(0, min(66, v * 67))),
  1
 ) ||
 CASE
  WHEN col=88 THEN X'0A'
  ELSE ''
 END, 
'') FROM res;

Задача финального запроса состоит в том, чтобы конвертировать таблицу с интенсивностями пикселей в одну ascii-строку. На вход он получает только таблицу res, содержащую колонки col и v.

  • group_concat(s, delim) – агрегирующая функция, конкатенирующая выражение s для всех строк, используя строку delim в качестве разделителя.
  • CASE WHEN cond1 THEN val1 WHEN cond2 THEN val2 ... ELSE valN END – условная конструкция, аналог тернарного оператора.
  • X'0A' – символ переноса строки, который вставляется перед первым символом каждой строки.
  • || – оператор конкатенации строк.
  • substr(s, start, count) – функция, возвращающая count символов строки s, начиная с символа с номером start. Индексация символов идет с единицы.
  • '$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,"^. ' – строка, содержащая «градиент» от «черного» ($) к «белому» (пробел) в ascii-символах. Взято с сайта http://paulbourke.net/dataformats/asciiart/.
  • round(1 + max(0, min(66, v * 67))) – преобразуем вещественные числа из интервала $[0, 1]$ в целое число в интервале $[1, 67]$ чтобы взять символ с соответствующим номером.
Взято отсюда