Ответ 1
Я думаю, что вы никогда не получите точного результата... Я потратил некоторое время на изучение того, как делать 3D-графику с использованием контекста canvas 2d, и я счел целесообразным делать текстурирование с помощью gouraud shading, вычислив соответствующие 2d градиенты и матрицы:
- Твердые многоугольники, конечно же, легкие.
- Заполнение Gouraud возможно только на одном компоненте (т.е. вы не можете иметь треугольник, где каждая вершина является произвольным RGB, заполненным билинейной интерполяцией, но вы можете делать это заполнение, используя, например, три произвольных оттенка одного цвета)
- Линейное отображение текстур может быть выполнено с помощью обрезки и рисования изображений
Я бы реализовал перспективное отображение текстуры с использованием деления сетки (например, на PS1).
Однако я нашел много проблем... например, рисование изображений с матричным преобразованием (необходимое для текстурного сопоставления) довольно неточно на хроме и IMO невозможно получить результат с точным пикселем; в общем, нет способа отключить сглаживание при рисовании на холсте, и это означает, что вы получите видимые сквозные линии при разбиении на треугольники. Я также нашел, что многопроцессорный рендеринг работает очень плохо на хроме (вероятно, из-за того, как реализована рендеринг hw-accellerated).
В целом такой вид рендеринга, безусловно, является стрессом для веб-браузеров и, по-видимому, эти варианты использования (например, странные матрицы) не очень хорошо тестируются. Я даже смог заставить Firefox сбой настолько плохо, что он уничтожил всю систему X susbsystem на моем Ubuntu.
Вы можете увидеть результаты моих усилий здесь или как видео здесь... IMO, несомненно, впечатляет, что это можно сделать в браузере без использования 3D-расширений, но я не думаю, что текущие проблемы будут исправлены в будущем.
В любом случае основная идея, используемая для рисования изображения, чтобы 4 угла заканчивались в определенном положении пикселей, состоит в том, чтобы нарисовать два треугольника, каждый из которых будет использовать билинейную интерполяцию.
В следующем коде я предполагаю, что у вас есть объект изображения texture
и 4 угла, каждый из которых является объектом с полями x,y,u,v
, где x,y
являются пиксельными координатами на целевом холсте, а u,v
- пиксельными координатами на texture
:
function textureMap(ctx, texture, pts) {
var tris = [[0, 1, 2], [2, 3, 0]]; // Split in two triangles
for (var t=0; t<2; t++) {
var pp = tris[t];
var x0 = pts[pp[0]].x, x1 = pts[pp[1]].x, x2 = pts[pp[2]].x;
var y0 = pts[pp[0]].y, y1 = pts[pp[1]].y, y2 = pts[pp[2]].y;
var u0 = pts[pp[0]].u, u1 = pts[pp[1]].u, u2 = pts[pp[2]].u;
var v0 = pts[pp[0]].v, v1 = pts[pp[1]].v, v2 = pts[pp[2]].v;
// Set clipping area so that only pixels inside the triangle will
// be affected by the image drawing operation
ctx.save(); ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2); ctx.closePath(); ctx.clip();
// Compute matrix transform
var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2;
var delta_a = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2;
var delta_b = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2;
var delta_c = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2
- v0*u1*x2 - u0*x1*v2;
var delta_d = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2;
var delta_e = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2;
var delta_f = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2
- v0*u1*y2 - u0*y1*v2;
// Draw the transformed image
ctx.transform(delta_a/delta, delta_d/delta,
delta_b/delta, delta_e/delta,
delta_c/delta, delta_f/delta);
ctx.drawImage(texture, 0, 0);
ctx.restore();
}
}
Эти уродливые странные формулы для всех этих "дельта-переменных" используются для решения двух линейных систем трех уравнений с тремя неизвестными с помощью метода Cramer и Sarrus для детерминант 3x3.
В частности, мы ищем значения a
, b
,... f
, чтобы выполнялись следующие уравнения:
a*u0 + b*v0 + c = x0
a*u1 + b*v1 + c = x1
a*u2 + b*v2 + c = x2
d*u0 + e*v0 + f = y0
d*u1 + e*v1 + f = y1
d*u2 + e*v2 + f = y2
delta
является определителем матрицы
u0 v0 1
u1 v1 1
u2 v2 1
и, например, delta_a
является определителем одной и той же матрицы при замене первого столбца на x0
, x1
, x2
. С их помощью вы можете вычислить a = delta_a / delta
.