You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js/C++中实现不同编码纹理图的加权相乘并输出sRGB格式

Node.js 解决方案(优先)

问题根源

sharp 默认将 JPEG 视为 sRGB 编码解码,你处理 Linear 格式的 AO 图时输出偏暗,是因为它把 Linear 数据错误地做了 sRGB 的 gamma 压缩。要实现需求,必须手动处理色彩空间转换。

方案一:使用 sharp 手动处理像素

import sharp from 'sharp';

// sRGB 转 Linear 空间
const srgbToLinear = (value) => {
  return value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
};

// Linear 转 sRGB 空间
const linearToSrgb = (value) => {
  return value <= 0.0031308 ? value * 12.92 : 1.055 * Math.pow(value, 1/2.4) - 0.055;
};

async function processTextures(albedoPath, aoPath, outputPath) {
  // 读取两张图的原始像素数据和元信息
  const [albedo, ao] = await Promise.all([
    sharp(albedoPath).raw().toBuffer({ resolveWithObject: true }),
    sharp(aoPath).raw().toBuffer({ resolveWithObject: true })
  ]);

  // 校验尺寸一致性
  if (albedo.info.width !== ao.info.width || albedo.info.height !== ao.info.height) {
    throw new Error("两张图像尺寸必须一致");
  }

  const width = albedo.info.width;
  const height = albedo.info.height;
  const outputBuffer = Buffer.alloc(width * height * 3);

  // 遍历处理每个像素
  for (let i = 0; i < width * height; i++) {
    const idx = i * 3;

    // Albedo:sRGB 转 Linear
    const albedoR = srgbToLinear(albedo.data[idx] / 255);
    const albedoG = srgbToLinear(albedo.data[idx + 1] / 255);
    const albedoB = srgbToLinear(albedo.data[idx + 2] / 255);

    // AO:将 sharp 误解码的 sRGB 值转回 Linear
    const aoR = Math.pow(ao.data[idx] / 255, 2.2);
    const aoG = Math.pow(ao.data[idx + 1] / 255, 2.2);
    const aoB = Math.pow(ao.data[idx + 2] / 255, 2.2);

    // 按 0.5 权重执行相乘运算:Albedo * (0.5 + AO * 0.5)
    const resultR = albedoR * (0.5 + aoR * 0.5);
    const resultG = albedoG * (0.5 + aoG * 0.5);
    const resultB = albedoB * (0.5 + aoB * 0.5);

    // 转回 sRGB 并写入输出缓冲区
    outputBuffer[idx] = Math.round(linearToSrgb(resultR) * 255);
    outputBuffer[idx + 1] = Math.round(linearToSrgb(resultG) * 255);
    outputBuffer[idx + 2] = Math.round(linearToSrgb(resultB) * 255);
  }

  // 输出处理后的图像
  await sharp(outputBuffer, { raw: { width, height, channels: 3 } })
    .jpeg({ quality: 100 })
    .toFile(outputPath);
}

// 调用示例
processTextures('assets/albedo.jpg', 'assets/ao.jpg', 'assets/combined.jpg')
  .then(() => console.log("处理完成"))
  .catch(err => console.error(err));

方案二:使用 jimp(更直观的像素操作)

jimp 对像素级操作的封装更友好,无需手动处理 Buffer:

import Jimp from 'jimp';

// 色彩空间转换函数同前
const srgbToLinear = (value) => {
  return value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
};
const linearToSrgb = (value) => {
  return value <= 0.0031308 ? value * 12.92 : 1.055 * Math.pow(value, 1/2.4) - 0.055;
};

async function processWithJimp(albedoPath, aoPath, outputPath) {
  const [albedo, ao] = await Promise.all([
    Jimp.read(albedoPath),
    Jimp.read(aoPath)
  ]);

  albedo.scan(0, 0, albedo.bitmap.width, albedo.bitmap.height, (x, y, idx) => {
    // Albedo 转 Linear
    const aR = srgbToLinear(albedo.bitmap.data[idx] / 255);
    const aG = srgbToLinear(albedo.bitmap.data[idx + 1] / 255);
    const aB = srgbToLinear(albedo.bitmap.data[idx + 2] / 255);

    // AO 从误解码的 sRGB 转回 Linear
    const oR = Math.pow(ao.bitmap.data[idx] / 255, 2.2);
    const oG = Math.pow(ao.bitmap.data[idx + 1] / 255, 2.2);
    const oB = Math.pow(ao.bitmap.data[idx + 2] / 255, 2.2);

    // 执行权重运算
    const r = aR * (0.5 + oR * 0.5);
    const g = aG * (0.5 + oG * 0.5);
    const b = aB * (0.5 + oB * 0.5);

    // 转回 sRGB 并写入
    albedo.bitmap.data[idx] = Math.round(linearToSrgb(r) * 255);
    albedo.bitmap.data[idx + 1] = Math.round(linearToSrgb(g) * 255);
    albedo.bitmap.data[idx + 2] = Math.round(linearToSrgb(b) * 255);
  });

  await albedo.writeAsync(outputPath);
}

processWithJimp('assets/albedo.jpg', 'assets/ao.jpg', 'assets/combined_jimp.jpg')
  .then(() => console.log("处理完成"))
  .catch(err => console.error(err));

C++ 解决方案(使用 stb 轻量库)

用 stb_image 和 stb_image_write 实现,无需依赖大型图形库:

代码实现

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#include <cmath>
#include <cstdio>

// sRGB 转 Linear
float srgb_to_linear(float value) {
    return value <= 0.04045f ? value / 12.92f : powf((value + 0.055f) / 1.055f, 2.4f);
}

// Linear 转 sRGB
float linear_to_srgb(float value) {
    return value <= 0.0031308f ? value * 12.92f : 1.055f * powf(value, 1.0f / 2.4f) - 0.055f;
}

int main() {
    int width, height, channels;
    // 读取 Albedo 图(sRGB)
    unsigned char* albedo_data = stbi_load("assets/albedo.jpg", &width, &height, &channels, 3);
    if (!albedo_data) {
        fprintf(stderr, "加载 Albedo 图失败\n");
        return 1;
    }

    // 读取 AO 图(Linear,stb 默认按 sRGB 解码)
    unsigned char* ao_data = stbi_load("assets/ao.jpg", &width, &height, &channels, 3);
    if (!ao_data) {
        fprintf(stderr, "加载 AO 图失败\n");
        stbi_image_free(albedo_data);
        return 1;
    }

    // 分配输出缓冲区
    unsigned char* output_data = new unsigned char[width * height * 3];

    // 遍历处理每个像素
    for (int i = 0; i < width * height; ++i) {
        int idx = i * 3;

        float aR = srgb_to_linear(albedo_data[idx] / 255.0f);
        float aG = srgb_to_linear(albedo_data[idx+1] / 255.0f);
        float aB = srgb_to_linear(albedo_data[idx+2] / 255.0f);

        float oR = powf(ao_data[idx] / 255.0f, 2.2f);
        float oG = powf(ao_data[idx+1] / 255.0f, 2.2f);
        float oB = powf(ao_data[idx+2] / 255.0f, 2.2f);

        float r = aR * (0.5f + oR * 0.5f);
        float g = aG * (0.5f + oG * 0.5f);
        float b = aB * (0.5f + oB * 0.5f);

        output_data[idx] = (unsigned char)roundf(linear_to_srgb(r) * 255.0f);
        output_data[idx+1] = (unsigned char)roundf(linear_to_srgb(g) * 255.0f);
        output_data[idx+2] = (unsigned char)roundf(linear_to_srgb(b) * 255.0f);
    }

    // 输出结果图
    stbi_write_jpg("assets/combined.jpg", width, height, 3, output_data, 100);

    // 释放资源
    stbi_image_free(albedo_data);
    stbi_image_free(ao_data);
    delete[] output_data;

    return 0;
}

编译命令

将代码与 stb_image.h、stb_image_write.h 放在同一目录,执行:

g++ -o texture_combine texture_combine.cpp -lm

内容的提问来源于stack exchange,提问作者NeelDigonto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 23:27:32