Last active
May 12, 2022 04:00
-
-
Save modster/2c00fba976d25b30407e2ccdc07aaa78 to your computer and use it in GitHub Desktop.
webgl-tips-screenshot-good
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- begin snippet: js hide: false console: true babel: false --> | |
<!-- language: lang-js --> | |
// WebGL2 Tips - Good Screenshot | |
// from https://webgl2fundamentals.org/webgl/webgl-tips-screenshot-good.html | |
"use strict"; | |
var vertexShaderSource = `#version 300 es | |
// an attribute is an input (in) to a vertex shader. | |
// It will receive data from a buffer | |
in vec4 a_position; | |
in vec4 a_color; | |
// A matrix to transform the positions by | |
uniform mat4 u_matrix; | |
// a varying the color to the fragment shader | |
out vec4 v_color; | |
// all shaders have a main function | |
void main() { | |
// Multiply the position by the matrix. | |
gl_Position = u_matrix * a_position; | |
// Pass the color to the fragment shader. | |
v_color = a_color; | |
} | |
`; | |
var fragmentShaderSource = `#version 300 es | |
precision highp float; | |
// the varied color passed from the vertex shader | |
in vec4 v_color; | |
// we need to declare an output for the fragment shader | |
out vec4 outColor; | |
void main() { | |
outColor = v_color; | |
} | |
`; | |
function main() { | |
// Get A WebGL context | |
/** @type {HTMLCanvasElement} */ | |
var canvas = document.querySelector("#canvas"); | |
var gl = canvas.getContext("webgl2"); | |
if (!gl) { | |
return; | |
} | |
// Use our boilerplate utils to compile the shaders and link into a program | |
var program = webglUtils.createProgramFromSources(gl, | |
[vertexShaderSource, fragmentShaderSource]); | |
// look up where the vertex data needs to go. | |
var positionAttributeLocation = gl.getAttribLocation(program, "a_position"); | |
var colorAttributeLocation = gl.getAttribLocation(program, "a_color"); | |
// look up uniform locations | |
var matrixLocation = gl.getUniformLocation(program, "u_matrix"); | |
// Create a buffer | |
var positionBuffer = gl.createBuffer(); | |
// Create a vertex array object (attribute state) | |
var vao = gl.createVertexArray(); | |
// and make it the one we're currently working with | |
gl.bindVertexArray(vao); | |
// Turn on the attribute | |
gl.enableVertexAttribArray(positionAttributeLocation); | |
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer) | |
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
// Set Geometry. | |
setGeometry(gl); | |
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) | |
var size = 3; // 3 components per iteration | |
var type = gl.FLOAT; // the data is 32bit floats | |
var normalize = false; // don't normalize the data | |
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position | |
var offset = 0; // start at the beginning of the buffer | |
gl.vertexAttribPointer( | |
positionAttributeLocation, size, type, normalize, stride, offset); | |
// create the color buffer, make it the current ARRAY_BUFFER | |
// and copy in the color values | |
var colorBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); | |
setColors(gl); | |
// Turn on the attribute | |
gl.enableVertexAttribArray(colorAttributeLocation); | |
// Tell the attribute how to get data out of colorBuffer (ARRAY_BUFFER) | |
var size = 3; // 3 components per iteration | |
var type = gl.UNSIGNED_BYTE; // the data is 8bit unsigned bytes | |
var normalize = true; // convert from 0-255 to 0.0-1.0 | |
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next color | |
var offset = 0; // start at the beginning of the buffer | |
gl.vertexAttribPointer( | |
colorAttributeLocation, size, type, normalize, stride, offset); | |
function degToRad(d) { | |
return d * Math.PI / 180; | |
} | |
// First let's make some variables | |
// to hold the translation, | |
var fieldOfViewRadians = degToRad(60); | |
var translation = [0, 0, -360]; | |
var rotation = [degToRad(190), degToRad(40), degToRad(320)]; | |
var scale = [1, 1, 1]; | |
var fieldOfViewRadians = degToRad(60); | |
var rotationSpeed = 1.2; | |
var then = 0; | |
requestAnimationFrame(renderLoop); | |
function renderLoop(now) { | |
// Convert to seconds | |
now *= 0.001; | |
// Subtract the previous time from the current time | |
var deltaTime = now - then; | |
// Remember the current time for the next frame. | |
then = now; | |
// Every frame increase the rotation a little. | |
rotation[1] += rotationSpeed * deltaTime; | |
drawScene(); | |
// Call renderLoop again next frame | |
requestAnimationFrame(renderLoop); | |
} | |
// Draw the scene. | |
function drawScene() { | |
webglUtils.resizeCanvasToDisplaySize(gl.canvas); | |
// Tell WebGL how to convert from clip space to pixels | |
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
// Clear the canvas | |
gl.clearColor(0, 0, 0, 0); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
// turn on depth testing | |
gl.enable(gl.DEPTH_TEST); | |
// tell webgl to cull faces | |
gl.enable(gl.CULL_FACE); | |
// Tell it to use our program (pair of shaders) | |
gl.useProgram(program); | |
// Bind the attribute/buffer set we want. | |
gl.bindVertexArray(vao); | |
// Compute the matrix | |
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; | |
var zNear = 1; | |
var zFar = 2000; | |
var matrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar); | |
matrix = m4.translate(matrix, translation[0], translation[1], translation[2]); | |
matrix = m4.xRotate(matrix, rotation[0]); | |
matrix = m4.yRotate(matrix, rotation[1]); | |
matrix = m4.zRotate(matrix, rotation[2]); | |
matrix = m4.scale(matrix, scale[0], scale[1], scale[2]); | |
// Set the matrix. | |
gl.uniformMatrix4fv(matrixLocation, false, matrix); | |
// Draw the geometry. | |
var primitiveType = gl.TRIANGLES; | |
var offset = 0; | |
var count = 16 * 6; | |
gl.drawArrays(primitiveType, offset, count); | |
} | |
const elem = document.querySelector('#screenshot'); | |
elem.addEventListener('click', () => { | |
canvas.toBlob((blob) => { | |
saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`); | |
}); | |
}); | |
const saveBlob = (function() { | |
const a = document.createElement('a'); | |
document.body.appendChild(a); | |
a.style.display = 'none'; | |
return function saveData(blob, fileName) { | |
const url = window.URL.createObjectURL(blob); | |
a.href = url; | |
a.download = fileName; | |
a.click(); | |
}; | |
}()); | |
} | |
// Fill the current ARRAY_BUFFER buffer | |
// with the values that define a letter 'F'. | |
function setGeometry(gl) { | |
var positions = new Float32Array([ | |
// left column front | |
0, 0, 0, | |
0, 150, 0, | |
30, 0, 0, | |
0, 150, 0, | |
30, 150, 0, | |
30, 0, 0, | |
// top rung front | |
30, 0, 0, | |
30, 30, 0, | |
100, 0, 0, | |
30, 30, 0, | |
100, 30, 0, | |
100, 0, 0, | |
// middle rung front | |
30, 60, 0, | |
30, 90, 0, | |
67, 60, 0, | |
30, 90, 0, | |
67, 90, 0, | |
67, 60, 0, | |
// left column back | |
0, 0, 30, | |
30, 0, 30, | |
0, 150, 30, | |
0, 150, 30, | |
30, 0, 30, | |
30, 150, 30, | |
// top rung back | |
30, 0, 30, | |
100, 0, 30, | |
30, 30, 30, | |
30, 30, 30, | |
100, 0, 30, | |
100, 30, 30, | |
// middle rung back | |
30, 60, 30, | |
67, 60, 30, | |
30, 90, 30, | |
30, 90, 30, | |
67, 60, 30, | |
67, 90, 30, | |
// top | |
0, 0, 0, | |
100, 0, 0, | |
100, 0, 30, | |
0, 0, 0, | |
100, 0, 30, | |
0, 0, 30, | |
// top rung right | |
100, 0, 0, | |
100, 30, 0, | |
100, 30, 30, | |
100, 0, 0, | |
100, 30, 30, | |
100, 0, 30, | |
// under top rung | |
30, 30, 0, | |
30, 30, 30, | |
100, 30, 30, | |
30, 30, 0, | |
100, 30, 30, | |
100, 30, 0, | |
// between top rung and middle | |
30, 30, 0, | |
30, 60, 30, | |
30, 30, 30, | |
30, 30, 0, | |
30, 60, 0, | |
30, 60, 30, | |
// top of middle rung | |
30, 60, 0, | |
67, 60, 30, | |
30, 60, 30, | |
30, 60, 0, | |
67, 60, 0, | |
67, 60, 30, | |
// right of middle rung | |
67, 60, 0, | |
67, 90, 30, | |
67, 60, 30, | |
67, 60, 0, | |
67, 90, 0, | |
67, 90, 30, | |
// bottom of middle rung. | |
30, 90, 0, | |
30, 90, 30, | |
67, 90, 30, | |
30, 90, 0, | |
67, 90, 30, | |
67, 90, 0, | |
// right of bottom | |
30, 90, 0, | |
30, 150, 30, | |
30, 90, 30, | |
30, 90, 0, | |
30, 150, 0, | |
30, 150, 30, | |
// bottom | |
0, 150, 0, | |
0, 150, 30, | |
30, 150, 30, | |
0, 150, 0, | |
30, 150, 30, | |
30, 150, 0, | |
// left side | |
0, 0, 0, | |
0, 0, 30, | |
0, 150, 30, | |
0, 0, 0, | |
0, 150, 30, | |
0, 150, 0, | |
]); | |
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); | |
} | |
// Fill the current ARRAY_BUFFER buffer with colors for the 'F'. | |
function setColors(gl) { | |
gl.bufferData( | |
gl.ARRAY_BUFFER, | |
new Uint8Array([ | |
// left column front | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
// top rung front | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
// middle rung front | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
200, 70, 120, | |
// left column back | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
// top rung back | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
// middle rung back | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
80, 70, 200, | |
// top | |
70, 200, 210, | |
70, 200, 210, | |
70, 200, 210, | |
70, 200, 210, | |
70, 200, 210, | |
70, 200, 210, | |
// top rung right | |
200, 200, 70, | |
200, 200, 70, | |
200, 200, 70, | |
200, 200, 70, | |
200, 200, 70, | |
200, 200, 70, | |
// under top rung | |
210, 100, 70, | |
210, 100, 70, | |
210, 100, 70, | |
210, 100, 70, | |
210, 100, 70, | |
210, 100, 70, | |
// between top rung and middle | |
210, 160, 70, | |
210, 160, 70, | |
210, 160, 70, | |
210, 160, 70, | |
210, 160, 70, | |
210, 160, 70, | |
// top of middle rung | |
70, 180, 210, | |
70, 180, 210, | |
70, 180, 210, | |
70, 180, 210, | |
70, 180, 210, | |
70, 180, 210, | |
// right of middle rung | |
100, 70, 210, | |
100, 70, 210, | |
100, 70, 210, | |
100, 70, 210, | |
100, 70, 210, | |
100, 70, 210, | |
// bottom of middle rung. | |
76, 210, 100, | |
76, 210, 100, | |
76, 210, 100, | |
76, 210, 100, | |
76, 210, 100, | |
76, 210, 100, | |
// right of bottom | |
140, 210, 80, | |
140, 210, 80, | |
140, 210, 80, | |
140, 210, 80, | |
140, 210, 80, | |
140, 210, 80, | |
// bottom | |
90, 130, 110, | |
90, 130, 110, | |
90, 130, 110, | |
90, 130, 110, | |
90, 130, 110, | |
90, 130, 110, | |
// left side | |
160, 160, 220, | |
160, 160, 220, | |
160, 160, 220, | |
160, 160, 220, | |
160, 160, 220, | |
160, 160, 220, | |
]), | |
gl.STATIC_DRAW); | |
} | |
main(); | |
<!-- language: lang-css --> | |
@import url("https://webgl2fundamentals.org/webgl/resources/webgl-tutorials.css"); | |
body { | |
margin: 0; | |
} | |
canvas { | |
width: 100vw; | |
height: 100vh; | |
display: block; | |
} | |
button { pointer-events:initial; } | |
<!-- language: lang-html --> | |
<canvas id="canvas"></canvas> | |
<div id="uiContainer"> | |
<div id="ui"> | |
<button id="screenshot" type="button">Save...</button> | |
</div> | |
</div> | |
<!-- | |
for most samples webgl-utils only provides shader compiling/linking and | |
canvas resizing because why clutter the examples with code that's the same in every sample. | |
See https://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html | |
and https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html | |
for webgl-utils, m3, m4, and webgl-lessons-ui. | |
--> | |
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script> | |
<script src="https://webgl2fundamentals.org/webgl/resources/m4.js"></script> | |
<!-- end snippet --> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment