Для того чтобы использовать освещение нам нужно вычислить нормаль для каждой вершины сетки. Напомню, что вектор нормали - это вектор единичной длины, перпендикулярный к поверхности в данной точке этой поверхности и направленный от обратной стороны поверхности к лицевой стороне. Чтобы нарисовать поверхность, нам потребуется ее разбить на множество треугольников. Лицевой стороной треугольника является сторона, которая при рисовании обходится по вершинам против часовой стрелки:
Нормаль к плоскости может быть вычислена как векторное произведение двух векторов A и B, лежащих в данной плоскости:
Как видно из рисунка направление нормали определяется по правилу правого винта при переходе от вектора A к вектору B. При переходе против часовой стрелки нормаль направлена вверх, при переходе по часовой стрелке - вниз. Направление "вверх" у нас совпадает с направление оси Y. В математическом виде компоненты векторного произведения записываются так:
normalX=ay*bz-by*az
normalY=bx*az-ax*bz
normalZ=ax*by-bx*ay
где ax, ay, az - координаты вектора A
bx, by, bz - координаты вектора B
Чтобы вычислить нормаль для конкретной вершины сетки, нам нужно определить для нее векторы A и B. Возьмем вершину с индексами [j] [i] и достроим от нее два вектора - вниз на один шаг - вектор A и вправо один шаг - вектор B:
Зададим для хранения координат векторов нормалей три массива:
float [] normalX=new float[jmax+1][imax+1];
float [] normalY=new float[jmax+1][imax+1];
float [] normalZ=new float[jmax+1][imax+1];
Теперь мы можем вычислить нормаль для вершины [j] [i]:
ax = 0
ay = y [j+1] [i] -y [j] [i]
az = z [j+1] - z [j] = dz шаг сетки по оси Z
bx = x [i+1] - x [i] = dx шаг сетки по оси X
by = y [j] [i+1] - y [j] [i]
bz=0
normalX [j] [i] = ay*bz-by*az = - by*az = - (y [j] [i+1] - y [j] [i]) * dz
normalY [j] [i] = bx*az-ax*bz = bx*az = dx * dz
normalZ [j] [i] = ax*by-bx*ay = - bx*ay = - dx * (y [j+1] [i] - y [j] [i])
По этим формулам мы можем вычислить нормали в диапазоне номеров i от 0 до (imax-1) и j от 0 до (jmax-1). Отдельно нужно рассчитать нормали для крайнего правого столбца (i=imax) и крайней нижней строки (j=jmax), а также для правого нижнего угла (i=imax, j=jmax). Для расчета нормалей нужно правильно подобрать векторы A и B:
Расчет вектора нормали для вершин с индексами [ j ] [ imax ] в крайнем правом столбце:
ax = x [ imax -1] - x [imax] = - dx
ay = y [ j ] [ imax -1] - y [ j ] [ imax]
az = 0
bx = 0
by = y [ j+1 ] [ imax] - y [ j ] [ imax ]
bz = z [ j+1] - z [ j ] = dz
normalX [j] [imax] = ay*bz-by*az = ay*bz = (y [ j ] [ imax -1] - y [ j ] [ imax]) * dz
normalY [j] [imax] = bx*az-ax*bz = - ax*bz = dx * dz
normalZ [j] [imax] = ax*by-bx*ay = ax*by = - dx * (y [ j+1 ] [ imax] - y [ j ] [ imax ])
Расчет вектора нормали для вершин с индексами [ jmax ] [ i ] в крайней нижней строке:
ax = x [ i+1 ] - x [ i ] = dx
ay = y [ jmax ] [ i+1 ] - y [ jmax ] [ i ]
az = 0
bx = 0
by = y [ jmax-1 ] [ i ] - y [ jmax ] [ i ]
bz = z [ jmax -1] - z [ jmax ] = - dz
normalX [jmax] [ i ] = ay*bz-by*az = ay*bz = - (y [ jmax ] [ i+1 ] - y [ jmax ] [ i ]) * dz
normalY [jmax] [ i ] = bx*az-ax*bz = -ax*bz = dx * dz
normalZ [jmax] [ i ] = ax*by-bx*ay = ax*by = dx * (y [ jmax-1 ] [ i ] - y [ jmax ] [ i ])
Расчет вектора нормали для правого нижнего угла [ jmax ] [ imax ]:
ax = 0
ay = y [ jmax-1 ] [ imax ] - y [ jmax ] [ imax ]
az = z [ jmax -1 ] - z [ jmax ] = - dz
bx = x [ imax -1 ] - x [ imax ] = - dx
by = y [ jmax ] [ imax -1 ] - y [ jmax ] [ imax ]
bz = 0
normalX [jmax] [ imax ]=ay*bz-by*az =-by*az =(y [ jmax] [ imax-1] - y [ jmax] [imax]) * dz
normalY [jmax] [ imax ] = bx*az-ax*bz = bx*az = dx * dz
normalZ [jmax] [ imax ] =ax*by-bx*ay = -bx*ay=dx * (y [jmax-1] [imax] - y[jmax ] [imax])
По аналогии с координатами вершин мы должны переписать координаты нормалей последовательно в одномерный массив и отправить его в байтовый буфер:
float [] normal=new float [(jmax+1)*(imax+1)*3];
ByteBuffer nb = ByteBuffer.allocateDirect((jmax+1)*(imax+1)*3*4);
nb.order(ByteOrder.nativeOrder());
normalBuffer = nb.asFloatBuffer();
normalBuffer.position(0);
int k=0;
for (int j=0; j<=jmax; j++){
for (int i=0; i<=imax; i++){
normal[k]=normalX[j][i];
k++;
normal[k]=normalY[j][i];
k++;
normal[k]=normalZ[j][i];
k++;
}
}
normalBuffer.put(normal);
normalBuffer.position(0);
Итак, мы научились по координатам вершин в сетки вычислять вектор нормали для каждой вершины. Оформим расчет нормалей в виде метода:
private void getNormal(){
for (int j=0; j<jmax; j++){
for (int i=0; i<imax; i++){
normalX [j] [i] = - (y [j] [i+1] - y [j] [i]) * dz;
normalY [j] [i] = dx * dz;
normalZ [j] [i] = - dx * (y [j+1] [i] - y [j] [i]);
}
}
//нормаль для i=imax
for (int j=0; j<jmax; j++){
normalX [j] [imax] = (y [ j ] [ imax -1] - y [ j ] [ imax]) * dz;
normalY [j] [imax] = dx * dz;
normalZ [j] [imax] = - dx * (y [ j+1 ] [ imax] - y [ j ] [ imax ]);
}
//нормаль для j=jmax
for (int i=0; i<imax; i++){
normalX [jmax] [ i ] = - (y [ jmax ] [ i+1 ] - y [ jmax ] [ i ]) * dz;
normalY [jmax] [ i ] = dx * dz;
normalZ [jmax] [ i ] = dx * (y [ jmax-1 ] [ i ] - y [ jmax ] [ i ]);
}
//нормаль для i=imax и j=jmax
normalX [jmax] [ imax ]= (y [ jmax] [ imax-1] - y [ jmax] [imax]) * dz;
normalY [jmax] [ imax ] = dx * dz;
normalZ [jmax] [ imax ] = dx * (y [jmax-1] [imax] - y[jmax ] [imax]);
//переписываем координаты вектора нормали в одномерный массив normal
int k=0;
for (int j=0; j<=jmax; j++){
for (int i=0; i<=imax; i++){
normal[k]=normalX[j][i];
k++;
normal[k]=normalY[j][i];
k++;
normal[k]=normalZ[j][i];
k++;
}
}
//отправляем одномерный массив normal в буфер
normalBuffer.put(normal);
normalBuffer.position(0);
} // конец метода
Мы не будем нормализовать нормали на CPU, поручим нормализацию шейдерам.