Можно ли генерировать карты нормалей из текстуры? - PullRequest
5 голосов
/ 03 марта 2010

Если у меня есть текстура, то можно ли сгенерировать карту нормалей для этой текстуры, чтобы ее можно было использовать для рельефного отображения?

Или как обычно создаются карты нормалей?

Ответы [ 4 ]

21 голосов
/ 03 марта 2010

Да. Ну вроде. Карты нормалей могут быть точно сделаны из карт высот. Как правило, вы также можете использовать обычную текстуру и получать приличные результаты. Имейте в виду, что существуют другие методы создания карты нормалей, такие как создание модели с высоким разрешением, создание ее с низким разрешением, а затем приведение к лучу, чтобы увидеть, какой нормаль должна быть для модели с низким разрешением, чтобы имитировать модель с более высоким разрешением.

Для отображения высоты на карту нормалей можно использовать оператор Sobel . Этот оператор может быть запущен в x-направлении, сообщая вам x-компонент нормали, а затем в y-направлении, сообщая вам y-компонент. Вы можете вычислить z с помощью 1.0 / strength, где сила - это акцент или "глубина" карты нормалей. Затем возьмите эти x, y и z, бросьте их в вектор, нормализуйте его, и у вас будет норма в этой точке. Закодируйте его в пиксель, и все готово.

Вот несколько старых неполных кодов, демонстрирующих это:

// pretend types, something like this
struct pixel
{
    uint8_t red;
    uint8_t green;
    uint8_t blue;
};

struct vector3d; // a 3-vector with doubles
struct texture; // a 2d array of pixels

// determine intensity of pixel, from 0 - 1
const double intensity(const pixel& pPixel)
{
    const double r = static_cast<double>(pPixel.red);
    const double g = static_cast<double>(pPixel.green);
    const double b = static_cast<double>(pPixel.blue);

    const double average = (r + g + b) / 3.0;

    return average / 255.0;
}

const int clamp(int pX, int pMax)
{
    if (pX > pMax)
    {
        return pMax;
    }
    else if (pX < 0)
    {
        return 0;
    }
    else
    {
        return pX;
    }
}

// transform -1 - 1 to 0 - 255
const uint8_t map_component(double pX)
{
    return (pX + 1.0) * (255.0 / 2.0);
}

texture normal_from_height(const texture& pTexture, double pStrength = 2.0)
{
    // assume square texture, not necessarily true in real code
    texture result(pTexture.size(), pTexture.size());

    const int textureSize = static_cast<int>(pTexture.size());
    for (size_t row = 0; row < textureSize; ++row)
    {
        for (size_t column = 0; column < textureSize; ++column)
        {
            // surrounding pixels
            const pixel topLeft = pTexture(clamp(row - 1, textureSize), clamp(column - 1, textureSize));
            const pixel top = pTexture(clamp(row - 1, textureSize), clamp(column, textureSize));
            const pixel topRight = pTexture(clamp(row - 1, textureSize), clamp(column + 1, textureSize));
            const pixel right = pTexture(clamp(row, textureSize), clamp(column + 1, textureSize));
            const pixel bottomRight = pTexture(clamp(row + 1, textureSize), clamp(column + 1, textureSize));
            const pixel bottom = pTexture(clamp(row + 1, textureSize), clamp(column, textureSize));
            const pixel bottomLeft = pTexture(clamp(row + 1, textureSize), clamp(column - 1, textureSize));
            const pixel left = pTexture(clamp(row, textureSize), clamp(column - 1, textureSize));

            // their intensities
            const double tl = intensity(topLeft);
            const double t = intensity(top);
            const double tr = intensity(topRight);
            const double r = intensity(right);
            const double br = intensity(bottomRight);
            const double b = intensity(bottom);
            const double bl = intensity(bottomLeft);
            const double l = intensity(left);

            // sobel filter
            const double dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
            const double dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
            const double dZ = 1.0 / pStrength;

            math::vector3d v(dX, dY, dZ);
            v.normalize();

            // convert to rgb
            result(row, column) = pixel(map_component(v.x), map_component(v.y), map_component(v.z));
        }
    }

    return result;
}
2 голосов
/ 03 марта 2010

Вероятно, существует много способов создания карты нормалей, но, как говорили другие, вы можете сделать это из карты высот, и 3d-пакеты, такие как XSI / 3dsmax / Blender /, любой из них может вывести один для вас в виде изображения.

Затем вы можете выводить и RGB-изображение с помощью плагина Nvidia для фотошопа, алгоритма для его преобразования или вы можете вывести его напрямую из этих 3d-пакетов с помощью сторонних плагинов.

Имейте в виду, что в некоторых случаях вам может понадобиться инвертировать каналы (R, G или B) из сгенерированной карты нормалей.

Вот ссылка на некоторые ресурсы с примерами и более полным объяснением:

  1. http://developer.nvidia.com/object/photoshop_dds_plugins.html
  2. http://en.wikipedia.org/wiki/Normal_mapping
  3. http://www.vrgeo.org/fileadmin/VRGeo/Bilder/VRGeo_Papers/jgt2002normalmaps.pdf
1 голос
/ 03 марта 2010

Я не думаю, что карты нормалей генерируются из текстуры. они генерируются из модели.

точно так же, как текстурирование позволяет вам определять сложные детали цвета с минимальными полисами (в отличие от использования миллионов уловок и только вершинных цветов для определения цвета в вашей сетке)

Карта нормалей позволяет вам определять сложные детали нормалей с минимальными полисами.

Я считаю, что карты нормалей обычно генерируются из сетки с более высоким разрешением, а затем используются с сеткой с низким разрешением.

Я уверен, что 3D-инструменты, такие как 3ds Max или Maya, а также более специфические инструменты сделают это за вас. в отличие от текстур, я не думаю, что они обычно делаются вручную.

но они генерируются из сетки, а не из текстуры.

0 голосов
/ 11 ноября 2016

Я предлагаю начать с OpenCV из-за его богатства алгоритмов. Вот тот, который я написал, который итеративно размывает карту нормалей и взвешивает их по общему значению, по существу создавая больше топологической карты.

#define ROW_PTR(img, y) ((uchar*)((img).data + (img).step * y))
cv::Mat normalMap(const cv::Mat& bwTexture, double pStrength)
{
    // assume square texture, not necessarily true in real code
    int scale = 1.0;
    int delta = 127;

    cv::Mat sobelZ, sobelX, sobelY;
    cv::Sobel(bwTexture, sobelX, CV_8U, 1, 0, 13, scale, delta, cv::BORDER_DEFAULT);
    cv::Sobel(bwTexture, sobelY, CV_8U, 0, 1, 13, scale, delta, cv::BORDER_DEFAULT);
    sobelZ = cv::Mat(bwTexture.rows, bwTexture.cols, CV_8UC1);

    for(int y=0; y<bwTexture.rows; y++) {
        const uchar *sobelXPtr = ROW_PTR(sobelX, y);
        const uchar *sobelYPtr = ROW_PTR(sobelY, y);
        uchar *sobelZPtr = ROW_PTR(sobelZ, y);

        for(int x=0; x<bwTexture.cols; x++) {
            double Gx = double(sobelXPtr[x]) / 255.0;
            double Gy = double(sobelYPtr[x]) / 255.0;

            double Gz =  pStrength * sqrt(Gx * Gx + Gy * Gy);

            uchar value = uchar(Gz * 255.0);

            sobelZPtr[x] = value;
        }
    }

    std::vector<cv::Mat>planes;

    planes.push_back(sobelX);
    planes.push_back(sobelY);
    planes.push_back(sobelZ);

    cv::Mat normalMap;
    cv::merge(planes, normalMap);

    cv::Mat originalNormalMap = normalMap.clone();

    cv::Mat normalMapBlurred;

    for (int i=0; i<3; i++) {
        cv::GaussianBlur(normalMap, normalMapBlurred, cv::Size(13, 13), 5, 5);
        addWeighted(normalMap, 0.4, normalMapBlurred, 0.6, 0, normalMap);
    }
    addWeighted(originalNormalMap, 0.3, normalMapBlurred, 0.7, 0, normalMap);

    return normalMap;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...