//////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/ // VERTEX SHADER //////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/ attribute vec4 aVertexPosition; attribute vec3 aVertexNormal; attribute vec2 aTextureCoord; uniform mat4 uNormalMatrix; uniform mat4 uModelViewMatrix; uniform mat4 uProjectionMatrix; varying highp vec2 vTextureCoord; varying highp vec3 vLighting; void main () { gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; vTextureCoord = aTextureCoord; highp vec3 ambient_light = vec3( 0.3, 0.3, 0.3 ); highp vec3 directional_light_color = vec3( 1, 1, 1 ); highp vec3 directional_vector = normalize( vec3( 0.85, 0.8, 0.75 ) ); highp vec4 transformed_normal = uNormalMatrix * vec4( aVertexNormal, 1.0 ); highp float directional = max( dot( transformed_normal.xyz, directional_vector ), 0.0 ); vLighting = ambient_light + (directional_light_color * directional); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/ // FRAGMENT SHADER //////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/ varying highp vec2 vTextureCoord; varying highp vec3 vLighting; uniform sampler2D uSampler; void main () { highp vec4 texel_color = texture2D( uSampler, vTextureCoord ); gl_FragColor = vec4( texel_color.rgb * vLighting, texel_color.a ); }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/
// WebGL TUTORIAL CODE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/
"use strict";
const DtoR = Math.PI / 180; // Convert degrees to radians
var frame_count = 0; // Count each drawn frame for calculating frames per second
/**
* init_shader_program()
* Compile and link the shader program
*/
function init_shader_program (gl_context, vertex_shader_source, fragment_shader_source) {
const gl = gl_context;
function load_shader (gl, type, source) {
const shader = gl.createShader( type );
gl.shaderSource( shader, source );
gl.compileShader( shader );
if (! gl.getShaderParameter( shader, gl.COMPILE_STATUS )) {
const compile_result = gl.getShaderInfoLog( shader )
gl.deleteShader( shader );
throw new Error( "Error while compiling shader program: " + compile_result );
}
return shader;
} // load_shader
const vertex_shader = load_shader( gl, gl.VERTEX_SHADER, vertex_shader_source );
const fragment_shader = load_shader( gl, gl.FRAGMENT_SHADER, fragment_shader_source );
const shader_program = gl.createProgram();
gl.attachShader( shader_program, vertex_shader );
gl.attachShader( shader_program, fragment_shader );
gl.linkProgram( shader_program );
if (! gl.getProgramParameter( shader_program, gl.LINK_STATUS )) {
const link_result = gl.getProgramInfoLog( shader_program );
throw new Error( "Error while linking shader program: " + link_result );
}
return shader_program;
} // init_shader_program
/**
* load_texture()
* Initialize a texture and load an image. When the image finished loading copy it into the texture.
*/
function load_texture (gl_context, url) {
const gl = gl_context;
// Create and select texture buffer
const texture = gl.createTexture();
gl.bindTexture( gl.TEXTURE_2D, texture );
// Because images have to be download over the internet they might take a moment until they are ready. Until
// then put a single pixel in the texture so we can use it immediately. When the image has finished downloading
// we'll update the texture with the contents of the image.
const level = 0;
const internalFormat = gl.RGBA;
const width = 1;
const height = 1;
const border = 0;
const srcFormat = gl.RGBA;
const srcType = gl.UNSIGNED_BYTE;
const pixel = new Uint8Array( [0, 0, 255, 255] ); // Opaque blue
gl.texImage2D(
gl.TEXTURE_2D,
level,
internalFormat,
width,
height,
border,
srcFormat,
srcType,
pixel
);
// Create image object for downloading the texture
const image = new Image();
image.onload = function() {
// Select buffer and upload image data
gl.bindTexture( gl.TEXTURE_2D, texture );
gl.texImage2D(
gl.TEXTURE_2D,
level,
internalFormat,
srcFormat,
srcType,
image
);
// WebGL1 has different requirements for power of 2 images vs. non power of 2 images so check if the
// image is a power of 2 in both dimensions.
function is_power_of_2 (value) {
return (value & (value - 1)) == 0;
} // is_power_of_2
if (is_power_of_2( image.width ) && is_power_of_2( image.height )) {
// Yes, it's a power of 2. Generate mips.
gl.generateMipmap( gl.TEXTURE_2D );
} else {
// No, it's not a power of 2. Turn of mips and set wrapping to clamp to edge
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.LINEAR );
}
};
// Trigger download
image.src = url;
return texture;
} // load_texture
/**
* init_buffers()
* Make our data accessible to the GPU
*/
function init_buffers (gl) {
const positions = [
// Front face
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// Back face
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
// Top face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
// Bottom face
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,
// Right face
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,
// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0,
];
const vertex_normals = [
// Front
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
// Back
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
0.0, 0.0, -1.0,
// Top
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
// Bottom
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
0.0, -1.0, 0.0,
// Right
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
// Left
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0,
-1.0, 0.0, 0.0
];
// This array defines each face as two triangles, using the indices into the vertex array to specify each
// triangle's position
const indices = [
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23, // left
];
const texture_coordinates = [
// Front
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Back
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Top
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Bottom
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Right
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
// Left
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
];
// Create and select texture coordinates buffer and send data to WebGL
const texture_coord_buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, texture_coord_buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( texture_coordinates ), gl.STATIC_DRAW );
// Create and select normals buffer and send data to WebGL
const normal_buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, normal_buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertex_normals ), gl.STATIC_DRAW );
// Create and select position buffer and send data to WebGL
const position_buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, position_buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( positions ), gl.STATIC_DRAW );
// Create and select index buffer and send data to WebGL
const index_buffer = gl.createBuffer();
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, index_buffer );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );
return {
position : position_buffer,
normal : normal_buffer,
textureCoord : texture_coord_buffer,
indices : index_buffer,
}
} // init_buffers
/**
* draw_scene()
*/
function draw_scene (gl_context, program_info, buffers, texture, rotation) {
const gl = gl_context;
// Fill canvas with black
gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
gl.clearDepth( 1.0 );
gl.enable ( gl.DEPTH_TEST );
gl.depthFunc ( gl.LEQUAL );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
const field_of_view = 45 * DtoR;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const z_near = 0.1;
const z_far = 100.0;
// Set up camera
const projection_matrix = mat4.create();
mat4.perspective(
projection_matrix,
field_of_view,
aspect,
z_near,
z_far,
);
// Set up world coordinate transformation ("move the camera")
const model_view_matrix = mat4.create();
mat4.translate(
model_view_matrix,
model_view_matrix,
[-0.0, 0.0, -6.0],
);
mat4.rotate(
model_view_matrix,
model_view_matrix,
rotation,
[0, 0, 1],
);
mat4.rotate(
model_view_matrix,
model_view_matrix,
rotation * 0.7,
[0, 1, 0],
);
// Set up normal matrix
const normal_matrix = mat4.create();
mat4.invert( normal_matrix, model_view_matrix );
mat4.transpose( normal_matrix, normal_matrix );
// Tell WebGL how to pull out the positions from the position buffer into the vertexPosition attribute
{
const num_components = 3;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer( gl.ARRAY_BUFFER, buffers.position );
gl.vertexAttribPointer(
program_info.attribLocations.vertexPosition,
num_components,
type,
normalize,
stride,
offset,
);
gl.enableVertexAttribArray(
program_info.attribLocations.vertexPosition
);
}
// Tell WebGL how to pull out the normals from the normal buffer into the vertextNormal attribute
{
const num_components = 3;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer( gl.ARRAY_BUFFER, buffers.normal );
gl.vertexAttribPointer(
program_info.attribLocations.vertexNormal,
num_components,
type,
normalize,
stride,
offset,
);
gl.enableVertexAttribArray( program_info.attribLocations.vertexNormal );
}
// Tell WebGL how to pull out the texture coordinates from the buffer into the textureCoord attribute
{
const numComponents = 2;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer( gl.ARRAY_BUFFER, buffers.textureCoord );
gl.vertexAttribPointer(
program_info.attribLocations.textureCoord,
numComponents,
type,
normalize,
stride,
offset
);
gl.enableVertexAttribArray( program_info.attribLocations.textureCoord );
}
// Tell WebGL which indices to use for indexing the vertices
{
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffers.indices );
}
// Upload the shader program to the GPU
gl.useProgram( program_info.program );
// Upload matrices
gl.uniformMatrix4fv(
program_info.uniformLocations.projectionMatrix,
false,
projection_matrix,
);
gl.uniformMatrix4fv(
program_info.uniformLocations.modelViewMatrix,
false,
model_view_matrix,
);
gl.uniformMatrix4fv(
program_info.uniformLocations.normalMatrix,
false,
normal_matrix,
);
// Specify the texture to map onto the faces
// Tell WebGL we want to affect texture unit 0
gl.activeTexture( gl.TEXTURE0 );
// Bind the texture to texture unit 0
gl.bindTexture( gl.TEXTURE_2D, texture );
// Tell the shader we bound the texture to texture unit 0
gl.uniform1i( program_info.uniformLocations.uSampler, 0 );
// Trigger rendering
{
const vertex_count = 36;
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements( gl.TRIANGLES, vertex_count, type, offset );
}
++frame_count;
} // draw_scene
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/
// PROGRAM ENTRY POINT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////119:/
/**
* body.onload()
*/
window.addEventListener( "load", ()=>{
function computed_style( element ) {
return element.currentStyle || window.getComputedStyle( element );
} // computedStyle
// Get size of the <canvas> element
const canvas = document.querySelector( ".viewport" );
const style = computed_style( canvas );
const new_width = parseInt( style.width ); // We could use different dimensions, if so desired
const new_height = parseInt( style.height );
// Initialize the <canvas> element
// Set size of the DOM element (how large the canvas appears on the page) and its drawing surface
canvas.setAttribute( "width", canvas.width = new_width ); // Setting both sizes in one go
canvas.setAttribute( "height", canvas.height = new_height );
// Attempt to retreive the WebGL context
const gl = canvas.getContext( "webgl" );
if (! gl) throw new Error( "Unable to initialize WebGL." );
// Fill the canvas with a dark red. If this color appears on the page, something went wrong
gl.clearColor( 0.5, 0.0, 0.0, 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT );
// Load shader sources
const vertex_shader_source = document.querySelector( "#vertex_shader" ).innerText;
const fragment_shader_source = document.querySelector( "#fragment_shader" ).innerText;
// Compile shaders
const shader_program = init_shader_program( gl, vertex_shader_source, fragment_shader_source );
// Store all info
const program_info = {
program: shader_program,
attribLocations: {
vertexPosition : gl.getAttribLocation( shader_program, "aVertexPosition" ),
vertexNormal : gl.getAttribLocation( shader_program, "aVertexNormal" ),
textureCoord : gl.getAttribLocation( shader_program, "aTextureCoord" ),
},
uniformLocations: {
projectionMatrix : gl.getUniformLocation( shader_program, "uProjectionMatrix" ),
modelViewMatrix : gl.getUniformLocation( shader_program, "uModelViewMatrix" ),
normalMatrix : gl.getUniformLocation( shader_program, "uNormalMatrix" ),
uSampler : gl.getUniformLocation( shader_program, 'uSampler' ),
},
};
// Set up memory
const buffers = init_buffers( gl );
// Load texture
const texture = load_texture( gl, 'cubetexture.png' );
// Animate
let then = 0;
let rotation = 0.0;
function render (now) {
now *= 0.001; // Convert to seconds
const delta_t = now - then;
then = now;
rotation += delta_t;
draw_scene( gl, program_info, buffers, texture, rotation );
requestAnimationFrame( render );
} // render
requestAnimationFrame( render ); // Start render loop
// Display frames per second
setInterval( ()=>{
document.title = "F/s: " + frame_count;
frame_count = 0;
}, 1000 );
});
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL