首页 > 解决方案 > 将 LUT 应用于图像 GLSL

问题描述

我对 CG 很陌生,正在尝试实现一个片段着色器,将 png LUT 应用于图片,但我没有得到预期的结果,现在我的代码使图片非常蓝。
这是一个示例 LUT:[![在此处输入图像描述][1]][1]

当我使用以下代码将 LUT 应用于某些图像时,整个图片变得非常蓝。
代码 :

precision mediump float;

uniform sampler2D u_image;
uniform sampler2D u_lut;

// LUT resolution for one component (4, 8, 16, ...)
uniform float u_resolution;

layout(location = 0) out vec4 fragColor;

in vec2 v_uv;

void main(void)
{
    vec2 tiles = vec2(u_resolution, u_resolution);
    vec2 tilesSize = vec2(u_resolution * u_resolution);
    vec3 imageColor = texture(u_image, v_uv).rgb;
    // min and max are used to interpolate between 2 tiles in the LUT
    float index = imageColor.b * (tiles.x * tiles.y - 1.0);
    float index_min = min(u_resolution - 2.0, floor(index));
    float index_max = index_min + 1.0;

    vec2 tileIndex_min;
    tileIndex_min.y = floor(index_min / tiles.x);
    tileIndex_min.x = floor(index_min - tileIndex_min.y * tiles.x);
    vec2 tileIndex_max;
    tileIndex_max.y = floor(index_max / tiles.x);
    tileIndex_max.x = floor(index_max - tileIndex_max.y * tiles.x);

    vec2 tileUV = mix(0.5/tilesSize, (tilesSize - 0.5)/tilesSize, imageColor.rg);

    vec2 tableUV_1 = tileIndex_min / tiles + tileUV / tiles;
    vec2 tableUV_2 = tileIndex_max / tiles + tileUV / tiles;

    vec3 lookUpColor_1 = texture(u_lut, tableUV_1).rgb;
    vec3 lookUpColor_2 = texture(u_lut, tableUV_2).rgb;
    vec3 lookUpColor = mix(lookUpColor_1, lookUpColor_2, index - index_min);
    fragColor = vec4(lookUpColor, 1.0);
}
 

标签: imageimage-processinggraphicswebgl

解决方案


由于您使用的是 WebGL2,因此您可以只使用 3D 纹理

#version 300 es
precision highp float;

in vec2 vUV;

uniform sampler2D uImage;
uniform mediump sampler3D uLUT;

out vec4 outColor;

void main() {
  vec4 color = texture(uImage, vUV);
  vec3 lutSize = vec3(textureSize(uLUT, 0));
  vec3 uvw = (color.rgb * float(lutSize - 1.0) + 0.5) / lutSize;
  outColor = texture(uLUT, uvw);
}

您可以使用UNPACK_ROW_LENGTHUNPACK_SKIP_PIXELS将 PNG 切片加载到 3D 纹理中

function createLUTTexture(gl, img, filter, size = 8) {
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_3D, tex);
  gl.texStorage3D(gl.TEXTURE_3D, 1, gl.RGBA8, size, size, size);
  // grab  slices
  for (let z = 0; z < size; ++z) {
    gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, z * size);
    gl.pixelStorei(gl.UNPACK_ROW_LENGTH, img.width);
    gl.texSubImage3D(
      gl.TEXTURE_3D, 
      0,     // mip level
      0,     // x
      0,     // y
      z,     // z
      size,  // width,
      size,  // height,
      1,     // depth
      gl.RGBA, 
      gl.UNSIGNED_BYTE,
      img,
    );
  }
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, filter);
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, filter);
  return tex;
}

例子:

const fs = `#version 300 es
precision highp float;

in vec2 vUV;

uniform sampler2D uImage;
uniform mediump sampler3D uLUT;

out vec4 outColor;

void main() {
  vec4 color = texture(uImage, vUV);
  vec3 lutSize = vec3(textureSize(uLUT, 0));
  vec3 uvw = (color.rgb * float(lutSize - 1.0) + 0.5) / lutSize;
  outColor = texture(uLUT, uvw);
}
`;

const vs = `#version 300 es
in vec4 position;
in vec2 texcoord;

out vec2 vUV;

void main() {
  gl_Position = position;
  vUV = texcoord;
}
`;

const lutURLs = [
  'default.png',
  'bgy.png',
  '-black-white.png',
  'blues.png',
  'color-negative.png',
  'funky-contrast.png',
  'googley.png',
  'high-contrast-bw.png',
  'hue-minus-60.png',
  'hue-plus-60.png',
  'hue-plus-180.png',
  'infrared.png',
  'inverse.png',
  'monochrome.png',
  'nightvision.png',
  '-posterize-3-lab.png',
  '-posterize-3-rgb.png',
  '-posterize-4-lab.png',
  '-posterize-more.png',
  '-posterize.png',
  'radioactive.png',
  'red-to-cyan.png',
  'saturated.png',
  'sepia.png',
  'thermal.png',
];
let luts = {};

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));


async function main() {

  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    alert('need WebGL2');
    return;
  }

  const img = await loadImage('https://i.imgur.com/CwQSMv9.jpg');
  document.querySelector('#img').append(img);
  const imgTexture = twgl.createTexture(gl, {src: img, yFlip: true});
 
  // compile shaders, link program, lookup locatios
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  
  // calls gl.createBuffer, gl.bindBuffer, gl.bufferData for
  // a plane with positions, and texcoords
  const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl, 2);
  
  gl.useProgram(programInfo.program);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  gl.activeTexture(gl.TEXTURE0 + 1);

  for (;;) {
    for (let name of lutURLs) {
      let lut = luts[name];
      if (!lut) {
        let url = name;
        let filter = gl.LINEAR;
        if (url.startsWith('-')) {
          filter = gl.NEAREST;
          url = url.substr(1);
        }
        const lutImg = await loadImage(`https://webglsamples.org/color-adjust/adjustments/${url}`);
        lut = {
          name: url,
          texture: createLUTTexture(gl, lutImg, filter),
        };
        luts[name] = lut;
      }

      document.querySelector('#info').textContent = lut.name;

      // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture
      twgl.setUniformsAndBindTextures(programInfo, {
        uImg: imgTexture,
        uLUT: lut.texture,
      });
      
      // calls gl.drawArrays or gl.drawElements
      twgl.drawBufferInfo(gl, bufferInfo);

      await wait(1000);
    }
  }
}
main();

function createLUTTexture(gl, img, filter, size = 8) {
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_3D, tex);
  gl.texStorage3D(gl.TEXTURE_3D, 1, gl.RGBA8, size, size, size);
  // grab  slices
  for (let z = 0; z < size; ++z) {
    gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, z * size);
    gl.pixelStorei(gl.UNPACK_ROW_LENGTH, img.width);
    gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, z * size);
    gl.pixelStorei(gl.UNPACK_ROW_LENGTH, img.width);
    gl.texSubImage3D(
      gl.TEXTURE_3D, 
      0,     // mip level
      0,     // x
      0,     // y
      z,     // z
      size,  // width,
      size,  // height,
      1,     // depth
      gl.RGBA, 
      gl.UNSIGNED_BYTE,
      img,
    );
  }
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, filter);
  gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, filter);
  return tex;
}

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onerror = reject;
    img.onload = () => resolve(img);
    img.crossOrigin = "anonymous";
    img.src = url;
  });
}
.split { display: flex; }
.split>div { padding: 5px; }
img { width: 150px; }
<div class="split">
  <div>
    <div id="img"></div>
    <div>original</div>
  </div>
  <div>
    <canvas width="150" height="198"></canvas>
    <div>LUT Applied: <span id="info"></span></div>
  </div>
</div>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

至于在 2D 中进行操作,有一个视频解释它链接在顶部。如果您想查看一个有效的着色器,还有这个。


推荐阅读