Comments 9
На самом деле микро-чанки — не самый удачный пример, потому что «монетизировать» их микро-адреса ни я, ни Док не пытались и смысла не видели. Там ещё была куча идей, показывающих, как важно генерить максимум идей, чтобы было что отметать, а одна потом отомстила за всех.
В его случае это была «шестерёнка», в моём — «собачий логарифм». И да, мне отметать тоже пришлось мешок вариантов, включая извращения на тему манхэттенского расстояния, а уж касаемо текстурирования — тут кладбище вариантов может поспорить со средним кладбищем какодемонов после визита спейсмарина, причём текущий мне тоже не особо…
Хорошая статья. Девайсы Смотрел, к сожалению пока не хватает скилла на ремонт. Могу выслать.
Рейкастер я тоже писал, правда у меня стены были с текстурами и пришлось поломать голову с вычислением v-координаты для интерполятора. Интерполятор был на фикседпоинтах и сильно зависел от филлрейта (насколько утыкаемся носом в стенку), но на омапе 200мгц при 240х320 бегал.
Девайсы Смотрел
Ахаха, и не сказал %)
Чего там где и как? Расскажи хоть итоги осмотра :)
UPD: чёрт, не туда кликнул :( ответить хотел :(
На самом деле микро-чанки — не самый удачный пример, потому что «монетизировать» их микро-адреса ни я, ни Док не пытались и смысла не видели. Там ещё была куча идей, показывающих, как важно генерить максимум идей, чтобы было что отметать, а одна потом отомстила за всех.
В его случае это была «шестерёнка», в моём — «собачий логарифм». И да, мне отметать тоже пришлось мешок вариантов, включая извращения на тему манхэттенского расстояния, а уж касаемо текстурирования — тут кладбище вариантов может поспорить со средним кладбищем какодемонов после визита спейсмарина, причём текущий мне тоже не особо…
Следующая статья будет про текстурирование?
Пока я это дело приостановил, посмотрим, что Док сделает со своей частью — а там посмотрим, сколько и чего у нас осталось на «улучшайзинги». Текстуры пока так и остались на том варианте, который можно на «Итче» скачать и глянуть. Ну то есть на таком, примерно более-менее дающем зацепки для глаза насчёт расстояний и углов, но очень уж корявые эти зацепки…
Я сейчас ковыряю другие занимательные движки «на грани эзотерики», в том числе пресловутый MDA — возможно, надписи на стенах оттуда можно будет тоже сюда добавить в качестве вариантов текстурирования. Но там сильно больше столбцов, так что есть у меня опасения.
Так что следующая статья «на тему» будет, наверное, всё-таки про динозавров пека, хотя кое-какие наработки для этого движка там могут промелькнуть. А буквально следующая — это лог старых наработок про открытый телефон-звонилку, я её вот сейчас пытаюсь запостить :)
Думаю, как применить наработки 2.5D движка BoxEngine (тоже в своём роде самого-самого, см. также недавний комментарий) для текстурирования стен на Dendy, с учётом ограничений.
BoxEngine разрабатывался изначально так, чтобы выдать наилучшую картинку в 128x128 (2006-й год), так что сверхнизкое разрешение 32x30 само по себе не проблема.
Но вот палитра, 4 цвета на 4 тайла… Можно ли выбрать цвета так, чтобы не было вырвиглазно? Первый цвет это фон (серый), второй цвет это А, третий цвет это Б, четвёртый цвет это ближайший к (А+Б)/2.
Можно пока сделать одномерные текстуры. Это позволит не заморачиваться с вычислением текстурной координаты для каждого столбца, и памяти под текстуры тогда вволю (даже добавив в будущем поддержку двухмерных, есть смысл использовать и одномерные, где возможно).
Теперь, остаётся только научиться рисовать…
Текстурированный столбец
Код из BoxEngine (вариант на C, для редактора на компьютере):
__declspec (dllexport) int WINAPI DrawTexLineW(PIXEL *GenericPixels, int *WallTilesAddresses, int x0, int YStart, int tgl, int TileStart, int TileFinish, int adr, int xmin, int xmax, int TexPeriod, int TexFloater, int lig, int addi) {
int xs,xc,t,b,addr,xcx,t2,x,k,t3,i,x2,tt1,tt2,xmx1,xmx2,xmn1,xmn2,LC;
PIXEL c,c1,c2;
//addr=adr+(TileStart%TexPeriod);
t=tgl>>(2+TexFloater);
t2=(tgl>>(3+TexFloater))<<3;
t3=t>>1; //t3a=t3+1;
xs=YStart; x=(xs>>TexFloater)*ScreenWidth+x0;
//tgl+=ScreenWidth<<TexFloater;
LC=AO[0]<<24;
switch (t) {
case 1:
for (k=TileStart;k<=TileFinish;k++) {
if (x>xmax) break;
//if (k%TexPeriod==0) addr=adr;
addr=adr+WallTilesAddresses[k];
LC=(AO[k]*lig+addi)&0xFF000000;
xs+=tgl;
xc=xs>>TexFloater;
b=TexelPriorities8[xc-(x/ScreenWidth)];
if ((b&3)==3) {
GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
} else GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
if ((b&12)==12) {
GenericPixels[x+=ScreenWidth]=Textures2[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures3[addr]|LC;
} else GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
if ((b&48)==48) {
GenericPixels[x+=ScreenWidth]=Textures4[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures5[addr]|LC;
} else GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
if ((b&192)==192) {
GenericPixels[x+=ScreenWidth]=Textures6[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures7[addr]|LC;
} else GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
addr++;
}
break;
case 2:
for (k=TileStart;k<=TileFinish;k++) {
if (x>xmax) break;
//if (k%TexPeriod==0) addr=adr;
addr=adr+WallTilesAddresses[k];
LC=(AO[k]*lig+addi)&0xFF000000;
xs+=tgl;
xc=xs>>TexFloater;
xcx=(xc-(x/ScreenWidth))&7;
if (xcx==0&&xc-(x/ScreenWidth)>t2)
b=TexelPriorities8[8];
else
b=TexelPriorities8[xcx];
GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
if ((b&3)!=0) GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures2[addr]|LC;
if ((b&12)!=0) GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures3[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures4[addr]|LC;
if ((b&48)!=0) GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures5[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures6[addr]|LC;
if ((b&192)!=0) GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures7[addr]|LC;
addr++;
}
break;
case 3:
for (k=TileStart;k<=TileFinish;k++) {
if (x>xmax) break;
//if (k%TexPeriod==0) addr=adr;
addr=adr+WallTilesAddresses[k];
LC=(AO[k]*lig+addi)&0xFF000000;
xs+=tgl;
xc=xs>>TexFloater;
xcx=(xc-(x/ScreenWidth))&7;
if (xcx==0&&xc-(x/ScreenWidth)>t2)
b=TexelPriorities8[8];
else
b=TexelPriorities8[xcx];
if ((b&3)==3) {
//GenericPixels[x+=ScreenWidth]=GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
//GenericPixels[x+=ScreenWidth]=GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
GenericPixels[x+=ScreenWidth]=c=Textures0[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
GenericPixels[x+=ScreenWidth]=c=Textures1[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
} else {
GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
}
if ((b&12)==12) {
GenericPixels[x+=ScreenWidth]=c=Textures2[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
GenericPixels[x+=ScreenWidth]=c=Textures3[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
} else {
GenericPixels[x+=ScreenWidth]=Textures2[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures3[addr]|LC;
}
if ((b&48)==48) {
GenericPixels[x+=ScreenWidth]=c=Textures4[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
GenericPixels[x+=ScreenWidth]=c=Textures5[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
} else {
GenericPixels[x+=ScreenWidth]=Textures4[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures5[addr]|LC;
}
if ((b&192)==192) {
GenericPixels[x+=ScreenWidth]=c=Textures6[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
GenericPixels[x+=ScreenWidth]=c=Textures7[addr]|LC;
GenericPixels[x+=ScreenWidth]=c;
} else {
GenericPixels[x+=ScreenWidth]=Textures6[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
GenericPixels[x+=ScreenWidth]=Textures7[addr]|LC;
}
addr++;
}
break;
default:
int tt1s,tt2s;
tt1=t3+(t&1); tt1s=tt1*ScreenWidth;
tt2=t3+1; tt2s=tt2*ScreenWidth;
xmx1=xmax-(tt1+tt1)*ScreenWidth;
xmx2=xmax-(tt2+t3)*ScreenWidth;
xmn1=xmin+(tt1+tt1)*ScreenWidth;
xmn2=xmin+(tt2+t3)*ScreenWidth;
xs=YStart+tgl/*-(1<<TexFloater)*/; x=(xs>>TexFloater)*ScreenWidth+x0;
//xs=YStart+tgl-TexFloater; x=xs>>TexFloater;
k=TileStart;
if (YStart<=((xmin/ScreenWidth)<<TexFloater)) {
LC=(AO[k]*lig+addi)&0xFF000000;
addr=adr+WallTilesAddresses[k++];
xs-=tgl;
xc=xs>>TexFloater;
xcx=((x/ScreenWidth)-xc)&7;
if (xcx==0&&(x/ScreenWidth)-xc>t2)
b=TexelPriorities8k[8];
else
b=TexelPriorities8k[xcx];
//1
c1=Textures7[addr]|LC; c2=Textures6[addr]|LC;
if ((b&1)!=0) {
if (x<xmn2) {c=Textures67[addr]|LC; goto fin_l2;}
x2=x-tt2s;
for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
GenericPixels[x-=ScreenWidth]=Textures67[addr]|LC;
} else {
if (x<xmn1) goto fin_l1;
x2=x-tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
}
x=x2;
//2
c1=Textures5[addr]|LC; c2=Textures4[addr]|LC;
if ((b&2)!=0) {
if (x<xmn2) {c=Textures45[addr]|LC; goto fin_l2;}
x2=x-tt2s;
for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
GenericPixels[x-=ScreenWidth]=Textures45[addr]|LC;
} else {
if (x<xmn1) goto fin_l1;
x2=x-tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
}
x=x2;
//3
c1=Textures3[addr]|LC; c2=Textures2[addr]|LC;
if ((b&4)!=0) {
if (x<xmn2) {c=Textures23[addr]|LC; goto fin_l2;}
x2=x-tt2s;
for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
GenericPixels[x-=ScreenWidth]=Textures23[addr]|LC;
} else {
if (x<xmn1) goto fin_l1;
x2=x-tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
}
x=x2;
//4
c1=Textures1[addr]|LC; c2=Textures0[addr]|LC;
if ((b&8)!=0) {
if (x<xmn2) {c=Textures01[addr]|LC; goto fin_l2;}
x2=x-tt2s;
for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
GenericPixels[x-=ScreenWidth]=Textures01[addr]|LC;
} else {
if (x<xmn1) goto fin_l1;
x2=x-tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
}
x=x2;
//.
addr++;
};
goto fin_l;
fin_l2: for (i=0;i<t3;i++) {
if (x<xmin) goto fin_l;
GenericPixels[x-=ScreenWidth]=c1;
}
GenericPixels[x-=ScreenWidth]=c;
for (i=0;i<t3;i++) {
if (x<xmin) goto fin_l;
GenericPixels[x-=ScreenWidth]=c2;
}
goto fin_l;
fin_l1: for (i=0;i<tt1;i++) {
if (x<xmin) goto fin_l;
GenericPixels[x-=ScreenWidth]=c1;
}
for (i=0;i<tt1;i++) {
if (x<xmin) goto fin_l;
GenericPixels[x-=ScreenWidth]=c2;
}
fin_l:;
xs=YStart+tgl*(k-TileStart)-(1<<TexFloater); x=(xs>>TexFloater)*ScreenWidth+x0;
for (;k<TileFinish;k++) {
addr=adr+WallTilesAddresses[k];
LC=(AO[k]*lig+addi)&0xFF000000;
xs+=tgl;
xc=xs>>TexFloater;
xcx=(xc-(x/ScreenWidth))&7;
if (xcx==0&&xc-(x/ScreenWidth)>t2)
b=TexelPriorities8k[8];
else
b=TexelPriorities8k[xcx];
//1
c1=Textures0[addr]|LC; c2=Textures1[addr]|LC;
if ((b&1)!=0) {
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
} else {
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//2
c1=Textures2[addr]|LC; c2=Textures3[addr]|LC;
if ((b&2)!=0) {
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
} else {
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//3
c1=Textures4[addr]|LC; c2=Textures5[addr]|LC;
if ((b&4)!=0) {
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
} else {
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//4
c1=Textures6[addr]|LC; c2=Textures7[addr]|LC;
if ((b&8)!=0) {
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
} else {
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//.
addr++;
};
if (k==TileFinish) {
addr=adr+WallTilesAddresses[k];
LC=(AO[k]*lig+addi)&0xFF000000;
xs+=tgl;
xc=xs>>TexFloater;
xcx=(xc-(x/ScreenWidth))&7;
if (xcx==0&&xc-(x/ScreenWidth)>t2)
b=TexelPriorities8k[8];
else
b=TexelPriorities8k[xcx];
//1
c1=Textures0[addr]|LC; c2=Textures1[addr]|LC;
if ((b&1)!=0) {
if (x>xmx2) {c=Textures01[addr]|LC; goto fin_r2;}
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
} else {
if (x>xmx1) goto fin_r1;
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//2
c1=Textures2[addr]|LC; c2=Textures3[addr]|LC;
if ((b&2)!=0) {
if (x>xmx2) {c=Textures23[addr]|LC; goto fin_r2;}
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
} else {
if (x>xmx1) goto fin_r1;
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//3
c1=Textures4[addr]|LC; c2=Textures5[addr]|LC;
if ((b&4)!=0) {
if (x>xmx2) {c=Textures45[addr]|LC; goto fin_r2;}
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
} else {
if (x>xmx1) goto fin_r1;
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//4
c1=Textures6[addr]|LC; c2=Textures7[addr]|LC;
if ((b&8)!=0) {
if (x>xmx2) {c=Textures67[addr]|LC; goto fin_r2;}
x2=x+tt2s;
for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
} else {
if (x>xmx1) goto fin_r1;
x2=x+tt1s;
for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
}
x=x2;
//.
addr++;
};
goto fin_r;
fin_r2: for (i=0;i<t3;i++) {
if (x>xmax) goto fin_r;
GenericPixels[x+=ScreenWidth]=c1;
}
GenericPixels[x+=ScreenWidth]=c;
for (i=0;i<t3;i++) {
if (x>xmax) goto fin_r;
GenericPixels[x+=ScreenWidth]=c2;
}
goto fin_r;
fin_r1: for (i=0;i<tt1;i++) {
if (x>xmax) goto fin_r;
GenericPixels[x+=ScreenWidth]=c1;
}
for (i=0;i<tt1;i++) {
if (x>xmax) goto fin_r;
GenericPixels[x+=ScreenWidth]=c2;
}
fin_r:;
}
return 0;
}
Текстура разбита на группы, каждая из 8-ми подряд идущих текселей Texture0, Texture1, … Texture7. Также, в Texture01 предрасчитан цвет, средний от Texture0 и Texture1... Это позволяет создать эффект, которому трудно найти точный термин в компьютерной графике, но, например, шахматная доска на большом расстоянии может выглядеть однотонной, а не сверкать пиксельным калейдоскопом.
У Dendy ещё есть особенность, одна палитра на 2x2 тайла. Есть идеи, как с этим быть, и учтено ли это в gif'ке?
Не учтено, рисовал так, как будто эту проблему уже обошли. Идея есть, довольно банальная — раз уж особенности «взгляда под углом» не позволяют разгуляться с текстурированием (максимум — та рябь, которую я попробовал и выложил только на Итче), то нам потребуется максимум два цвета, а то и вообще один (как на гифке, одноцветная заливка). То есть чётные и нечётные тайлы просто делаем разными («залить цветом 3» и «залить цветом 4»), цвета 1 и 2 отдаём под пол и потолок (для скошенных верхнего и нижнего тайлов) и назначаем 3 и 4 в зависимости от того, какие нам попались стены. Получается плюс-минус независимый выбор цветов.
Тут два пути.
Первый это сделать идеальный Hovertank 3D. Но даже так, объединяя две палитры в одну, теряется разнообразие тайлов, получится меньше вариантов скошенностей.
Второй это с текстурированием, Wolf 3D. И вот для него то особенно пригодится идея...
Движок получается почти панорамным. Плавное вращение на месте в приоритете. Движения как получится. Хорошо.
Что, если контролировать позиции, где может находится камера? Просто не допускать ситуации, когда в пределах 6 градусов (2 тайла подряд) видны стены из разных палитр. В редакторе карт рассчитываем допустимые области для перемещения игрока. В недопустимые можно поставить непроходимые объекты.
Как мы написали самый¹ быстрый 2.5D шутерный движок за историю человечества и как он работает