我对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);
}
由于您使用的是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_LENGTH
和UNPACK_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中做这件事,有一个视频链接在顶部解释它。如果你想看一个有效的着色器,也有这个。