apply LUT to an image GLSL - image

I am very new to CG and am trying to implement a fragment shader that applies a png LUT to a picture, but I don't get the expected result, right now my code makes the picture very blue-ish.
Here is an example LUT :
[![enter image description here][1]][1]
When I apply the LUT using the following code to some image the whole picture just turns very blue-ish.
Code :
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);
}

Since you're using WebGL2 you can just use a 3D texture
#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);
}
And you can use UNPACK_ROW_LENGTH and UNPACK_SKIP_PIXELS to load slice of a PNG into a 3D texture
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;
}
Example:
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>
As for doing it in 2D there's this which as a video explaining it linked at the top. There's also this if you want to look at a shader that works.

Related

WebGL2: How to render via shaders onto a TEXTURE_3D

I've read that WebGL2 gives us access to 3d textures. I'm trying to use this to perform some GPU-side computations and then store the output in a 64x64x64 3D texture. The render flow is
compute shader -> render to 3dTexture -> read shader -> render to screen
This is my simple compute shader, the texture's RGB channels should correspond to the XYZ fragment coordinates.
#version 300 es
precision mediump sampler3D;
precision highp float;
layout(location = 0) out highp vec4 pc_fragColor;
void main() {
vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, gl_FragDepth);
pc_fragColor.rgb = color;
pc_fragColor.a = 1.0;
}
However, this only seems to be rendering to a single "slice" of the 3DTexture, where depth is 0.0. All subsequent depths from 1 to 63 px remain black:
I've created a working demo below to demonstrate this issue.
var renderer, target3d, camera;
const SIDE = 64;
var computeMaterial, computeMesh;
var readDataMaterial, readDataMesh,
read3dTargetMaterial, read3dTargetMesh;
var textField = document.querySelector("#textField");
function init() {
// Three.js boilerplate
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x000000), 1.0);
document.body.appendChild(renderer.domElement);
camera = new THREE.Camera();
// Create volume material to render to 3dTexture
computeMaterial = new THREE.RawShaderMaterial({
vertexShader: SIMPLE_VERTEX,
fragmentShader: COMPUTE_FRAGMENT,
uniforms: {
uZCoord: { value: 0.0 },
},
depthTest: false,
});
computeMaterial.type = "VolumeShader";
computeMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), computeMaterial);
// Left material, reads Data3DTexture
readDataMaterial = new THREE.RawShaderMaterial({
vertexShader: SIMPLE_VERTEX,
fragmentShader: READ_FRAGMENT,
uniforms: {
uZCoord: { value: 0.0 },
tDiffuse: { value: create3dDataTexture() }
},
depthTest: false
});
readDataMaterial.type = "DebugShader";
readDataMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), readDataMaterial);
// Right material, reads 3DRenderTarget texture
target3d = new THREE.WebGL3DRenderTarget(SIDE, SIDE, SIDE);
target3d.depthBuffer = false;
read3dTargetMaterial = readDataMaterial.clone();
read3dTargetMaterial.uniforms.tDiffuse.value = target3d.texture;
read3dTargetMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), read3dTargetMaterial);
}
// Creates 3D texture with RGB gradient along the XYZ axes
function create3dDataTexture() {
const d = new Uint8Array( SIDE * SIDE * SIDE * 4 );
window.dat = d;
let i4 = 0;
for ( let z = 0; z < SIDE; z ++ ) {
for ( let y = 0; y < SIDE; y ++ ) {
for ( let x = 0; x < SIDE; x ++ ) {
d[i4 + 0] = (x / SIDE) * 255;
d[i4 + 1] = (y / SIDE) * 255;
d[i4 + 2] = (z / SIDE) * 255;
d[i4 + 3] = 1.0;
i4 += 4;
}
}
}
const texture = new THREE.Data3DTexture( d, SIDE, SIDE, SIDE );
texture.format = THREE.RGBAFormat;
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
texture.unpackAlignment = 1;
texture.needsUpdate = true;
return texture;
}
function onResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate(t) {
// Render volume shader to target3d buffer
renderer.setRenderTarget(target3d);
renderer.render(computeMesh, camera);
// Update z texture coordinate along sine wave
renderer.autoClear = false;
const sinZCoord = Math.sin(t / 1000);
readDataMaterial.uniforms.uZCoord.value = sinZCoord;
read3dTargetMaterial.uniforms.uZCoord.value = sinZCoord;
textField.innerText = sinZCoord.toFixed(4);
// Render data3D texture to screen
renderer.setViewport(0, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4);
renderer.setRenderTarget(null);
renderer.render(readDataMesh, camera);
// Render 3dRenderTarget texture to screen
renderer.setViewport(SIDE * 4, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4);
renderer.setRenderTarget(null);
renderer.render(read3dTargetMesh, camera);
renderer.autoClear = true;
requestAnimationFrame(animate);
}
init();
window.addEventListener("resize", onResize);
requestAnimationFrame(animate);
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
}
#title {
position: absolute;
top: 0;
left: 0;
color: white;
font-family: sans-serif;
}
h3 {
margin: 2px;
}
<div id="title">
<h3>texDepth</h3><h3 id="textField"></h3>
</div>
<script src="https://threejs.org/build/three.js"></script>
<script>
/////////////////////////////////////////////////////////////////////////////////////
// Compute frag shader
// It should output an RGB gradient in the XYZ axes to the 3DRenderTarget
// But gl_FragCoord.z is always 0.5 and gl_FragDepth is always 0.0
const COMPUTE_FRAGMENT = `#version 300 es
precision mediump sampler3D;
precision highp float;
precision highp int;
layout(location = 0) out highp vec4 pc_fragColor;
void main() {
vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, gl_FragDepth);
pc_fragColor.rgb = color;
pc_fragColor.a = 1.0;
}`;
/////////////////////////////////////////////////////////////////////////////////////
// Reader frag shader
// Samples the 3D texture along uv.x, uv.y, and uniform Z coordinate
const READ_FRAGMENT = `#version 300 es
precision mediump sampler3D;
precision highp float;
precision highp int;
layout(location = 0) out highp vec4 pc_fragColor;
in vec2 vUv;
uniform sampler3D tDiffuse;
uniform float uZCoord;
void main() {
vec3 UV3 = vec3(vUv.x, vUv.y, uZCoord);
vec3 diffuse = texture(tDiffuse, UV3).rgb;
pc_fragColor.rgb = diffuse;
pc_fragColor.a = 1.0;
}
`;
/////////////////////////////////////////////////////////////////////////////////////
// Simple vertex shader,
// renders a full-screen quad with UVs without any transformations
const SIMPLE_VERTEX = `#version 300 es
precision highp float;
precision highp int;
in vec2 uv;
in vec3 position;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}`;
/////////////////////////////////////////////////////////////////////////////////////
</script>
On the left side, I’m sampling a Data3DTexture that I created via JavaScript. The blue channel smoothly transitions as I move up and down the depth axis, as expected.
On the right side I’m sampling the WebGL3DRenderTarget texture rendered in the frag shader I showed above. As you can see, it's only rendering to the texture when the depth coordinate is 0.0. All the other “slices” are black.
How can I render my computations to all 64 depth slices? I'm using Three.js for this demo, but I could use any other library like TWGL or vanilla WebGL to achieve the same results.
It doesn't look documented but you can use a second argument to setRenderTarget to set the "layer" of the 3d render target to render to. Here are the changes to make:
When rendering to the render target perform a new render for every layer:
for ( let i = 0; i < SIDE; i ++ ) {
// set the uZCoord color value for the shader
computeMesh.material.uniforms.uZCoord.value = i / (SIDE - 1);
// Set the 3d target "layer" to render into before rendering
renderer.setRenderTarget(target3d, i);
renderer.render(computeMesh, camera);
}
Use the "uZCoord" uniform in the compute fragment shader:
uniform float uZCoord;
void main() {
vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, uZCoord);
pc_fragColor.rgb = color;
pc_fragColor.a = 1.0;
}
Other than that I don't believe theres a way to render to the full 3d volume of the target in a single draw call. This three.js example shows how to do this but with render target arrays, as well:
https://threejs.org/examples/?q=array#webgl2_rendertarget_texture2darray
var renderer, target3d, camera;
const SIDE = 64;
var computeMaterial, computeMesh;
var readDataMaterial, readDataMesh,
read3dTargetMaterial, read3dTargetMesh;
var textField = document.querySelector("#textField");
function init() {
// Three.js boilerplate
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0x000000), 1.0);
document.body.appendChild(renderer.domElement);
camera = new THREE.Camera();
// Create volume material to render to 3dTexture
computeMaterial = new THREE.RawShaderMaterial({
vertexShader: SIMPLE_VERTEX,
fragmentShader: COMPUTE_FRAGMENT,
uniforms: {
uZCoord: { value: 0.0 },
},
depthTest: false,
});
computeMaterial.type = "VolumeShader";
computeMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), computeMaterial);
// Left material, reads Data3DTexture
readDataMaterial = new THREE.RawShaderMaterial({
vertexShader: SIMPLE_VERTEX,
fragmentShader: READ_FRAGMENT,
uniforms: {
uZCoord: { value: 0.0 },
tDiffuse: { value: create3dDataTexture() }
},
depthTest: false
});
readDataMaterial.type = "DebugShader";
readDataMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), readDataMaterial);
// Right material, reads 3DRenderTarget texture
target3d = new THREE.WebGL3DRenderTarget(SIDE, SIDE, SIDE);
target3d.depthBuffer = false;
read3dTargetMaterial = readDataMaterial.clone();
read3dTargetMaterial.uniforms.tDiffuse.value = target3d.texture;
read3dTargetMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), read3dTargetMaterial);
}
// Creates 3D texture with RGB gradient along the XYZ axes
function create3dDataTexture() {
const d = new Uint8Array( SIDE * SIDE * SIDE * 4 );
window.dat = d;
let i4 = 0;
for ( let z = 0; z < SIDE; z ++ ) {
for ( let y = 0; y < SIDE; y ++ ) {
for ( let x = 0; x < SIDE; x ++ ) {
d[i4 + 0] = (x / SIDE) * 255;
d[i4 + 1] = (y / SIDE) * 255;
d[i4 + 2] = (z / SIDE) * 255;
d[i4 + 3] = 1.0;
i4 += 4;
}
}
}
const texture = new THREE.Data3DTexture( d, SIDE, SIDE, SIDE );
texture.format = THREE.RGBAFormat;
texture.minFilter = THREE.NearestFilter;
texture.magFilter = THREE.NearestFilter;
texture.unpackAlignment = 1;
texture.needsUpdate = true;
return texture;
}
function onResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate(t) {
for ( let i = 0; i < SIDE; i ++ ) {
// Render volume shader to target3d buffer
computeMesh.material.uniforms.uZCoord.value = i / ( SIDE - 1 );
renderer.setRenderTarget(target3d, i);
renderer.render(computeMesh, camera);
}
// Update z texture coordinate along sine wave
renderer.autoClear = false;
const sinZCoord = Math.sin(t / 1000);
readDataMaterial.uniforms.uZCoord.value = sinZCoord;
read3dTargetMaterial.uniforms.uZCoord.value = sinZCoord;
textField.innerText = sinZCoord.toFixed(4);
// Render data3D texture to screen
renderer.setViewport(0, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4);
renderer.setRenderTarget(null);
renderer.render(readDataMesh, camera);
// Render 3dRenderTarget texture to screen
renderer.setViewport(SIDE * 4, window.innerHeight - SIDE*4, SIDE * 4, SIDE * 4);
renderer.setRenderTarget(null);
renderer.render(read3dTargetMesh, camera);
renderer.autoClear = true;
requestAnimationFrame(animate);
}
init();
window.addEventListener("resize", onResize);
requestAnimationFrame(animate);
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
}
#title {
position: absolute;
top: 0;
left: 0;
color: white;
font-family: sans-serif;
}
h3 {
margin: 2px;
}
<div id="title">
<h3>texDepth</h3><h3 id="textField"></h3>
</div>
<script src="https://threejs.org/build/three.js"></script>
<script>
/////////////////////////////////////////////////////////////////////////////////////
// Compute frag shader
// It should output an RGB gradient in the XYZ axes to the 3DRenderTarget
// But gl_FragCoord.z is always 0.5 and gl_FragDepth is always 0.0
const COMPUTE_FRAGMENT = `#version 300 es
precision mediump sampler3D;
precision highp float;
precision highp int;
layout(location = 0) out highp vec4 pc_fragColor;
uniform float uZCoord;
void main() {
vec3 color = vec3(gl_FragCoord.x / 64.0, gl_FragCoord.y / 64.0, uZCoord);
pc_fragColor.rgb = color;
pc_fragColor.a = 1.0;
}`;
/////////////////////////////////////////////////////////////////////////////////////
// Reader frag shader
// Samples the 3D texture along uv.x, uv.y, and uniform Z coordinate
const READ_FRAGMENT = `#version 300 es
precision mediump sampler3D;
precision highp float;
precision highp int;
layout(location = 0) out highp vec4 pc_fragColor;
in vec2 vUv;
uniform sampler3D tDiffuse;
uniform float uZCoord;
void main() {
vec3 UV3 = vec3(vUv.x, vUv.y, uZCoord);
vec3 diffuse = texture(tDiffuse, UV3).rgb;
pc_fragColor.rgb = diffuse;
pc_fragColor.a = 1.0;
}
`;
/////////////////////////////////////////////////////////////////////////////////////
// Simple vertex shader,
// renders a full-screen quad with UVs without any transformations
const SIMPLE_VERTEX = `#version 300 es
precision highp float;
precision highp int;
in vec2 uv;
in vec3 position;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}`;
/////////////////////////////////////////////////////////////////////////////////////
</script>

How can textures with transparent spots be correctly applied to multiple stacked plane instances in threejs?

I'm creating 512 instances of the same 1x1 plane with a texture that has transparent areas. The planes are randomly spread around the origin like the image below.
How can the planes in front be drawn after the planes behind so that the transparency of the planes in front take into account the output of the planes from behind?
(with depthTest disabled)
(with depthTest normal)
For reference, the transparency disabled version of the instanced geometry. This proves that the planes are correctly positioned.
Update:
Adding code as asked:
import {
Mesh,
ShaderMaterial,
Vector3,
PlaneBufferGeometry,
EdgesGeometry,
LineBasicMaterial,
LineSegments,
InstancedBufferAttribute,
UniformsLib,
BufferAttribute,
TextureLoader,
InstancedBufferGeometry,
DoubleSide,
} from 'three'
import path from 'path'
import fs from 'fs'
import {
randomValueBetween,
} from '../../utils'
const vertexShader = fs.readFileSync(path.resolve(__dirname, './assets/vertex.glsl'), 'utf8')
const fragmentShader = fs.readFileSync(path.resolve(__dirname, './assets/fragment.glsl'), 'utf8')
const createInstancedAtrributes = (geometry, instanceCount) => {
const startseed = new InstancedBufferAttribute(new Float32Array(instanceCount * 1), 1)
const scale = new InstancedBufferAttribute(new Float32Array(instanceCount * 3), 3)
const offset = new InstancedBufferAttribute(new Float32Array(instanceCount * 2), 2)
const orientationY = new InstancedBufferAttribute(new Float32Array(instanceCount), 1)
const baseScale = 0.5
for (let i = 0; i < instanceCount; i += 1) {
scale.setXYZ(i,
baseScale * randomValueBetween(0.8, 1.3, 1),
baseScale * randomValueBetween(0.8, 1.3, 1),
baseScale * randomValueBetween(0.8, 1.3, 1),
)
orientationY.setX(i, randomValueBetween(0.0, 1.0, 3))
startseed.setX(i, randomValueBetween(1, 3, 1))
}
for (let i = 0; i < instanceCount / 4; i += 4) {
const randomX = randomValueBetween(-3.5, 3.5, 1)
const randomY = randomValueBetween(-3.5, 3.5, 1)
offset.setXY(i, randomX, randomY)
}
geometry.addAttribute('scale', scale)
geometry.addAttribute('offset', offset)
geometry.addAttribute('startseed', offset)
geometry.addAttribute('orientationY', offset)
return { scale, offset }
}
const createInstancedGeometry = (instancePerUnitCount) => {
const geometry = new InstancedBufferGeometry()
geometry.maxInstancedCount = instancePerUnitCount
const shape = new PlaneBufferGeometry(1, 1, 1, 3)
const data = shape.attributes
geometry.addAttribute('position', new BufferAttribute(new Float32Array(data.position.array), 3))
geometry.addAttribute('uv', new BufferAttribute(new Float32Array(data.uv.array), 2))
geometry.addAttribute('normal', new BufferAttribute(new Float32Array(data.normal.array), 3))
geometry.setIndex(new BufferAttribute(new Uint16Array(shape.index.array), 1))
shape.dispose()
createInstancedAtrributes(geometry, instancePerUnitCount)
return geometry
}
export default class GrassDeform extends Mesh {
constructor() {
const geometry = createInstancedGeometry(8 * 256)
const uniforms = {
uTime: {
type: 'f',
value: 0,
},
uMap: {
type: 't',
value: null,
},
}
const textureLoader = new TextureLoader()
textureLoader.load(path.resolve(__dirname, './assets/grass-texture-01.png'), (t) => {
uniforms.uMap.value = t
})
const material = new ShaderMaterial({
uniforms: Object.assign({},
UniformsLib.ambient,
UniformsLib.lights,
uniforms,
),
vertexShader,
fragmentShader,
lights: true,
transparent: true,
side: DoubleSide,
})
super(geometry, material)
this.geometry = geometry
this.material = material
this.up = new Vector3(0, 0, 1)
const lineGeo = new EdgesGeometry(geometry) // or WireframeGeometry
const mat = new LineBasicMaterial({ color: 0xffffff, linewidth: 2 })
const wireframe = new LineSegments(lineGeo, mat)
this.add(wireframe)
this.frustumCulled = false
}
update({ ellapsedTime }) {
this.material.uniforms.uTime.value = ellapsedTime
}
}
And the object is added to the scene like this:
const grass2 = new GrassDeform2()
grass2.position.set(-1, 0, 0.50)
grass2.rotateX(Math.PI / 2)
scene.add(grass2)
dirLight.target = grass2
const animate = (ellapsedTime = 0) => {
stats.begin()
grass2.update({ ellapsedTime })
/// other scene stuff
renderer.render(scene, playerController.camera)
requestAnimationFrame(animate)
}
animate()
The vertex shader:
#if NUM_DIR_LIGHTS > 0
struct DirectionalLight {
vec3 direction;
vec3 color;
int shadow;
float shadowBias;
float shadowRadius;
vec2 shadowMapSize;
};
uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];
#endif
uniform float uTime;
attribute vec2 offset;
attribute vec3 scale;
attribute float startseed;
varying vec2 vUv;
varying vec3 vPosition;
varying vec3 vDirectionalLightDirection;
varying vec3 vDirectionalLightColor;
varying vec3 uNormal;
void main() {
vec3 pos = position * scale;
pos.x += offset.x;
pos.z += offset.y;
pos.y += (scale.y - 1.0) * 0.5;
pos.y = orientationY
vPosition = pos;
uNormal = normal;
vUv = uv;
uNormal = normal;
vDirectionalLightDirection = directionalLights[0].direction;
vDirectionalLightColor = directionalLights[0].color;
float variation = startseed + uTime * 0.002;
float pass = (0.5 + pos.y) * 0.05;
pos.x += sin(pass + variation) * pass;
pos.z += cos(pass + variation + 0.01) * pass;
pos.y += sin(pass + variation - 0.01) * pass;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos,1.0);
}
And the fragment shader (has some extra stuff for light, not added for now):
uniform sampler2D uMap;
varying vec2 vUv;
varying vec3 vPosition;
varying vec3 vDirectionalLightDirection;
varying vec3 vDirectionalLightColor;
varying vec3 uNormal;
void main() {
vec4 map = texture2D(uMap, vUv);
vec3 lightVector = normalize((vDirectionalLightDirection) - vPosition);
float dotNL = dot( uNormal, lightVector );
vec3 baseColor = map.rgb;
vec3 lightedColor = vDirectionalLightColor * 0.6 * dotNL;
if ( map.a < 0.5 ) discard; //!!! THIS WAS THE LINE NEEDED TO SOLVE THE ISSUE
gl_FragColor = vec4( map.rgb , 1 );
}
After applying the change from the final result, the scene looks right!
You can solve your problem with alpha testing. Use a pattern like the following in your fragment shader:
vec4 texelColor = texture2D( map, vUv );
if ( texelColor.a < 0.5 ) discard;
Your material will no longer need to have transparent = true, since you appear to be using a cut-out in which the texture alpha is either 0 or 1.
three.js r.88

Webgl 1 bit per pixel texture

Is there any way to use 1 bit per pixel texture data in WebGL (for example texture 16×16px in 32 bytes of data) or is it necessary to unpack data from 1bpp to 8bpp first?
I have found similar OpenGL related question and answer https://stackoverflow.com/a/15706596/4540236, but it seems to me, that WebGL does not have GL_BITMAP data type constant.
WebGL itself has no one bit per pixel format. You'll have to unpack the data yourself.
You can unpack it to 1 byte per pixel with formats gl.ALPHA or gl.LUMINANCE
you could try creating a fragment shader to unpack, no idea if the precision issues would kill you. Something like
precision mediump float;
varying vec2 v_texcoord;
uniform vec2 u_textureSize;
uniform sampler2D u_texture;
void main() {
float texelCoord = floor(v_texcoord.x * u_textureSize.x);
float bit = mod(texelCoord, 8.0);
float byte = texelCoord / 8.0;
vec2 uv = vec2(byte / u_textureSize.x, v_texcoord.y);
float eightPixels = texture2D(u_texture, uv).r * 255.0;
float pixel = mod(floor(eightPixels / pow(2.0, bit)), 2.0);
gl_FragColor = vec4(pixel, pixel, pixel, 1.0);
}
Hmmm I guess we should test ...
// Using TWGL.js because it's easier and I'm lazy
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: [-1, -1, 0, 1, -1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0],
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
function render(time) {
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
var uniforms = {
u_texture: twgl.createTexture(gl, {
format: gl.LUMINANCE,
min: gl.NEAREST,
mag: gl.NEAREST,
width: 1,
src: [ 0x3C, 0x42, 0xBD, 0x81, 0xA5, 0x81, 0x42, 0x3C, ],
}),
u_textureSize: [8, 8],
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
console.log("foo");
twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);
}
render();
canvas { border: 1px solid red; }
<script src="//twgljs.org/dist/twgl-full.min.js"></script>
<canvas id="c"></canvas>
<script id="vs" type="notjs">
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * 0.5 + 0.5;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec2 v_texcoord;
uniform vec2 u_textureSize;
uniform sampler2D u_texture;
void main() {
float texelCoord = floor(v_texcoord.x * u_textureSize.x);
float bit = mod(texelCoord, 8.0);
float byte = texelCoord / 8.0;
vec2 uv = vec2(byte / u_textureSize.x, v_texcoord.y);
float eightPixels = texture2D(u_texture, uv).r * 255.0;
float pixel = mod(floor(eightPixels / pow(2.0, bit)), 2.0);
gl_FragColor = vec4(pixel, pixel, pixel, 1.0);
}
</script>
As gman answered (https://stackoverflow.com/a/30529136/4540236): WebGL does not support one bit per pixel format.
So I used LUMINANCE format and unpack data manually with:
int off = 0;
for (int i=0;i<packedData.length;i++,off+=8) {
int val = packedData[i];
for (int j=0;j<8;j++) {
unpackedData[off+j] = ((val&(1<<j))!=0)?255:0;
}
}

webGL render to texture depth test fails

I enabled gl.DEPTH_TEST while rendering a shadow map.
I use the rgb packing for the depth information.
When I render the scene into the shadow map, the depth test does not work.
Some farer objects are drawn over some nearer objects.
The shadow map shaders :
<script id="shadow-shader-fs" type="x-shader/x-fragment">
precision mediump float;
const float basis = 128.0;
varying vec4 vPosition;
void main(void) {
float z = vPosition.z / vPosition.w;
float x = floor(z * (basis * basis * basis - 1.0));
float b = floor(mod(x,basis)) / basis;
x = floor(x / basis);
float g = floor(mod(x,basis)) / basis;
x /= basis;
float r = floor(mod(x,basis)) / basis;
x /= basis;
gl_FragColor = vec4(r,g,b,1.0);
}
</script>
<script id="shadow-shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec4 vPosition;
void main(void) {
vPosition = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
gl_Position = vPosition;
}
</script>
This came out :
a Chess 3D
A pawn is rendered over the king.
Setup code :
function SpotLight(gl,loc,dir,minCos,col,en) {
this.location = loc;
this.direction = dir;
this.minCos = minCos;
this.color = col;
this.enabled = en;
this.pMatrix = mat4.create();
this.mvMatrix = mat4.create();
this.childMatrixStack = new MatrixStack(30);
this.frameBuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer);
this.frameBuffer.width = 512;
this.frameBuffer.height = 512;
this.shadowMap = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D,this.shadowMap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.frameBuffer.width, this.frameBuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
this.renderBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.frameBuffer.width, this.frameBuffer.height);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.shadowMap, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.renderbuffer);
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

Drawing an image using WebGL

Please could anyone explain how to draw an image on a WebGL canvas? At the moment, on a regular '2d' canvas, I'm using this:
var canvas = document.getElementById("canvas");
var cxt = canvas.getContext('2d');
img.onload = function() {
cxt.drawImage(img, 0, 0, canvas.width, canvas.height);
}
img.src = "data:image/jpeg;base64," + base64var;
With WebGL it seems you have to use textures, etc. Can anyone explain how I would adapt this code for WebGL? Thanks for the help! :)
If it was up to me I'd do it with a unit quad and a matrix like this
Given these shaders
vertex shader
attribute vec2 a_position;
uniform mat3 u_matrix;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(u_matrix * vec3(a_position, 1), 1);
// because we're using a unit quad we can just use
// the same data for our texcoords.
v_texCoord = a_position;
}
fragment shader
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
I'd create a unit quad and then fill out a 3x3 matrix to translate, rotate, and scale it where I needed it to be
var dstX = 20;
var dstY = 30;
var dstWidth = 64;
var dstHeight = 64;
// convert dst pixel coords to clipspace coords
var clipX = dstX / gl.canvas.width * 2 - 1;
var clipY = dstY / gl.canvas.height * -2 + 1;
var clipWidth = dstWidth / gl.canvas.width * 2;
var clipHeight = dstHeight / gl.canvas.height * -2;
// build a matrix that will stretch our
// unit quad to our desired size and location
gl.uniformMatrix3fv(u_matrixLoc, false, [
clipWidth, 0, 0,
0, clipHeight, 0,
clipX, clipY, 1,
]);
"use strict";
window.onload = main;
function main() {
var image = new Image();
// using a dataURL because stackoverflow
image.src = "data:image/png;base64,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"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
render(image);
}
}
function render(image) {
// Get A WebGL context
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
// look up uniform locations
var u_imageLoc = gl.getUniformLocation(program, "u_image");
var u_matrixLoc = gl.getUniformLocation(program, "u_matrix");
// provide texture coordinates for the rectangle.
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
var dstX = 20;
var dstY = 30;
var dstWidth = 64;
var dstHeight = 64;
// convert dst pixel coords to clipspace coords
var clipX = dstX / gl.canvas.width * 2 - 1;
var clipY = dstY / gl.canvas.height * -2 + 1;
var clipWidth = dstWidth / gl.canvas.width * 2;
var clipHeight = dstHeight / gl.canvas.height * -2;
// build a matrix that will stretch our
// unit quad to our desired size and location
gl.uniformMatrix3fv(u_matrixLoc, false, [
clipWidth, 0, 0,
0, clipHeight, 0,
clipX, clipY, 1,
]);
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
canvas {
border: 1px solid black;
}
<script src="//webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<canvas id="c"></canvas>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform mat3 u_matrix;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(u_matrix * vec3(a_position, 1), 1);
v_texCoord = a_position;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
Here's some articles that will explain the the matrix math
Here is a self-contained script that does what you want, if that helps. All you need to provide is image.jpg.
<canvas id="cvs" width="1024" height="768"></canvas>
<script>
var img, tex, vloc, tloc, vertexBuff, texBuff;
var cvs3d = document.getElementById('cvs');
var ctx3d = cvs3d.getContext('experimental-webgl');
var uLoc;
// create shaders
var vertexShaderSrc =
"attribute vec2 aVertex;" +
"attribute vec2 aUV;" +
"varying vec2 vTex;" +
"uniform vec2 pos;" +
"void main(void) {" +
" gl_Position = vec4(aVertex + pos, 0.0, 1.0);" +
" vTex = aUV;" +
"}";
var fragmentShaderSrc =
"precision highp float;" +
"varying vec2 vTex;" +
"uniform sampler2D sampler0;" +
"void main(void){" +
" gl_FragColor = texture2D(sampler0, vTex);"+
"}";
var vertShaderObj = ctx3d.createShader(ctx3d.VERTEX_SHADER);
var fragShaderObj = ctx3d.createShader(ctx3d.FRAGMENT_SHADER);
ctx3d.shaderSource(vertShaderObj, vertexShaderSrc);
ctx3d.shaderSource(fragShaderObj, fragmentShaderSrc);
ctx3d.compileShader(vertShaderObj);
ctx3d.compileShader(fragShaderObj);
var progObj = ctx3d.createProgram();
ctx3d.attachShader(progObj, vertShaderObj);
ctx3d.attachShader(progObj, fragShaderObj);
ctx3d.linkProgram(progObj);
ctx3d.useProgram(progObj);
ctx3d.viewport(0, 0, 1024, 768);
vertexBuff = ctx3d.createBuffer();
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]), ctx3d.STATIC_DRAW);
texBuff = ctx3d.createBuffer();
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]), ctx3d.STATIC_DRAW);
vloc = ctx3d.getAttribLocation(progObj, "aVertex");
tloc = ctx3d.getAttribLocation(progObj, "aUV");
uLoc = ctx3d.getUniformLocation(progObj, "pos");
img = new Image();
img.src = "image.jpg";
img.onload = function(){
tex = ctx3d.createTexture();
ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MIN_FILTER, ctx3d.NEAREST);
ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MAG_FILTER, ctx3d.NEAREST);
ctx3d.texImage2D(ctx3d.TEXTURE_2D, 0, ctx3d.RGBA, ctx3d.RGBA, ctx3d.UNSIGNED_BYTE, this);
ctx3d.enableVertexAttribArray(vloc);
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
ctx3d.vertexAttribPointer(vloc, 2, ctx3d.FLOAT, false, 0, 0);
ctx3d.enableVertexAttribArray(tloc);
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
ctx3d.vertexAttribPointer(tloc, 2, ctx3d.FLOAT, false, 0, 0);
ctx3d.drawArrays(ctx3d.TRIANGLE_FAN, 0, 4);
};
</script>

Resources