【问题标题】:Generate a normal map with a color map in Three js三个js中生成带颜色贴图的法线贴图
【发布时间】:2022-12-24 11:41:01
【问题描述】:

我开始学习 Three js,我一直在寻找一种将颜色贴图转换为法线贴图的方法。我想做的是尝试根据颜色贴图[image 1]制作法线贴图,方法是根据颜色更改像素,使其看起来像这张法线贴图[image 2]。我不想简单地上传文件,因为我试图尽可能地减少项目的重量。这是我已经尝试过的:

let img = new Image();

img.src = './texture/color.jpg';
img.onload = function () {
    let canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    document.getElementById('body').appendChild(canvas)
    const c = canvas.getContext('2d')
    c.clearRect(0, 0, canvas.width, canvas.height);
    c.fillStyle = '#EEEEEE';
    c.fillRect(0,0,canvas.width, canvas.height);

    //draw background image
    c.drawImage(img, 0, 0);
    //draw a box over the top
    c.fillStyle = "rgba(200, 0, 0, 0)";
    c.fillRect(0, 0, canvas.width, canvas.height);

    draw(c, canvas);
};


function draw(c, canvas)
{


    let img2 = c.getImageData(0, 0, canvas.width,canvas.height);
    console.log(img2.data)
    let d = img2.data;
    for (let i=0; i<d.length; i+=4) {
        let r = d[i];
        let g = d[i+1];
        let b = d[i+2];

        v1 = r < 75 ? r / (50 - r) : r * (255 - r);
        v2 = g > 75 ? g / (50 - g) : g * (255 - g);
        v3 = b > 75 ? b / (50 - b) : b * (255 - b);

        d[i] = v1;
        d[i+1] = v2;
        d[i+2] = v3;
    }
    console.log(img2.data)
    c.putImageData(img2, 0, 0);
}

【问题讨论】:

  • 我不认为,您无法从亮起的“光泽”图像中真正导出正确的法线贴图。如果您不需要颜色贴图的 alpha 通道,您可以在该通道中放置一个高度贴图并使用它来导出法线。
  • 好的,我会尝试看看我能用它做什么。谢谢你们的快速响应!

标签: javascript three.js


【解决方案1】:

我不能说 Three.js 能做什么或不能做什么,因为我真正知道的是它使 3d 资产与画布的集成变得轻而易举。

除此之外,我编写了一个纯 javascript 函数,用于非常有效地从彩色贴图生成法线贴图。但是请记住,这是我大约 4 年前为 C# winforms 编写的函数的 js 的快速移植,它循环遍历给定图像的所有像素以推断转换所需的数据。它很慢。严重的是,缓慢得令人痛苦,这是因为从浏览器的范围内获得漂亮、清晰、准确的法线贴图显然需要花费时间才能方便。

但它完全按照您的意愿行事;从给定的颜色贴图生成一个非常漂亮、干净、精确的法线贴图。

我已将它设置为live demo,这样您就可以在投入使用之前看到/感受到它的实际效果。据我所知,真的没有更快的方法来获取每个像素,计算它的亮度然后构建一个新像素所以,真的,除非你正在处理非常小的法线贴图,否则一个运行在已经很笨重的 js applet浏览器可能不是您的最佳选择。

如果有一种更快的方法可以在浏览器中以达到标准和准确的法线贴图输出的方式迭代数万或数百万像素,那么我会尝试一些狡猾的算法。

我没有实现任何花哨的异步更新,所以你只会知道这是在处理,因为在地图生成完成之前,用于按钮的手形光标不会返回到默认箭头。

我还包含了您的原始图像的 4 个变体供您使用,全部在代码中,4 个中的 3 个被注释掉了。该应用程序以 256x256 开始,因为从中生成法线贴图所需的时间是合理的。它们的大小从 128 到原始的 1024 不等,但我强烈建议不要使用全尺寸变体,因为您的浏览器可能会抱怨操作需要多长时间。

与 C# 变体一样,您可以实现一种方法,客户端可以通过这种方法通过调整亮度参数来控制生成的法线计算的强度。除了 C# 变体之外,这绝对可以作为生成法线贴图的基础,以便在使用 Three.js 应用于几何体时实时可视化。我所说的“实时”是指“无论生成 x 比例尺地图需要多长时间”,因为将完整的地图实际应用到几何体中需要几毫秒。

为了配合现场演示,这里是代码:

归一化.htm

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Normalizer</title>
<link rel="stylesheet" href="css/normalize.css">
</head>
<body onload="startup()">
    <canvas id="input" class="canvas"></canvas>
    <canvas id="output" class="canvas"></canvas>
    <div class="progress">
        <input type="button" class="button" onclick="MakeItSo()" value="Normalize!" />
        <span id="progress">Ready to rock and / or roll on your command!</span>
    </div>
    <script src="js/normalize.js"></script>
</body>
</html>

规范化.css

  html {
    margin: 0px;
    padding: 0px;
    width: 100vw;
    height: 100vh;
  }
  body {
    margin: 0px;
    padding: 0px;
    width: 100vw;
    height: 100vh;
    overflow: hidden;
    font-family: "Arial Unicode MS";
    background: linear-gradient(330deg, rgb(150, 150, 150), rgb(200, 200, 200));
    background-color: rgb(200, 200, 200);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .canvas {
    outline: 1px solid hsla(0, 0%, 0%, 0.25);
  }
  .progress {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 40px;
    display: flex;
  }
  .progress span {
    width: calc(100% - 160px);
    height: 40px;
    line-height: 40px;
    color: hsl(0, 0%, 0%);
    text-align: center;
  }
  input[type="button"] {
    margin: 0px;
    width: 120px;
    height: 40px;
    cursor: pointer;
    display: inline;
  }

标准化.js

// Javascript Normal Map Generator
// Copyright © Brian "BJS3D" Spencer 2022
// Incorporating W3C proposed algorithm to
// calculate pixel brightness in conjunction
// with the Sobel Operator.

window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 60000;

function startup() {

  // lets load the color map first to ensure it's there before we start iterating over its pixels.
  // Then lets make sure the input and output canvases are sized according to the color map's dimensions.

    "use strict";
    var input, output, ctx_i, ctx_o, img, w, h;

    input = document.getElementById("input");
    ctx_i = input.getContext("2d");
    ctx_i.clearRect(0, 0,input.width, input.height);

    img = new Image();
    img.crossOrigin = "Anonymous";
    //img.src = "https://i.imgur.com/a4N2Aj4.jpg"; //128x128 - Tiny but fast.
    img.src = "https://i.imgur.com/wFe4EG7.jpg"; //256x256 - Takes about a minute.
    //img.src = "https://i.imgur.com/bm4pXrn.jpg"; //512x512 - May take 5 or 10 minutes.
    //img.src = "https://i.imgur.com/aUIdxHH.jpg"; //original - Don't do it! It'll take hours.
    img.onload = function () {
        w = img.width;
        h = img.height;
        input.width = w;
        input.height = h;
        ctx_i.drawImage(img, 0, 0);

        output = document.getElementById("output");
        ctx_o = output.getContext("2d");
        output.width = w;
        output.height = h;
    };
}

function MakeItSo(){
  document.getElementById("progress").innerHTML = "Normal map generation in progress...";
  totallyNormal();
}

function totallyNormal() {

  // Now let's prep input to have its pixels violated to calculate their brightness
  // and prep output to receive all those totally violated pixels...

    "use strict";
    var input, output, ctx_i, ctx_o, pixel, x_vector, y_vector, w, h;

    input = document.getElementById("input");
    ctx_i = input.getContext("2d");

    output = document.getElementById("output");
    ctx_o = output.getContext("2d");

    w = input.width - 1;
    h = input.height - 1;

  // Let's begin iterating, using the Sobel Operator to get some really nice pixels to violate...
    for (var y = 0; y < w + 1; y += 1) {
      for (var x = 0; x < h + 1; x += 1) {
        var data = [0, 0, 0, 0, x > 0, x < w, y > 1, y < h, x - 1, x + 1, x, x, y, y, y - 1, y + 1];
        for (var z = 0; z < 4; z +=1) {
          if (data[z + 4]) {
            pixel = ctx_i.getImageData(data[z + 8], data[z + 12], 1, 1);
            data[z] = ((0.299 * (pixel.data[0] / 100)) + (0.587 * (pixel.data[1] / 100)) + (0.114 * (pixel.data[2] / 100)) / 3);
          } else {
            pixel = ctx_i.getImageData(x, y, 1, 1);
            data[z] = ((0.299 * (pixel.data[0] / 100)) + (0.587 * (pixel.data[1] / 100)) + (0.114 * (pixel.data[2] / 100)) / 3);
          }
        }
        x_vector = parseFloat((Math.abs(data[0] - data[1]) + 1) * 0.5) * 255;
        y_vector = parseFloat((Math.abs(data[2] - data[3]) + 1) * 0.5) * 255;
        ctx_o.fillStyle = "rgba(" + x_vector + "," + y_vector + ",255,255)";
        ctx_o.fillRect(x, y, 1, 1);
      }
    }
  document.getElementById("progress").innerHTML = "Normal map generation complete.";
}

【讨论】:

    猜你喜欢
    • 2023-03-11
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-23
    相关资源
    最近更新 更多