Локальные буферы для каждого потока: Вместо записи в общий zBuffer и colorBuffer используйте локальные буферы для каждого потока, а по завершении их слияния объедините результаты. Это устранит конфликты доступа к буферам.

package com.cgvsu.render_engine;

import com.cgvsu.math.Matrix4f;
import com.cgvsu.math.Point2f;
import com.cgvsu.math.Vector2f;
import com.cgvsu.math.Vector3f;
import com.cgvsu.model.Model;
import com.cgvsu.model.Polygon;
import com.cgvsu.model.Texture;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;

import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;

/**

Класс для рендеринга 3D моделей с использованием графического контекста JavaFX.
*/
public class RenderEngine { private boolean enableWireframe = false; // Отрисовка каркаса модели
private boolean enableFillPolygon = true; // Заливка полигонов
private boolean enableInterpolatedLighting = true; // Освещение с интерполяцией нормалей
private boolean enableFlatLighting = false; // Плоское освещение
private boolean enableTextureMapping = false; // Наложение текстур private float lightingCoefficient = 0.9f; // Коэффициент освещения (k)
private Vector3f lightDirection = new Vector3f(1, 1, 1); // Направление света // Одномерный Z-буфер и цветовой буфер
private float[] zBuffer;
private int[] colorBuffer;
private int bufferWidth;
private int bufferHeight; // Предпреобразованные вершины
private Vector3f[] transformedVertices; /**
Устанавливает режим отрисовки каркаса.
@param enableWireframe true для включения, false для отключения.
*/
public void setEnableWireframe(boolean enableWireframe) {
this.enableWireframe = enableWireframe;
}
/**
Устанавливает режим заливки полигонов.
@param enableFillPolygon true для включения, false для отключения.
*/
public void setEnableFillPolygon(boolean enableFillPolygon) {
this.enableFillPolygon = enableFillPolygon;
}
/**
Устанавливает режим освещения с интерполяцией нормалей.
@param enableInterpolatedLighting true для включения, false для отключения.
*/
public void setEnableInterpolatedLighting(boolean enableInterpolatedLighting) {
this.enableInterpolatedLighting = enableInterpolatedLighting;
}
/**
Устанавливает режим плоского освещения без интерполяции нормалей.
@param enableFlatLighting true для включения, false для отключения.
*/
public void setEnableFlatLighting(boolean enableFlatLighting) {
this.enableFlatLighting = enableFlatLighting;
}
/**
Устанавливает режим наложения текстур.
@param enableTextureMapping true для включения, false для отключения.
*/
public void setEnableTextureMapping(boolean enableTextureMapping) {
this.enableTextureMapping = enableTextureMapping;
}
/**
Вычисляет матрицу модель-представление-проекция с учетом камеры и трансформаций модели.
@param camera Объект камеры, содержащий матрицы вида и проекции.
@param rotate Вектор поворота модели.
@param scale Вектор масштабирования модели.
@param translate Вектор переноса модели.
@return Матрица модель-представление-проекция.
*/
private Matrix4f calculateModelViewProjectionMatrix(Camera camera, Vector3f rotate, Vector3f scale, Vector3f translate) {
Matrix4f modelMatrix = GraphicConveyor.scaleRotateTranslate(rotate, scale, translate);
Matrix4f viewMatrix = camera.getViewMatrix();
Matrix4f projectionMatrix = camera.getProjectionMatrix(); modelMatrix.transposition();
viewMatrix.transposition();
projectionMatrix.transposition(); Matrix4f mvpMatrix = new Matrix4f(projectionMatrix);
mvpMatrix.multiply(viewMatrix);
mvpMatrix.multiply(modelMatrix);
return mvpMatrix;
} /**
Инициализирует Z-буфер и цветовой буфер заданными размерами.
@param width Ширина экрана.
@param height Высота экрана.
*/
private void initializeBuffers(int width, int height) {
if (zBuffer == null || width != bufferWidth || height != bufferHeight) {
bufferWidth = width;
bufferHeight = height;
zBuffer = new float[width * height];
colorBuffer = new int[width * height];
transformedVertices = new Vector3f[0]; // Инициализируем пустым массивом
}
Arrays.fill(zBuffer, Float.MAX_VALUE);
Arrays.fill(colorBuffer, 0); // Чёрный цвет по умолчанию
}
/**
Предварительно преобразует все вершины модели.
@param model Модель для рендеринга.
@param mvpMatrix Матрица модель-представление-проекция.
*/
private void transformAllVertices(Model model, Matrix4f mvpMatrix) {
if (transformedVertices.length != model.getVertices().size()) {
transformedVertices = new Vector3f[model.getVertices().size()];
}
for (int i = 0; i < model.getVertices().size(); i++) {
transformedVertices[i] = GraphicConveyor.multiplyMatrix4ByVector3(mvpMatrix, model.getVertices().get(i));
}
}
/**
Выполняет рендеринг 3D модели на графическом контексте.
@param graphicsContext Графический контекст JavaFX для рисования.
@param camera Объект камеры, определяющий вид и проекцию.
@param model Модель для рендеринга.
@param width Ширина области рендеринга.
@param height Высота области рендеринга.
*/
public void render(GraphicsContext graphicsContext, Camera camera, Model model, int width, int height) {
// Инициализация буферов
initializeBuffers(width, height); // Вычисление MVP матрицы
Matrix4f mvpMatrix = calculateModelViewProjectionMatrix(camera, model.getRotate(), model.getScale(), model.getTranslate()); // Предварительное преобразование вершин
transformAllVertices(model, mvpMatrix); // Получение текстуры
Texture texture = model.getTexture(); // Параллельная обработка полигонов
ForkJoinPool.commonPool().submit(() ->
model.getPolygons().parallelStream().forEach(polygon -> {
renderPolygonOptimized(polygon, model, texture);
})
).join(); // Создание WritableImage из colorBuffer
WritableImage writableImage = new WritableImage(width, height);
PixelWriter pixelWriter = writableImage.getPixelWriter();
IntBuffer intBuffer = IntBuffer.wrap(colorBuffer);
pixelWriter.setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), colorBuffer, 0, width); // Отрисовка изображения на Canvas
graphicsContext.drawImage(writableImage, 0, 0); // Отрисовка каркаса, если включено
if (enableWireframe) {
renderWireframe(graphicsContext, model, mvpMatrix, width, height);
}
} /**
Оптимизированный метод рендеринга отдельного многоугольника.
@param polygon Многоугольник для рендеринга.
@param model Модель, содержащая вершины.
@param texture Текстура модели.
*/
private void renderPolygonOptimized(Polygon polygon, Model model, Texture texture) {
int[] vertexIndices = polygon.getVertexIndices().stream().mapToInt(Integer::intValue).toArray();
int[] textureIndices = polygon.getTextureVertexIndices().stream().mapToInt(Integer::intValue).toArray(); // Проверка на треугольники
if (vertexIndices.length < 3) return; // Пропуск неполных многоугольников // Предполагаем, что полигоны треугольники. Для многоугольников с большим количеством вершин необходимо триангулировать.
for (int i = 1; i < vertexIndices.length - 1; i++) {
int idx0 = vertexIndices[0];
int idx1 = vertexIndices[i];
int idx2 = vertexIndices[i + 1];

Vector3f v0 = transformedVertices[idx0];
Vector3f v1 = transformedVertices[idx1];
Vector3f v2 = transformedVertices[idx2];

// Экранные координаты
Point2f p0 = vertexToPoint(v0, bufferWidth, bufferHeight);
Point2f p1 = vertexToPoint(v1, bufferWidth, bufferHeight);
Point2f p2 = vertexToPoint(v2, bufferWidth, bufferHeight);

// Задание текстурных координат
Vector2f tex0 = null, tex1 = null, tex2 = null;
if (enableTextureMapping && texture != null && textureIndices.length >= 3) {
tex0 = model.getTextureVertices().get(textureIndices[0]);
tex1 = model.getTextureVertices().get(textureIndices[i]);
tex2 = model.getTextureVertices().get(textureIndices[i + 1]);
}

// Нормали вершин
Vector3f n0 = null, n1 = null, n2 = null;
if (enableInterpolatedLighting || enableFlatLighting) {
n0 = model.getNormals().get(idx0);
n1 = model.getNormals().get(idx1);
n2 = model.getNormals().get(idx2);
}

// Растеризация треугольника
rasterizeTriangleOptimized(p0, p1, p2, v0.getZ(), v1.getZ(), v2.getZ(),
n0, n1, n2, tex0, tex1, tex2, texture);
}
} /**
Оптимизированная растеризация треугольника.
@param p0 Первая вершина треугольника.
@param p1 Вторая вершина треугольника.
@param p2 Третья вершина треугольника.
@param z0 Глубина первой вершины.
@param z1 Глубина второй вершины.
@param z2 Глубина третьей вершины.
@param n0 Нормаль первой вершины.
@param n1 Нормаль второй вершины.
@param n2 Нормаль третьей вершины.
@param tex0 Текстурная координата первой вершины.
@param tex1 Текстурная координата второй вершины.
@param tex2 Текстурная координата третьей вершины.
@param texture Текстура модели.
*/
private void rasterizeTriangleOptimized(Point2f p0, Point2f p1, Point2f p2,
float z0, float z1, float z2,
Vector3f n0, Vector3f n1, Vector3f n2,
Vector2f tex0, Vector2f tex1, Vector2f tex2,
Texture texture) {
// Вычисление границ треугольника
float minX = Math.max(0, (float) Math.floor(Math.min(p0.getX(), Math.min(p1.getX(), p2.getX()))));
float maxX = Math.min(bufferWidth - 1, (float) Math.ceil(Math.max(p0.getX(), Math.max(p1.getX(), p2.getX()))));
float minY = Math.max(0, (float) Math.floor(Math.min(p0.getY(), Math.min(p1.getY(), p2.getY()))));
float maxY = Math.min(bufferHeight - 1, (float) Math.ceil(Math.max(p0.getY(), Math.max(p1.getY(), p2.getY())))); // Предварительные вычисления для барицентрических координат
float denom = ((p1.getY() - p2.getY()) * (p0.getX() - p2.getX()) +
(p2.getX() - p1.getX()) * (p0.getY() - p2.getY()));
if (denom == 0) return; // Избегаем деления на ноль float invDenom = 1.0f / denom; // Предварительные значения для освещения
Vector3f faceNormal = null;
if (enableFlatLighting) {
faceNormal = calculateFaceNormal(n0, n1, n2);
faceNormal.normalize();
} // Итерация по пикселям треугольника
for (int y = (int) minY; y <= (int) maxY; y++) {
for (int x = (int) minX; x <= (int) maxX; x++) {
// Барицентрические координаты
float lambda0 = ((p1.getY() - p2.getY()) * (x - p2.getX()) +
(p2.getX() - p1.getX()) * (y - p2.getY())) * invDenom;
float lambda1 = ((p2.getY() - p0.getY()) * (x - p2.getX()) +
(p0.getX() - p2.getX()) * (y - p2.getY())) * invDenom;
float lambda2 = 1.0f - lambda0 - lambda1;

// Проверка, находится ли пиксель внутри треугольника
if (lambda0 >= 0 && lambda1 >= 0 && lambda2 >= 0) {
int index = y * bufferWidth + x;
float depth = lambda0 * z0 + lambda1 * z1 + lambda2 * z2;

if (depth < zBuffer[index]) {
// Вычисление цвета пикселя
int color = computePixelColor(lambda0, lambda1, lambda2,
n0, n1, n2, tex0, tex1, tex2, depth, faceNormal, texture);

// Обновление буферов
zBuffer[index] = depth;
colorBuffer[index] = color;
}
}
}
}
} /**
Вычисляет цвет пикселя с учетом текстурирования и освещения.
@param lambda0 Барицентрическая координата λ0.
@param lambda1 Барицентрическая координата λ1.
@param lambda2 Барицентрическая координата λ2.
@param n0 Нормаль первой вершины.
@param n1 Нормаль второй вершины.
@param n2 Нормаль третьей вершины.
@param tex0 Текстурная координата первой вершины.
@param tex1 Текстурная координата второй вершины.
@param tex2 Текстурная координата третьей вершины.
@param depth Глубина пикселя.
@param faceNormal Нормаль грани для плоского освещения.
@param texture Текстура модели.
@return Цвет пикселя в формате ARGB.
*/
private int computePixelColor(float lambda0, float lambda1, float lambda2,
Vector3f n0, Vector3f n1, Vector3f n2,
Vector2f tex0, Vector2f tex1, Vector2f tex2,
float depth, Vector3f faceNormal, Texture texture) {
// Базовый цвет
float r = 0.0f, g = 0.0f, b = 0.0f; // Текстурирование
if (enableTextureMapping && texture != null && tex0 != null && tex1 != null && tex2 != null) {
float u = lambda0 * tex0.getX() + lambda1 * tex1.getX() + lambda2 * tex2.getX();
float v = lambda0 * tex0.getY() + lambda1 * tex1.getY() + lambda2 * tex2.getY();
Color texColor = texture.getColor(u, v);
r = (float) texColor.getRed();
g = (float) texColor.getGreen();
b = (float) texColor.getBlue();
} else {
// Если текстуры нет, используем базовый цвет (например, серый)
r = 0.5f;
g = 0.5f;
b = 0.5f;
} // Освещение
if (enableInterpolatedLighting || enableFlatLighting) {
Vector3f normal;
if (enableInterpolatedLighting && n0 != null && n1 != null && n2 != null) {
normal = interpolateNormal(lambda0, lambda1, lambda2, n0, n1, n2);
} else if (enableFlatLighting && faceNormal != null) {
normal = faceNormal;
} else {
normal = new Vector3f(0, 0, 1); // Нормаль по умолчанию
}

// Диффузное освещение
float diffuse = Math.max(0, normal.dot(lightDirection));
float brightness = lightingCoefficient * diffuse;

// Применение освещения
r *= brightness;
g *= brightness;
b *= brightness;

// Ограничение значений до [0,1]
r = clamp(r);
g = clamp(g);
b = clamp(b);
} // Преобразование цвета в формат ARGB
int argb = ((255 & 0xFF) << 24) | // Альфа
((int) (clamp(r) * 255) << 16) | // Красный
((int) (clamp(g) * 255) << 8) | // Зеленый
((int) (clamp(b) * 255)); // Синий
return argb;
} /**
Интерполирует нормаль на основе барицентрических координат.
@param lambda0 Барицентрическая координата λ0.
@param lambda1 Барицентрическая координата λ1.
@param lambda2 Барицентрическая координата λ2.
@param n0 Нормаль первой вершины.
@param n1 Нормаль второй вершины.
@param n2 Нормаль третьей вершины.
@return Интерполированная нормаль.
*/
private Vector3f interpolateNormal(float lambda0, float lambda1, float lambda2,
Vector3f n0, Vector3f n1, Vector3f n2) {
Vector3f interpolated = new Vector3f(
lambda0 * n0.getX() + lambda1 * n1.getX() + lambda2 * n2.getX(),
lambda0 * n0.getY() + lambda1 * n1.getY() + lambda2 * n2.getY(),
lambda0 * n0.getZ() + lambda1 * n1.getZ() + lambda2 * n2.getZ()
);
interpolated.normalize();
return interpolated;
}
/**
Вычисляет нормаль грани для плоского освещения.
@param n0 Нормаль первой вершины.
@param n1 Нормаль второй вершины.
@param n2 Нормаль третьей вершины.
@return Средняя нормаль грани.
*/
private Vector3f calculateFaceNormal(Vector3f n0, Vector3f n1, Vector3f n2) {
Vector3f averageNormal = new Vector3f(
(n0.getX() + n1.getX() + n2.getX()) / 3,
(n0.getY() + n1.getY() + n2.getY()) / 3,
(n0.getZ() + n1.getZ() + n2.getZ()) / 3
);
averageNormal.normalize();
return averageNormal;
}
/**
Выполняет отрисовку каркаса модели (wireframe) с использованием преобразованных вершин.
@param graphicsContext Графический контекст для рисования.
@param model Модель для рендеринга.
@param mvpMatrix Матрица модель-представление-проекция.
@param width Ширина области рендеринга.
@param height Высота области рендеринга.
*/
private void renderWireframe(GraphicsContext graphicsContext, Model model, Matrix4f mvpMatrix, int width, int height) {
// Предварительно преобразованные вершины уже существуют, используем их
for (Polygon polygon : model.getPolygons()) {
int[] vertexIndices = polygon.getVertexIndices().stream().mapToInt(Integer::intValue).toArray();

Point2f[] screenPoints = new Point2f[vertexIndices.length];

for (int i = 0; i < vertexIndices.length; i++) {
Vector3f transformedVertex = transformedVertices[vertexIndices[i]];
screenPoints[i] = vertexToPoint(transformedVertex, width, height);
}

// Отрисовка линий между вершинами
for (int i = 0; i < vertexIndices.length; i++) {
int next = (i + 1) % vertexIndices.length;
graphicsContext.strokeLine(
screenPoints[i].getX(), screenPoints[i].getY(),
screenPoints[next].getX(), screenPoints[next].getY()
);
}
}
} /**
Преобразует вершину в экранные координаты.
@param vertex Вершина в пространстве камеры.
@param width Ширина экрана.
@param height Высота экрана.
@return Точка на экране.
*/
private Point2f vertexToPoint(Vector3f vertex, int width, int height) {
float x = (vertex.getX() + 1) * 0.5f * width;
float y = (1 - (vertex.getY() + 1) * 0.5f) * height; // Инвертирование Y
return new Point2f(x, y);
}
/**
Ограничивает значение в диапазоне [0, 1].
@param value Исходное значение.
@return Ограниченное значение.
*/
private float clamp(float value) {
return Math.max(0.0f, Math.min(1.0f, value));
}
/**
Ограничивает целочисленное значение в заданном диапазоне.
@param value Исходное значение.
@param min Минимальное значение.
@param max Максимальное значение.
@return Ограниченное значение.
*/
private int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
}