gpt4 book ai didi

javascript - 是否有一种更简单(且仍然高效)的方法来使用最近邻重采样来升级 Canvas 渲染?

转载 作者:行者123 更新时间:2023-11-29 17:44:54 25 4
gpt4 key购买 nike

我对这个看似简单的以最近邻格式放大 Canvas 渲染的任务感到困惑,我在这里问过:

How can I properly write this shader function in JS?

目标是像这样转换 3D 渲染输出:

enter image description here

像这样的像素画:

enter image description here

但在那个问题中,我问的是如何正确实现我选择的解决方案(本质上使用着色器来处理放大)。或许我应该问:是否有更简单(并且仍然高效)的方法来做到这一点?

最佳答案

我可以提供两种方法,它们都可以使用最近邻有效地放大或缩小图像。

要手动执行此操作,您应该遍历新缩放图像的每个像素,并使用旧尺寸与新尺寸的比率来计算它们应该使用原始尺寸中的哪个像素。

(我的代码片段使用 .toDataURL() 所以它们可能无法在 chrome 中工作。)

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
#input {
display: none;
}

body {
background-color: black;
}

body > * {
display: block;
margin-top: 10px;
margin-left: auto;
margin-right: auto;
}

img {
background-color: gray;
border: solid 1px white;
border-radius: 10px;
image-rendering: optimizeSpeed;
}

label {
transition: 0.1s;
cursor: pointer;
text-align: center;
font-size: 15px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;

width: 130px;
height: 40px;
line-height: 40px;
border-radius: 10px;

color: white;
background-color: #005500;
box-shadow: 0px 4px #555555;
}

label:hover {
background-color: #007700;
}

label:active {
box-shadow: 0px 1px #555555;
transform: translateY(3px);
}

script {
display: none;
}
</style>
</head>

<body>
<img id="unscaledImage"></img>
<img id="scaledImage"></img>
<input id="scale" type="range" min="1" max="100" value="50"></input>
<label for="input">Upload Image</label>
<input id="input" type="file"></input>

<script type="application/javascript">

void function() {

"use strict";

var unscaledImage = null;
var scaledImage = null;
var scale = null;
var input = null;
var canvas = null;
var ctx = null;
var hasImage = false;

function scaleImage(img,scale) {
var newWidth = (img.width * scale) | 0;
var newHeight = (img.height * scale) | 0;

canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img,0,0);

var unscaledData = ctx.getImageData(0,0,img.width,img.height);
var scaledData = ctx.createImageData(newWidth,newHeight);
var unscaledBitmap = unscaledData.data;
var scaledBitmap = scaledData.data;

var xScale = img.width / newWidth;
var yScale = img.height / newHeight;

for (var x = 0; x < newWidth; ++x) {
for (var y = 0; y < newHeight; ++y) {
var _x = (x * xScale) | 0;
var _y = (y * yScale) | 0;
var scaledIndex = (x + y * newWidth) * 4;
var unscaledIndex = (_x + _y * img.width) * 4;

scaledBitmap[scaledIndex] = unscaledBitmap[unscaledIndex];
scaledBitmap[scaledIndex + 1] = unscaledBitmap[unscaledIndex + 1];
scaledBitmap[scaledIndex + 2] = unscaledBitmap[unscaledIndex + 2];
scaledBitmap[scaledIndex + 3] = 255;
}
}

ctx.clearRect(0,0,canvas.width,canvas.height);
canvas.width = newWidth;
canvas.height = newHeight;
ctx.putImageData(scaledData,0,0);

return canvas.toDataURL();
}

function onImageLoad() {
URL.revokeObjectURL(this.src);
scaledImage.src = scaleImage(this,scale.value * 0.01);
scaledImage.style.width = this.width + "px";
scaledImage.style.height = this.height + "px";
hasImage = true;
}

function onImageError() {
URL.revokeObjectURL(this.src);
}

function onScaleChanged() {
if (hasImage) {
scaledImage.src = scaleImage(unscaledImage,this.value * 0.01);
}
}

function onImageSelected() {
if (this.files[0]) {
unscaledImage.src = URL.createObjectURL(this.files[0]);
}
}

onload = function() {
unscaledImage = document.getElementById("unscaledImage");
scaledImage = document.getElementById("scaledImage");
scale = document.getElementById("scale");
input = document.getElementById("input");
canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");

ctx.imageSmoothingEnabled = false;
unscaledImage.onload = onImageLoad;
unscaledImage.onerror = onImageError;
scale.onmouseup = onScaleChanged;
input.oninput = onImageSelected;
}

}();

</script>
</body>
</html>

或者,使用着色器的一种更快的方法是将您的图像添加到设置为使用最近邻过滤的纹理并将其绘制到四边形上。绘制前可以通过 gl.viewport 控制四边形的大小。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
#file {
display: none;
}

body {
background-color: black;
}

body > * {
display: block;
margin-top: 10px;
margin-left: auto;
margin-right: auto;
}

img {
background-color: gray;
border: solid 1px white;
border-radius: 10px;
image-rendering: optimizeSpeed;
}

label {
transition: 0.1s;
cursor: pointer;
text-align: center;
font-size: 15px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;

width: 130px;
height: 40px;
line-height: 40px;
border-radius: 10px;

color: white;
background-color: #005500;
box-shadow: 0px 4px #555555;
}

label:hover {
background-color: #007700;
}

label:active {
box-shadow: 0px 1px #555555;
transform: translateY(3px);
}

script {
display: none;
}
</style>
</head>

<body>
<img id="unscaledImage"></img>
<img id="scaledImage"></img>
<input id="scale" type="range" min="1" max="100" value="50"></input>
<input id="file" type="file"></input>
<label for="file">Upload Image</label>
<script type="application/javascript">

void function() {

"use strict";

// DOM
var unscaledImage = document.getElementById("unscaledImage");
var scaledImage = document.getElementById("scaledImage");
var scale = document.getElementById("scale");
var file = document.getElementById("file");
var imageUploaded = false;

function onScaleChanged() {
if (imageUploaded) {
scaledImage.src = scaleOnGPU(this.value * 0.01);
}
}

function onImageLoad() {
URL.revokeObjectURL(this.src);
uploadImageToGPU(this);

scaledImage.src = scaleOnGPU(scale.value * 0.01);
scaledImage.style.width = this.width + "px";
scaledImage.style.height = this.height + "px";

imageUploaded = true;
}

function onImageError() {
URL.revokeObjectURL(this.src);
}

function onImageSubmitted() {
if (this.files[0]) {
unscaledImage.src = URL.createObjectURL(this.files[0]);
}
}

// GL
var canvas = document.createElement("canvas");
var gl = canvas.getContext("webgl",{ preserveDrawingBuffer: true })
var program = null;
var buffer = null;
var texture = null;

function uploadImageToGPU(img) {
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,img);
}

function scaleOnGPU(scale) {
canvas.width = (unscaledImage.width * scale) | 0;
canvas.height = (unscaledImage.height * scale) | 0;
gl.viewport(0,0,canvas.width,canvas.height);
gl.drawArrays(gl.TRIANGLES,0,6);

return canvas.toDataURL();
}

// Entry point
onload = function() {
// DOM setup
unscaledImage.onload = onImageLoad;
unscaledImage.onerror = onImageError;
scale.onmouseup = onScaleChanged;
file.oninput = onImageSubmitted;

// GL setup

// Program (shaders)
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
program = gl.createProgram();

gl.shaderSource(vertexShader,`
precision mediump float;

attribute vec2 aPosition;
attribute vec2 aUV;

varying vec2 vUV;

void main() {
vUV = aUV;
gl_Position = vec4(aPosition,0.0,1.0);
}
`);

gl.shaderSource(fragmentShader,`
precision mediump float;

varying vec2 vUV;

uniform sampler2D uTexture;

void main() {
gl_FragColor = texture2D(uTexture,vUV);
}
`);

gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader);
gl.linkProgram(program);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
gl.useProgram(program);

// Buffer
buffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([

1.0, 1.0, 1.0, 0.0,
-1.0, 1.0, 0.0, 0.0,
-1.0,-1.0, 0.0, 1.0,

1.0, 1.0, 1.0, 0.0,
-1.0,-1.0, 0.0, 1.0,
1.0,-1.0, 1.0, 1.0

]),gl.STATIC_DRAW);

gl.vertexAttribPointer(0,2,gl.FLOAT,gl.FALSE,16,0);
gl.vertexAttribPointer(1,2,gl.FLOAT,gl.FALSE,16,8);
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);

// Texture
texture = gl.createTexture();

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D,texture);
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST);
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);
}

onunload = function() {
gl.deleteProgram(program);
gl.deleteBuffer(buffer);
gl.deleteTexture(texture);
}

}();

</script>
</body>
</html>

编辑:为了更好地说明这在实际渲染器中的样子,我创建了另一个示例,将场景绘制到低分辨率帧缓冲区,然后将其放大到 Canvas (关键是设置 min & mag 过滤器到最近的邻居)。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>

body {
background-color: black;
}

.center {
display: block;
margin-top: 30px;
margin-left: auto;
margin-right: auto;
border: solid 1px white;
border-radius: 10px;
}

script {
display: none;
}
</style>
</head>

<body>
<canvas id="canvas" class="center"></canvas>
<input id="scale" type="range" min="1" max="100" value="100" class="center"></input>
<script type="application/javascript">

void function() {

"use strict";

// DOM
var canvasWidth = 180 << 1;
var canvasHeight = 160 << 1;
var canvas = document.getElementById("canvas");
var scale = document.getElementById("scale");

function onScaleChange() {
var scale = this.value * 0.01;

internalWidth = (canvasWidth * scale) | 0;
internalHeight = (canvasHeight * scale) | 0;

gl.uniform1f(uAspectRatio,1.0 / (internalWidth / internalHeight));

gl.deleteFramebuffer(framebuffer);
gl.deleteTexture(framebufferTexture);

[framebuffer,framebufferTexture] = createFramebuffer(internalWidth,internalHeight);
}

// GL
var internalWidth = canvasWidth;
var internalHeight = canvasHeight;
var currentCubeAngle = -0.5;

var gl = canvas.getContext("webgl",{ preserveDrawingBuffer: true, antialias: false }) || console.warn("WebGL Not Supported.");

var cubeProgram = null; // Shaders to draw 3D cube
var scaleProgram = null; // Shaders to scale the frame
var uAspectRatio = null; // Aspect ratio for projection matrix
var uCubeRotation = null; // uniform location for cube program

var cubeBuffer = null; // cube model (attributes)
var scaleBuffer = null; // quad position & UV's

var framebuffer = null; // render target
var framebufferTexture = null; // textured that is rendered to. (The cube is drawn on this)

function createProgram(vertexCode,fragmentCode) {
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

gl.shaderSource(vertexShader,vertexCode);
gl.shaderSource(fragmentShader,fragmentCode);
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);

try {
if (!gl.getShaderParameter(vertexShader,gl.COMPILE_STATUS)) { throw "VS: " + gl.getShaderInfoLog(vertexShader); }
if (!gl.getShaderParameter(fragmentShader,gl.COMPILE_STATUS)) { throw "FS: " + gl.getShaderInfoLog(fragmentShader); }
} catch(error) {
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
console.error(error);
}

var program = gl.createProgram();

gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
gl.linkProgram(program);

return program;
}

function createBuffer(data) {
var buffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bufferData(gl.ARRAY_BUFFER,Float32Array.from(data),gl.STATIC_DRAW);

return buffer;
}

function createFramebuffer(width,height) {
var texture = gl.createTexture();

gl.bindTexture(gl.TEXTURE_2D,texture);
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,width,height,0,gl.RGBA,gl.UNSIGNED_BYTE,null);

var _framebuffer = gl.createFramebuffer();

gl.bindFramebuffer(gl.FRAMEBUFFER,_framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,texture,0);

gl.bindTexture(gl.TEXTURE_2D,null);
gl.bindFramebuffer(gl.FRAMEBUFFER,null);

return [_framebuffer,texture];
}

function loop() {
//
currentCubeAngle += 0.01;

if (currentCubeAngle > 2.0 * Math.PI) {
currentCubeAngle = 0.0;
}

//
gl.bindFramebuffer(gl.FRAMEBUFFER,framebuffer);
gl.bindTexture(gl.TEXTURE_2D,null);
gl.viewport(0,0,internalWidth,internalHeight);
gl.useProgram(cubeProgram);
gl.uniform1f(uCubeRotation,currentCubeAngle);
gl.bindBuffer(gl.ARRAY_BUFFER,cubeBuffer);
gl.vertexAttribPointer(0,3,gl.FLOAT,gl.FALSE,36,0);
gl.vertexAttribPointer(1,3,gl.FLOAT,gl.FALSE,36,12);
gl.vertexAttribPointer(2,3,gl.FLOAT,gl.FALSE,36,24);
gl.enableVertexAttribArray(2);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,24);

gl.bindFramebuffer(gl.FRAMEBUFFER,null);
gl.bindTexture(gl.TEXTURE_2D,framebufferTexture);
gl.viewport(0,0,canvasWidth,canvasHeight);
gl.useProgram(scaleProgram);
gl.bindBuffer(gl.ARRAY_BUFFER,scaleBuffer);
gl.vertexAttribPointer(0,2,gl.FLOAT,gl.FALSE,16,0);
gl.vertexAttribPointer(1,2,gl.FLOAT,gl.FALSE,16,8);
gl.disableVertexAttribArray(2);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,6);

//
requestAnimationFrame(loop);
}

// Entry Point
onload = function() {
// DOM
canvas.width = canvasWidth;
canvas.height = canvasHeight;
scale.onmouseup = onScaleChange;

// GL
gl.clearColor(0.5,0.5,0.5,1.0);
gl.enable(gl.CULL_FACE);
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);

cubeProgram = createProgram(`
precision mediump float;

const float LIGHT_ANGLE = 0.5;
const vec3 LIGHT_DIR = vec3(sin(LIGHT_ANGLE),0.0,cos(LIGHT_ANGLE));

const mat4 OFFSET = mat4(
1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
0.0,0.0,-5.0,1.0
);

const float FOV = 0.698132;
const float Z_NEAR = 1.0;
const float Z_FAR = 20.0;
const float COT_FOV = 1.0 / tan(FOV * 0.5);
const float Z_FACTOR_1 = -(Z_FAR / (Z_FAR - Z_NEAR));
const float Z_FACTOR_2 = -((Z_NEAR * Z_FAR) / (Z_FAR - Z_NEAR));

attribute vec3 aPosition;
attribute vec3 aNormal;
attribute vec3 aColour;

varying vec3 vColour;

uniform float uAspectRatio;
uniform float uRotation;

void main() {
float s = sin(uRotation);
float c = cos(uRotation);

mat4 PROJ = mat4(
COT_FOV * uAspectRatio,0.0,0.0,0.0,
0.0,COT_FOV,0.0,0.0,
0.0,0.0,Z_FACTOR_1,Z_FACTOR_2,
0.0,0.0,-1.0,0.0
);

mat4 rot = mat4(
c ,0.0,-s ,0.0,
0.0,1.0,0.0,0.0,
s ,0.0,c ,0.0,
0.0,0.0,0.0,1.0
);

vec3 normal = (vec4(aNormal,0.0) * rot).xyz;

vColour = aColour * max(0.4,dot(normal,LIGHT_DIR));
gl_Position = PROJ * OFFSET * rot * vec4(aPosition,1.0);
}
`,`
precision mediump float;

varying vec3 vColour;

void main() {
gl_FragColor = vec4(vColour,1.0);
}
`);

uAspectRatio = gl.getUniformLocation(cubeProgram,"uAspectRatio");
uCubeRotation = gl.getUniformLocation(cubeProgram,"uRotation");

gl.useProgram(cubeProgram);
gl.uniform1f(uAspectRatio,1.0 / (internalWidth / internalHeight));

scaleProgram = createProgram(`
precision mediump float;

attribute vec2 aPosition;
attribute vec2 aUV;

varying vec2 vUV;

void main() {
vUV = aUV;
gl_Position = vec4(aPosition,0.0,1.0);
}
`,`
precision mediump float;

varying vec2 vUV;

uniform sampler2D uTexture;

void main() {
gl_FragColor = texture2D(uTexture,vUV);
}
`);

cubeBuffer = createBuffer([
// Position Normal Colour

// Front
1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,0.0,0.6,
-1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,0.0,0.6,
-1.0,-1.0, 1.0, 0.0, 0.0, 1.0, 0.0,0.0,0.6,

-1.0,-1.0, 1.0, 0.0, 0.0, 1.0, 0.0,0.0,0.6,
1.0,-1.0, 1.0, 0.0, 0.0, 1.0, 0.0,0.0,0.6,
1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0,0.0,0.6,

// Back
-1.0,-1.0,-1.0, 0.0, 0.0,-1.0, 0.0,0.0,0.6,
-1.0, 1.0,-1.0, 0.0, 0.0,-1.0, 0.0,0.0,0.6,
1.0, 1.0,-1.0, 0.0, 0.0,-1.0, 0.0,0.0,0.6,

1.0, 1.0,-1.0, 0.0, 0.0,-1.0, 0.0,0.0,0.6,
1.0,-1.0,-1.0, 0.0, 0.0,-1.0, 0.0,0.0,0.6,
-1.0,-1.0,-1.0, 0.0, 0.0,-1.0, 0.0,0.0,0.6,

// Left
-1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0,0.0,0.6,
-1.0,-1.0,-1.0, 1.0, 0.0, 0.0, 0.0,0.0,0.6,
-1.0,-1.0, 1.0, 1.0, 0.0, 0.0, 0.0,0.0,0.6,

-1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0,0.0,0.6,
-1.0, 1.0,-1.0, 1.0, 0.0, 0.0, 0.0,0.0,0.6,
-1.0,-1.0,-1.0, 1.0, 0.0, 0.0, 0.0,0.0,0.6,

// Right
1.0,-1.0, 1.0, -1.0, 0.0, 0.0, 0.0,0.0,0.6,
1.0,-1.0,-1.0, -1.0, 0.0, 0.0, 0.0,0.0,0.6,
1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0,0.0,0.6,

1.0,-1.0,-1.0, -1.0, 0.0, 0.0, 0.0,0.0,0.6,
1.0, 1.0,-1.0, -1.0, 0.0, 0.0, 0.0,0.0,0.6,
1.0, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0,0.0,0.6
]);

scaleBuffer = createBuffer([
// Position UV
1.0, 1.0, 1.0,1.0,
-1.0, 1.0, 0.0,1.0,
-1.0,-1.0, 0.0,0.0,

1.0, 1.0, 1.0,1.0,
-1.0,-1.0, 0.0,0.0,
1.0,-1.0, 1.0,0.0
]);

[framebuffer,framebufferTexture] = createFramebuffer(internalWidth,internalHeight);

loop();
}

// Exit point
onunload = function() {
gl.deleteProgram(cubeProgram);
gl.deleteProgram(scaleProgram);
gl.deleteBuffer(cubeBuffer);
gl.deleteBuffer(scaleBuffer);
gl.deleteFramebuffer(framebuffer);
gl.deleteTexture(framebufferTexture);
}
}();

</script>
</body>
</html>

关于javascript - 是否有一种更简单(且仍然高效)的方法来使用最近邻重采样来升级 Canvas 渲染?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50440166/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com