#include "PlanetObject.h" #include #include #include "render/OpenGlExtensions.h" #include "Environment.h" #include "StoneObject.h" #include "utils/TaskManager.h" namespace ZL { #ifdef EMSCRIPTEN using std::min; using std::max; #endif extern const char* CONST_ZIP_FILE; Matrix3f GetRotationForTriangle(const Triangle& tri) { Vector3f vA = tri.data[0]; Vector3f vB = tri.data[1]; Vector3f vC = tri.data[2]; // 1. Вычисляем ось X (горизонталь). Vector3f x_axis = (vC - vB).normalized(); // 2. Вычисляем нормаль (ось Z). // Порядок cross product (AB x AC) определит "лицевую" сторону. Vector3f edge1 = vB - vA; Vector3f edge2 = vC - vA; Vector3f z_axis = edge1.cross(edge2).normalized(); // 3. Вычисляем ось Y (вертикаль). // В ортонормированном базисе Y всегда перпендикулярна Z и X. Vector3f y_axis = z_axis.cross(x_axis).normalized(); // 4. Формируем прямую матрицу поворота (Rotation/World Matrix). Matrix3f rot; // Столбец 0: Ось X rot.data()[0] = x_axis.data()[0]; rot.data()[3] = x_axis.data()[1]; rot.data()[6] = x_axis.data()[2]; // Столбец 1: Ось Y rot.data()[1] = y_axis.data()[0]; rot.data()[4] = y_axis.data()[1]; rot.data()[7] = y_axis.data()[2]; // Столбец 2: Ось Z rot.data()[2] = z_axis.data()[0]; rot.data()[5] = z_axis.data()[1]; rot.data()[8] = z_axis.data()[2]; return rot; } PlanetObject::PlanetObject() { } void PlanetObject::init() { // 1. Инициализируем данные (генерация мешей) planetData.init(); // 2. Забираем данные для VBO // Берем максимальный LOD для начальной отрисовки int lodIndex = planetData.getMaxLodIndex(); planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData; //planetRenderStruct.data.PositionData.resize(9); planetRenderStruct.RefreshVBO(); sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand2.png", CONST_ZIP_FILE)); stoneTexture = std::make_unique(CreateTextureDataFromPng("./resources/rockdark3.png", CONST_ZIP_FILE)); // Атмосфера planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData; if (planetAtmosphereRenderStruct.data.PositionData.size() > 0) { planetAtmosphereRenderStruct.RefreshVBO(); } planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex)); //stonesToRender = planetStones.inflate(planetStones.allInstances.size()); stonesToRender.resize(planetStones.allInstances.size()); planetStones.initStatuses(); stoneToBake = planetStones.inflateOne(0, 0.75); } void PlanetObject::update(float deltaTimeMs) { // 1. Проверка порога движения (оптимизация из текущего кода) float movementThreshold = 1.0f; if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) { processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач return; } lastUpdatePos = Environment::shipPosition; // 2. Получаем список видимых треугольников auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition); std::sort(newIndices.begin(), newIndices.end()); // 3. Анализируем, что нужно загрузить for (int triIdx : newIndices) { if (planetStones.statuses[triIdx] == ChunkStatus::Empty) { // Помечаем, чтобы не спамить задачами planetStones.statuses[triIdx] = ChunkStatus::Generating; // Отправляем тяжелую математику в TaskManager taskManager->EnqueueBackgroundTask([this, triIdx]() { // Выполняется в фоновом потоке: только генерация геометрии в RAM float scaleModifier = 1.0f; VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier); // Передаем задачу на загрузку в GPU в очередь главного потока this->EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable { // Проверяем актуальность: если треугольник всё еще в списке видимых auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx); if (it != triangleIndicesToDraw.end()) { stonesToRender[triIdx].data = std::move(data); stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов planetStones.statuses[triIdx] = ChunkStatus::Live; } else { // Если уже не нужен — просто сбрасываем статус planetStones.statuses[triIdx] = ChunkStatus::Empty; } }); }); } } // 4. Очистка (Unload) // Если статус Live, но треугольника нет в новых индексах — освобождаем GPU память for (size_t i = 0; i < planetStones.statuses.size(); ++i) { if (planetStones.statuses[i] == ChunkStatus::Live) { bool isVisible = std::binary_search(newIndices.begin(), newIndices.end(), (int)i); if (!isVisible) { // Очищаем данные и VBO (деструкторы shared_ptr в VertexRenderStruct сделают glDeleteBuffers) stonesToRender[i].data.PositionData.clear(); stonesToRender[i].vao.reset(); stonesToRender[i].positionVBO.reset(); stonesToRender[i].normalVBO.reset(); stonesToRender[i].tangentVBO.reset(); stonesToRender[i].binormalVBO.reset(); stonesToRender[i].colorVBO.reset(); stonesToRender[i].texCoordVBO.reset(); planetStones.statuses[i] = ChunkStatus::Empty; } } } triangleIndicesToDraw = std::move(newIndices); // 5. Выполняем одну задачу из очереди OpenGL (RefreshVBO и т.д.) processMainThreadTasks(); /* float movementThreshold = 1.0f; if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) { return; } lastUpdatePos = Environment::shipPosition; auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition); // Сортировка важна для сравнения векторов std::sort(newIndices.begin(), newIndices.end()); if (newIndices != triangleIndicesToDraw) { triangleIndicesToDraw = std::move(newIndices); }*/ } void PlanetObject::bakeStoneTexture(Renderer& renderer) { glViewport(0, 0, 512, 512); glClearColor(0,0,0, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); static const std::string defaultShaderName2 = "planetBake"; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vTexCoordName); Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // 1. Получаем матрицу вращения (оси в столбцах) Matrix3f mr = GetRotationForTriangle(tr); // 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты // Используем MultMatrixVector(Matrix, Vector). // Если ваша функция считает V * M, то передайте Inverse(mr). Vector3f rA = mr * tr.data[0]; Vector3f rB = mr * tr.data[1]; Vector3f rC = mr * tr.data[2]; // 3. Вычисляем реальные границы треугольника после поворота float minX = min(rA(0), min(rB(0), rC(0))); float maxX = max(rA(0), max(rB(0), rC(0))); float minY = min(rA(1), min(rB(1), rC(1))); float maxY = max(rA(1), max(rB(1), rC(1))); // Находим центр и размеры float width = maxX - minX; float height = maxY - minY; float centerX = (minX + maxX) * 0.5f; float centerY = (minY + maxY) * 0.5f; //width = width * 0.995; //height = height * 0.995; renderer.PushProjectionMatrix( centerX - width*0.5, centerX + width * 0.5, centerY - height * 0.5, centerY + height * 0.5, 150, 200000); renderer.PushMatrix(); renderer.LoadIdentity(); // Сдвигаем камеру по Z renderer.TranslateMatrix(Vector3f{ 0, 0, -45000 }); // Применяем вращение renderer.RotateMatrix(mr); // Извлекаем нормаль треугольника (это 3-й столбец нашей матрицы вращения) Vector3f planeNormal = mr.col(2); renderer.RenderUniform3fv("uPlanePoint", tr.data[0].data()); renderer.RenderUniform3fv("uPlaneNormal", planeNormal.data()); renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation glActiveTexture(GL_TEXTURE0); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // Отсекаем задние грани if (stoneToBake.data.PositionData.size() > 0) { glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); renderer.DrawVertexRenderStruct(stoneToBake); CheckGlError(); } glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::draw(Renderer& renderer) { { if (stoneMapFB == nullptr) { //stoneMapFB = std::make_unique(512, 512, true); stoneMapFB = std::make_unique(512, 512); } stoneMapFB->Bind(); bakeStoneTexture(renderer); stoneMapFB->Unbind(); stoneMapFB->GenerateMipmaps(); } glViewport(0, 0, Environment::width, Environment::height); //-------------------------- drawPlanet(renderer); drawStones(renderer); drawAtmosphere(renderer); } void PlanetObject::drawPlanet(Renderer& renderer) { static const std::string defaultShaderName = "planetLand"; 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 textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray("vTangent"); renderer.EnableVertexAttribArray("vBinormal"); renderer.EnableVertexAttribArray(vTexCoordName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); 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(); renderer.RenderUniform1i("Texture", 0); renderer.RenderUniform1i("BakedTexture", 1); Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании // Позиция камеры (корабля) в мире renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); //renderer.RenderUniform1f("uHeightScale", 0.08f); renderer.RenderUniform1f("uHeightScale", 0.0f); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); // Направление на солнце в мировом пространстве Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); // Направление от центра планеты к игроку для расчета дня/ночи Vector3f playerDirWorld = Environment::shipPosition.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); // Тот же фактор освещенности игрока float playerLightFactor = playerDirWorld.dot(-sunDirWorld); playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); //glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); renderer.DrawVertexRenderStruct(planetRenderStruct); CheckGlError(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray("vTangent"); renderer.DisableVertexAttribArray("vBinormal"); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::drawStones(Renderer& renderer) { static const std::string defaultShaderName2 = "planetStone"; 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 textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vTexCoordName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); 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); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); //std::cout << "uViewPos" << Environment::shipPosition << std::endl; // PlanetObject.cpp, метод drawStones Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); Vector3f playerDirWorld = Environment::shipPosition.normalized(); float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); /* for (int i : triangleIndicesToDraw) //for (int i = 0; i < stonesToRender.size(); i++) { if (stonesToRender[i].data.PositionData.size() > 0) { renderer.DrawVertexRenderStruct(stonesToRender[i]); } }*/ for (int i : triangleIndicesToDraw) { // КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: // Проверяем, что данные не просто существуют, а загружены в GPU if (planetStones.statuses[i] == ChunkStatus::Live) { // Дополнительная проверка на наличие данных (на всякий случай) if (stonesToRender[i].data.PositionData.size() > 0) { renderer.DrawVertexRenderStruct(stonesToRender[i]); } } // Если статус Generating или Empty — мы просто пропускаем этот индекс. // Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO. } CheckGlError(); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); glClear(GL_DEPTH_BUFFER_BIT); } void PlanetObject::drawAtmosphere(Renderer& renderer) { static const std::string defaultShaderName = "defaultAtmosphere"; //static const std::string defaultShaderName = "defaultColor"; static const std::string vPositionName = "vPosition"; static const std::string vNormalName = "vNormal"; //glClear(GL_DEPTH_BUFFER_BIT); glDepthMask(GL_FALSE); renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vNormalName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); float currentZNear = zRange.first; float currentZFar = zRange.second; if (currentZNear < 200) { currentZNear = 200; currentZFar = currentZNear * 100; } // 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 color = { 0.0, 0.5, 1.0 }; renderer.RenderUniform3fv("uColor", color.data()); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data()); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); // В начале drawAtmosphere или как uniform //float time = SDL_GetTicks() / 1000.0f; //renderer.RenderUniform1f("uTime", time); // В методе PlanetObject::drawAtmosphere Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); // Получаем текущую матрицу вида (ModelView без трансляции объекта) // В вашем движке это Environment::inverseShipMatrix Matrix3f viewMatrix2 = Environment::inverseShipMatrix; // Трансформируем вектор света в пространство вида Vector3f viewLightDir = viewMatrix2 * worldLightDir; // Передаем инвертированный вектор (направление НА источник) Vector3f lightToSource = -viewLightDir.normalized(); renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); // В методе Game::drawCubemap //renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f); // Направление света в мировом пространстве для освещения облаков //Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); // 1. Рассчитываем uPlayerLightFactor (как в Game.cpp) Vector3f playerDirWorld = Environment::shipPosition.normalized(); Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); // Насколько игрок на свету float playerLightFactor = playerDirWorld.dot(-sunDirWorld); playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог // 2. ОБЯЗАТЕЛЬНО передаем в шейдер renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); // 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было) renderer.RenderUniform3fv("uSkyColor", color.data()); //Vector3f playerDirWorld = Environment::shipPosition.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); glDisable(GL_BLEND); glDepthMask(GL_TRUE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) { return planetData.distanceToPlanetSurfaceFast(viewerPosition); } } // namespace ZL