Reputation: 33
I want to draw an image in WebGL, but downscaled. When I don't scale it, then the image has a good quality, but if I scale it down then it has poor quality.
I read about 'Handling High DPI (Retina) displays in WebGL' from here: http://www.khronos.org/webgl/wiki/HandlingHighDPI and I tryed to do the same. My code in WebGL is:
Initializations:
var devicePixelRatio = window.devicePixelRatio || 1;
gl.canvas.style.width = "800px";
gl.canvas.style.height = "600px";
canvas.width = Math.round(800 * devicePixelRatio);
canvas.height = Math.round(600 * devicePixelRatio);
For drawing:
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
matrix = m3.scale(matrix, 28/800, 35/600); // matrix for scaling texture
Textures:
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);
But this didn't have the same quality like the HTML image. My code in HTML is:
setTimeout(function() {
var imagine = new Image();
imagine.src = 'Tank.png';
imagine.width = '28';
imagine.height = '35';
document.body.appendChild(imagine);
}, 1000);
My code in canvas is:
var imagine = new Image();
imagine.src = 'Tank.png';
imagine.width = '28';
imagine.height = '35';
imagine.onload = function() {
context.drawImage(imagine, 0, 0, 28, 35);
}
context = canvas.getContext('2d');
The difference between quality of images is below ( quality: WebGL < Canvas < HTML)
I know that is not a very big difference, but I really want to show pictures at good quality. In HTML the picture is smoother. I observed that if I zoom in the Chrome browser, than the picture from HTML is increasing in quality, but the WebGL picture it remains at the same resolution and it will decrease quality. And if I refresh the page, to update the devicePixelRatio for WebGL, then the picture has better quality, but my browser is working slower at 500% zoom, I think because it makes the canvas bigger and it has to draw more. But in HTML, if I move the image at 500% zoom, there are not problems, the image is moving well and it has good quality.
In this situation - WebGL image rendering bad quality - the image is not scaled, but I need scaling down pictures.
And compared to this situation - canvas drawImage quality - I put the values as integers, in all three programs.
Final Question:
How can I draw images in webGL as in HTML at the same quality (not seeing that lines from those tracks) and to have good quality even the browser zoom in ? What other possibilities do I have to draw them ? What technology should I use ? I wanted to use WebGL because it has some features that the HTML doesn't have and I wanted to draw some things from scratch, like lines or points.
EDIT 1: This is the picture at the normal resolution.
In this picture it seems that the lines of the tracks are not straight, but they are crossing. If you can look close to the images from Canvas and WebGL, the first lines of the tracks, first on left and first on right, they are straight. More, the picture with Canvas has more straight tracks, even the last ones. The last image of HTML has all tracks of the same shape.
I am sorry because I put very small pictures. It's because I need this scale and I need that pictures not to be bigger. If I would draw bigger pictures then the devicePixelRatio would make pictures to look much better, but I don't use the pictures at normal dimensions. In that way, the problem could have been solved by devicePixelRatio. For smaller dimensions this method with devicePixelRatio doesn't help me. That's why I am looking for another solution.
Upvotes: 0
Views: 2344
Reputation: 54128
If the image is transformed and animated you are best to use linear interpolation with the original image at least 2 times larger than the rendered result.
Make sure the canvas (2D or WebGL) is aligned to device pixels and there is a one to one match with the physical device pixels.
Do not use devicePixelRatio
if it is any other value than 1 or 2 (2 for HDPI, or retina) as other values mean the page has been zoomed and you can not get a one to one pixel to device pixel match. Attempting to scale the canvas (2D or WebGL) will only decrease quality.
This applies best to axis aligned rendered image drawn to pixel boundaries. However it still has some benefit for scaled, rotated, and or unaligned rendering.
The most important part of good quality canvas (2d or webGL) image rendering is the relationship between the original image size and the rendered result.
If you scale the image down to 28 by 35 pixels then the original image MUST be 2* (68, 92) 4* (136, 184), etc... a power of 2 times the size of the final rendered result. NOTE you must consider the GPU RAM cost of larger images.
Important details in the image that must remain as crisp as possible must be aligned to the rendered pixel edges or you end up with blurred details.
The image below is 16* the final size. It has a checkerboard (each square 16 By 16 pixels) over it to shows the size of rendered pixels.
Note how the edges of the tracks, body, barrel, turret are all aligned with final scaled down pixels.
The next image show the rendered results, pixel aligned (WebGL top tow rows have same result in 2D)
From left to right is the size of the original source image, 2 times to 16 times the final size.
Each row uses a different pixel lookup
Nearest. The fastest rendering the pixel nearest the top left of each pixel the final render
Linear. Default for 2d (smoothing = true), webGL (gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
). The pixel color is a linear interpolation of the 4 pixels near (under, right, below, below right) the final pixel.
Log mean. Custom WebGL only. The fragment shader uses the total photon count of all pixels under the rendered pixel to calculate the final RGB pixel color. For original 2 times the size GPU cost is about the same as Linear, For 16 times is only for the high end devices, or low image rendering counts (64 times slower than linear per pixel)
Log Mean +. Uses same fragment shader as Log Mean to render but pre processes (Just in time) the original using a modified sharpen convolution filter to sharpen at 2/3 * invScale
(Log calcs) to increase the visual contrast at low contrast boundaries. The Cost is a one time init cost.
Personally for real-time projects I use second row first column 2 * linear, and when rendering on HDPI or high render counts I use the simplest 1 * linear (not shown).
Most people can not tell the difference between the 2nd, 3rd and last rows. Can you without zooming in on the image?
The next image shows the same method from 2 times to 16 times left to right, then rows linear, nearest, and log mean, +.
All images in this answer were created on GPU accelerated Chrome 80 Win 10 x64. Original tank reference image by the OP.
I just noticed, as I finish, that the Nearest
rotated images all have anti-aliasing on the outer edges. This is an error on my part as I forgot to give the image some transparent padding. The anti-aliasing is due to the polygon edge and not part of the pixel gl.TEXTURE_MIN_FILTER, gl.LINEAR
lookup process. To much work to fix, sorry.
Upvotes: 6
Reputation: 33
If you want your pictures to be drawn more smooth, then you could use mipmaps! Try to make the pictures to have both dimensions a power at 2 and after you can generate mipmaps, like this:
gl.generateMipmap(gl.TEXTURE_2D);
With mipmaps you can choose what WebGL does by setting the texture filtering for each texture. And if you want to your image to be smooth, then you have to change the texture filtering to be LINEAR_MIPMAP_LINEAR. So after you generate the mipmaps you have to write:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
The pictures with the differences are below ( LINEAR vs LINEAR_MIPMAP_LINEAR ):
In first picture the tracks are asymmetric, but the second picture is drawn smooth. I think that the second picture looks more like the HTML picture that you provided.
There is a great tutorial about WebGL Textures: https://webglfundamentals.org/webgl/lessons/webgl-3d-textures.html. There is written that this type of texture filtering is the slowest and it takes 33% more memory, so be aware of that!
Upvotes: 0