#include "PlanetObject.h" #include #include #include "OpenGlExtensions.h" #include "Environment.h" namespace ZL { static constexpr float PLANET_RADIUS = 20000.f; static Vector3f PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, -45000.f }; // --- 1. Дальний диапазон (FAR) --- static constexpr float FAR_Z_NEAR = 2000.0f; static constexpr float FAR_Z_FAR = 200000.0f; // Дистанция, где НАЧИНАЕТСЯ переход FAR -> MIDDLE static constexpr float TRANSITION_FAR_START = 3000.0f; // --- 2. Средний диапазон (MIDDLE) --- static constexpr float MIDDLE_Z_NEAR = 200.f; static constexpr float MIDDLE_Z_FAR = 20000.f; // Дистанция, где ЗАВЕРШАЕТСЯ переход FAR -> MIDDLE и НАЧИНАЕТСЯ MIDDLE -> NEAR static constexpr float TRANSITION_MIDDLE_START = 500.f; // --- 3. Ближний диапазон (NEAR) --- // Новые константы для максимальной точности static constexpr float NEAR_Z_NEAR = 5.0f; static constexpr float NEAR_Z_FAR = 5000.0f; // Дистанция, где ЗАВЕРШАЕТСЯ переход MIDDLE -> NEAR static constexpr float TRANSITION_NEAR_END = 40.f; std::pair calculateZRange(const Vector3f& shipPosition) { // 1. Вычисление расстояния до поверхности планеты const Vector3f planetWorldPosition = PLANET_CENTER_OFFSET; const float distanceToPlanetCenter = (planetWorldPosition - shipPosition).length(); const float distanceToPlanetSurface = distanceToPlanetCenter - PLANET_RADIUS; std::cout << "distanceToPlanetSurface " << distanceToPlanetSurface << std::endl; float currentZNear; float currentZFar; float alpha; // Коэффициент интерполяции для текущего сегмента // Диапазон I: Далеко (FAR) -> Средне (MIDDLE) if (distanceToPlanetSurface >= TRANSITION_FAR_START) { // Полностью дальний диапазон currentZNear = FAR_Z_NEAR; currentZFar = FAR_Z_FAR; } else if (distanceToPlanetSurface > TRANSITION_MIDDLE_START) { // Плавный переход от FAR к MIDDLE const float transitionLength = TRANSITION_FAR_START - TRANSITION_MIDDLE_START; // Нормализация расстояния, 0 при TRANSITION_FAR_START (Далеко), 1 при TRANSITION_MIDDLE_START (Близко) float normalizedDist = (distanceToPlanetSurface - TRANSITION_MIDDLE_START) / transitionLength; alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) // Интерполяция: FAR * (1-alpha) + MIDDLE * alpha currentZNear = FAR_Z_NEAR * (1.0f - alpha) + MIDDLE_Z_NEAR * alpha; currentZFar = FAR_Z_FAR * (1.0f - alpha) + MIDDLE_Z_FAR * alpha; // Диапазон II: Средне (MIDDLE) -> Близко (NEAR) } else if (distanceToPlanetSurface > TRANSITION_NEAR_END) { // Плавный переход от MIDDLE к NEAR const float transitionLength = TRANSITION_MIDDLE_START - TRANSITION_NEAR_END; // Нормализация расстояния, 0 при TRANSITION_MIDDLE_START (Далеко), 1 при TRANSITION_NEAR_END (Близко) float normalizedDist = (distanceToPlanetSurface - TRANSITION_NEAR_END) / transitionLength; alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) // Интерполяция: MIDDLE * (1-alpha) + NEAR * alpha currentZNear = MIDDLE_Z_NEAR * (1.0f - alpha) + NEAR_Z_NEAR * alpha; currentZFar = MIDDLE_Z_FAR * (1.0f - alpha) + NEAR_Z_FAR * alpha; } else { // Полностью ближний диапазон (distanceToPlanetSurface <= TRANSITION_NEAR_END) currentZNear = NEAR_Z_NEAR; currentZFar = NEAR_Z_FAR; } return { currentZNear, currentZFar }; } PerlinNoise::PerlinNoise() { p.resize(256); std::iota(p.begin(), p.end(), 0); // Перемешиваем для случайности (можно задать seed) std::default_random_engine engine(77777); std::shuffle(p.begin(), p.end(), engine); p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения } PerlinNoise::PerlinNoise(uint64_t seed) { p.resize(256); std::iota(p.begin(), p.end(), 0); // Перемешиваем для случайности (используем переданный seed) std::default_random_engine engine(seed); // <-- Использование сида std::shuffle(p.begin(), p.end(), engine); p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения } float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } float PerlinNoise::lerp(float t, float a, float b) { return a + t * (b - a); } float PerlinNoise::grad(int hash, float x, float y, float z) { int h = hash & 15; float u = h < 8 ? x : y; float v = h < 4 ? y : (h == 12 || h == 14 ? x : z); return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); } float PerlinNoise::noise(float x, float y, float z) { int X = (int)floor(x) & 255; int Y = (int)floor(y) & 255; int Z = (int)floor(z) & 255; x -= floor(x); y -= floor(y); z -= floor(z); float u = fade(x); float v = fade(y); float w = fade(z); int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z; int B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)), lerp(u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z))), lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1)))); } float PerlinNoise::getSurfaceHeight(Vector3f pos) { // Частота шума (чем больше, тем больше "холмов") float frequency = 7.0f; // Получаем значение шума (обычно от -1 до 1) float noiseValue = noise(pos.v[0] * frequency, pos.v[1] * frequency, pos.v[2] * frequency); // Переводим из диапазона [-1, 1] в [0, 1] //noiseValue = (noiseValue + 1.0f) * 0.5f; // Масштабируем: хотим отклонение от 1.0 до 1.1 // Значит амплитуда = 0.1 float height = 1.0f + (noiseValue * 0.01f); // * 0.2 даст вариацию высоты return height; } bool PlanetObject::planetIsFar() { const Vector3f planetWorldPosition = PLANET_CENTER_OFFSET; const float distanceToPlanetCenter = (planetWorldPosition - Environment::shipPosition).length(); const float distanceToPlanetSurface = distanceToPlanetCenter - PLANET_RADIUS; return distanceToPlanetSurface > 0.1*TRANSITION_MIDDLE_START; } float PlanetObject::distanceToPlanetSurface() { const Vector3f planetWorldPosition = PLANET_CENTER_OFFSET; const float distanceToPlanetCenter = (planetWorldPosition - Environment::shipPosition).length(); const float distanceToPlanetSurface = distanceToPlanetCenter - PLANET_RADIUS; return distanceToPlanetSurface; } PlanetObject::PlanetObject() : perlin(77777) , colorPerlin(123123) { } void PlanetObject::init() { planetMesh = generateSphere(8); planetMesh.Scale(PLANET_RADIUS); planetMesh.Move(PLANET_CENTER_OFFSET); planetRenderStruct.data = planetMesh; planetRenderStruct.RefreshVBO(); sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand.png", "")); //sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", "")); planetAtmosphere.data = CreateRect2D({ 0.f, 0.f }, { 1.f, 1.f }, 0.f); planetAtmosphere.data.TexCoordData.clear(); planetAtmosphere.data.ColorData.push_back({ 0,0.5,1 }); planetAtmosphere.data.ColorData.push_back({ 0,0.5,1 }); planetAtmosphere.data.ColorData.push_back({ 0,0.5,1 }); planetAtmosphere.data.ColorData.push_back({ 0,0.5,1 }); planetAtmosphere.data.ColorData.push_back({ 0,0.5,1 }); planetAtmosphere.data.ColorData.push_back({ 0,0.5,1 }); planetAtmosphere.data.Scale(1000.f); //planetAtmosphere.data.Scale(PLANET_RADIUS*1.25f); //planetAtmosphere.data.Move(PLANET_CENTER_OFFSET); planetAtmosphere.RefreshVBO(); } void PlanetObject::prepareDrawData() { if (!drawDataDirty) return; drawDataDirty = false; } void PlanetObject::draw(Renderer& renderer) { prepareDrawData(); static const std::string defaultShaderName = "defaultColor"; static const std::string vPositionName = "vPosition"; static const std::string vColorName = "vColor"; static const std::string vNormalName = "vNormal"; static const std::string vTexCoordName = "vTexCoord"; //static const std::string vTexCoord3Name = "vTexCoord3"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vTexCoordName); //renderer.EnableVertexAttribArray(vTexCoord3Name); const auto zRange = calculateZRange(Environment::shipPosition); const float currentZNear = zRange.first; const float currentZFar = zRange.second; // 2. Применяем динамическую матрицу проекции renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); Vector3f lightDir_World = Vector3f(1.0f, 0.0f, -1.0f).normalized(); // В OpenGL/шейдерах удобнее работать с вектором, указывающим ОТ источника к поверхности. Vector3f lightDirection_World = -lightDir_World; // Вектор, направленный от источника Vector3f lightDirection_View; lightDirection_View.v[0] = viewMatrix.m[0] * lightDirection_World.v[0] + viewMatrix.m[4] * lightDirection_World.v[1] + viewMatrix.m[8] * lightDirection_World.v[2]; lightDirection_View.v[1] = viewMatrix.m[1] * lightDirection_World.v[0] + viewMatrix.m[5] * lightDirection_World.v[1] + viewMatrix.m[9] * lightDirection_World.v[2]; lightDirection_View.v[2] = viewMatrix.m[2] * lightDirection_World.v[0] + viewMatrix.m[6] * lightDirection_World.v[1] + viewMatrix.m[10] * lightDirection_World.v[2]; lightDirection_View = lightDirection_View.normalized(); // Нормализуем на всякий случай // Установка uniform-переменной // Предполагается, что RenderUniform3fv определена в Renderer.h renderer.RenderUniform3fv("uLightDirection", &lightDirection_View.v[0]); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, &viewMatrix.m[0]); float dist = distanceToPlanetSurface(); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); renderer.DrawVertexRenderStruct(planetRenderStruct); CheckGlError(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); //renderer.DisableVertexAttribArray(vTexCoord3Name); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); //drawAtmosphere(renderer); } void PlanetObject::drawAtmosphere(Renderer& renderer) { static const std::string defaultShaderName = "defaultAtmosphere"; static const std::string vPositionName = "vPosition"; static const std::string vColorName = "vColor"; glClear(GL_DEPTH_BUFFER_BIT); renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); const auto zRange = calculateZRange(Environment::shipPosition); const float currentZNear = zRange.first; const float currentZFar = zRange.second; // 2. Применяем динамическую матрицу проекции renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); Matrix4f projMatrix = renderer.GetProjectionModelViewMatrix(); Matrix4f modelViewMatrix = renderer.GetCurrentModelViewMatrix(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.PushProjectionMatrix((Environment::width), static_cast(Environment::height), -1, 1); renderer.PushMatrix(); renderer.LoadIdentity(); // Преобразуем центр планеты в экранные координаты int screenX = 0; int screenY = 0; worldToScreenCoordinates( PLANET_CENTER_OFFSET, projMatrix, Environment::width, Environment::height, screenX, screenY ); // Позиция центра в пикселях Vector3f centerPos = Vector3f(static_cast(screenX), static_cast(screenY), 0.0f); renderer.TranslateMatrix(centerPos); renderer.RenderUniform1f("uCenterRadius", 0.f); renderer.RenderUniform3fv("uCenterPos", ¢erPos.v[0]); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения renderer.DrawVertexRenderStruct(planetAtmosphere); glDisable(GL_BLEND); renderer.PopMatrix(); renderer.PopProjectionMatrix(); /* renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); float centerRadius = PLANET_RADIUS*1.2f; Vector3f centerPos = PLANET_CENTER_OFFSET; renderer.RenderUniform1f("uCenterRadius", centerRadius); renderer.RenderUniform3fv("uCenterPos", ¢erPos.v[0]); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения renderer.DrawVertexRenderStruct(planetAtmosphere); glDisable(GL_BLEND); renderer.PopMatrix(); renderer.PopProjectionMatrix();*/ renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::update(float deltaTimeMs) { } std::vector PlanetObject::subdivideTriangles(const std::vector& inputTriangles) { std::vector output; output.reserve(inputTriangles.size() * 4); for (const auto& t : inputTriangles) { Vector3f a = t.data[0]; Vector3f b = t.data[1]; Vector3f c = t.data[2]; // 1. Вычисляем "сырые" середины Vector3f m_ab = (a + b) * 0.5f; Vector3f m_bc = (b + c) * 0.5f; Vector3f m_ac = (a + c) * 0.5f; // 2. Нормализуем их (получаем идеальную сферу радиуса 1) m_ab = m_ab.normalized(); m_bc = m_bc.normalized(); m_ac = m_ac.normalized(); // 3. ПРИМЕНЯЕМ ШУМ: Смещаем точку по радиусу m_ab = m_ab * perlin.getSurfaceHeight(m_ab); m_bc = m_bc * perlin.getSurfaceHeight(m_bc); m_ac = m_ac * perlin.getSurfaceHeight(m_ac); // 4. Формируем новые треугольники output.emplace_back(a, m_ab, m_ac); output.emplace_back(m_ab, b, m_bc); output.emplace_back(m_ac, m_bc, c); output.emplace_back(m_ab, m_bc, m_ac); } return output; } Vector3f PlanetObject::calculateSurfaceNormal(Vector3f p_sphere) { // p_sphere - это нормализованный вектор (точка на идеальной сфере) float theta = 0.01f; // Шаг для "щупанья" соседей (epsilon) // Нам нужно найти два вектора, касательных к сфере в точке p_sphere. // Для этого берем любой вектор (например UP), делаем Cross Product, чтобы получить касательную. // Если p_sphere совпадает с UP, берем RIGHT. Vector3f up = Vector3f(0.0f, 1.0f, 0.0f); if (abs(p_sphere.dot(up)) > 0.99f) { up = Vector3f(1.0f, 0.0f, 0.0f); } Vector3f tangentX = (up.cross(p_sphere)).normalized(); Vector3f tangentY = (p_sphere.cross(tangentX)).normalized(); // Точки на идеальной сфере со смещением Vector3f p0_dir = p_sphere; Vector3f p1_dir = (p_sphere + tangentX * theta).normalized(); Vector3f p2_dir = (p_sphere + tangentY * theta).normalized(); // Реальные точки на искаженной поверхности // p = dir * height(dir) Vector3f p0 = p0_dir * perlin.getSurfaceHeight(p0_dir); Vector3f p1 = p1_dir * perlin.getSurfaceHeight(p1_dir); Vector3f p2 = p2_dir * perlin.getSurfaceHeight(p2_dir); // Вектора от центральной точки к соседям Vector3f v1 = p1 - p0; Vector3f v2 = p2 - p0; // Нормаль - это перпендикуляр к этим двум векторам // Порядок (v2, v1) или (v1, v2) зависит от системы координат, // здесь подбираем так, чтобы нормаль смотрела наружу. return (-v2.cross(v1)).normalized(); } VertexDataStruct PlanetObject::trianglesToVertices(const std::vector& triangles) { VertexDataStruct buffer; buffer.PositionData.reserve(triangles.size() * 3); buffer.NormalData.reserve(triangles.size() * 3); buffer.TexCoordData.reserve(triangles.size() * 3); // <-- РЕЗЕРВИРУЕМ //buffer.TexCoord3Data.reserve(triangles.size() * 3); // <-- РЕЗЕРВИРУЕМ // Стандартные UV-координаты для покрытия одного треугольника // Покрывает текстурой всю грань. const std::array triangleUVs = { Vector2f(0.0f, 0.0f), Vector2f(1.0f, 0.0f), Vector2f(0.0f, 1.0f) }; /* const std::array barycentricCoords = { Vector3f(1.0f, 0.0f, 0.0f), // Вершина 1 Vector3f(0.0f, 1.0f, 0.0f), // Вершина 2 Vector3f(0.0f, 0.0f, 1.0f) // Вершина 3 };*/ for (const auto& t : triangles) { // Проходим по всем 3 вершинам треугольника for (int i = 0; i < 3; i++) { // p_geometry - это уже точка на поверхности (с шумом) Vector3f p_geometry = t.data[i]; // Нам нужно восстановить направление от центра к этой точке, // чтобы передать его в функцию расчета нормали. // Так как (0,0,0) - центр, то normalize(p) даст нам направление. Vector3f p_dir = p_geometry.normalized(); // Считаем аналитическую нормаль для этой конкретной точки Vector3f normal = calculateSurfaceNormal(p_dir); buffer.PositionData.push_back({ p_geometry }); buffer.NormalData.push_back({ normal }); //buffer.TexCoord3Data.push_back(barycentricCoords[i]); buffer.TexCoordData.push_back(triangleUVs[i]); } } return buffer; } VertexDataStruct PlanetObject::generateSphere(int subdivisions) { // 1. Исходный октаэдр std::vector geometry = { Triangle{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, // Top-Front-Right Triangle{{ 0.0f, 1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // Top-Right-Back Triangle{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, // Top-Back-Left Triangle{{ 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // Top-Left-Front Triangle{{ 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // Bottom-Right-Front Triangle{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, // Bottom-Front-Left Triangle{{ 0.0f, -1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // Bottom-Left-Back Triangle{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}} // Bottom-Back-Right }; // 2. ПРИМЕНЯЕМ ШУМ К ИСХОДНЫМ ВЕРШИНАМ for (auto& t : geometry) { for (int i = 0; i < 3; i++) { Vector3f dir = t.data[i].normalized(); t.data[i] = dir * perlin.getSurfaceHeight(dir); } } // 3. Разбиваем N раз for (int i = 0; i < subdivisions; i++) { geometry = subdivideTriangles(geometry); } // 4. Генерируем вершины И НОРМАЛИ с помощью trianglesToVertices // ЭТО ЗАПОЛНИТ PositionData И NormalData VertexDataStruct buffer = trianglesToVertices(geometry); // Теперь нам нужно заполнить ColorData, используя ту же логику, что и раньше. // Сначала резервируем место, так как trianglesToVertices не заполняет ColorData buffer.ColorData.reserve(geometry.size() * 3); // Базовый цвет и параметры const Vector3f baseColor = { 0.498f, 0.416f, 0.0f }; const float colorFrequency = 5.0f; const float colorAmplitude = 0.2f; const Vector3f offsetR = { 0.1f, 0.2f, 0.3f }; const Vector3f offsetG = { 0.5f, 0.4f, 0.6f }; const Vector3f offsetB = { 0.9f, 0.8f, 0.7f }; for (const auto& t : geometry) { for (int i = 0; i < 3; i++) { // ВАЖНО: Мы используем геометрию из t.data[i] для получения направления, // а не PositionData из buffer, поскольку там может не быть нужного порядка. Vector3f p_geometry = t.data[i]; Vector3f dir = p_geometry.normalized(); // Вычисление цветового шума (как вы делали) float noiseR = colorPerlin.noise( (dir.v[0] + offsetR.v[0]) * colorFrequency, (dir.v[1] + offsetR.v[1]) * colorFrequency, (dir.v[2] + offsetR.v[2]) * colorFrequency ); float noiseG = colorPerlin.noise( (dir.v[0] + offsetG.v[0]) * colorFrequency, (dir.v[1] + offsetG.v[1]) * colorFrequency, (dir.v[2] + offsetG.v[2]) * colorFrequency ); float noiseB = colorPerlin.noise( (dir.v[0] + offsetB.v[0]) * colorFrequency, (dir.v[1] + offsetB.v[1]) * colorFrequency, (dir.v[2] + offsetB.v[2]) * colorFrequency ); Vector3f colorOffset = { noiseR * colorAmplitude, noiseG * colorAmplitude, noiseB * colorAmplitude }; Vector3f finalColor = baseColor + colorOffset; finalColor.v[0] = max(0.0f, min(1.0f, finalColor.v[0])); finalColor.v[1] = max(0.0f, min(1.0f, finalColor.v[1])); finalColor.v[2] = max(0.0f, min(1.0f, finalColor.v[2])); // ДОБАВЛЯЕМ ТОЛЬКО ЦВЕТ, так как PositionData и NormalData уже заполнены! buffer.ColorData.push_back(finalColor); } } return buffer; } } // namespace ZL