WebGL JavaScript

Rotate a Texture Around the Y-axis

This JavaScript program demonstrates how to rotate a texture around the y-axis in WebGL.

RotateTextureAroundY.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>XoaX.net's WebGL</title>
		<style>
			#idCanvas {
				border: 1px solid black;
				width: 800px;
				height: 600px
			}
		</style>
		<script  id="idVertexShader" type="c">
			attribute vec4 av4Vertex;
			attribute vec2 av2UV;
			varying vec2 vv2FragUV;
			uniform mat4 um4Model;
			uniform mat4 um4View;
			uniform mat4 um4Projection;
			void main() {
				gl_Position = um4Projection*um4View*um4Model*av4Vertex;
				vv2FragUV = av2UV;
			}
		</script>
		<script  id="idFragmantShader" type="c">
			precision mediump float;
			varying vec2 vv2FragUV;
			uniform sampler2D us2Texture;
			void main() {
				gl_FragColor = texture2D(us2Texture, vv2FragUV);
			}
		</script>
		<script type="text/javascript" src="RotateTextureAroundY.js"></script>
  </head>
  <body onload="Render()">
    <canvas id="idCanvas"></canvas>
  </body>
</html>

RotateTextureAroundY.js

var gqScene = null;
var gdTime = 0.0;

function Render() {
	
	if (gqScene == null) {
		gqScene = new CScene();
	}
	
	let dLastTime = gdTime;
	let qDate = new Date();
	// Millseconds since January 1, 1970
	gdTime = qDate.getTime()/6000.0;
	let dDeltaTime = gdTime - dLastTime;
	
	RotateY(gqScene.mfaModel, 2*Math.PI*dDeltaTime);

	const kqGL = gqScene.mqGL;
	const kqShaderProgram = gqScene.mqShaderProgram;
	const kqPositionBuffer = gqScene.mqPositionBuffer;
	const kqTextureBuffer = gqScene.mqTextureBuffer;

	kqGL.bindBuffer(kqGL.ARRAY_BUFFER, kqPositionBuffer);
	kqGL.vertexAttribPointer(kqGL.getAttribLocation(kqShaderProgram, "av4Vertex"), 3, kqGL.FLOAT, true, 0, 0);
	kqGL.enableVertexAttribArray(kqGL.getAttribLocation(kqShaderProgram, "av4Vertex"));

	kqGL.bindBuffer(kqGL.ARRAY_BUFFER, kqTextureBuffer);
	kqGL.vertexAttribPointer(kqGL.getAttribLocation(kqShaderProgram, "av2UV"), 2, kqGL.FLOAT, true, 0, 0);
	kqGL.enableVertexAttribArray(kqGL.getAttribLocation(kqShaderProgram, "av2UV"));

	kqGL.useProgram(kqShaderProgram);

	kqGL.uniformMatrix4fv(kqGL.getUniformLocation(kqShaderProgram, "um4Model"), false, gqScene.mfaModel);
	kqGL.uniformMatrix4fv(kqGL.getUniformLocation(kqShaderProgram, "um4View"), false, gqScene.mfaView);
	kqGL.uniformMatrix4fv(kqGL.getUniformLocation(kqShaderProgram, "um4Projection"), false, gqScene.mfaProjection);

	kqGL.activeTexture(kqGL.TEXTURE0);
	kqGL.bindTexture(kqGL.TEXTURE_2D, gqScene.miTextureID);
	kqGL.uniform1i(kqGL.getUniformLocation(kqShaderProgram, "us2Texture"), 0);
	kqGL.drawElements(kqGL.TRIANGLES, 6, kqGL.UNSIGNED_SHORT, 0);

	requestAnimationFrame(Render);
}

class CScene {
	constructor() {
		const qCanvas = document.getElementById("idCanvas");
		qCanvas.width = qCanvas.clientWidth;
		qCanvas.height = qCanvas.clientHeight;
		const kqGL = qCanvas.getContext("webgl");
		this.mqGL = kqGL;

		// Compile the vertex shader
		const ksVertexShaderCode = document.getElementById("idVertexShader").innerHTML;
		let qVertexShader = kqGL.createShader(kqGL.VERTEX_SHADER);
		kqGL.shaderSource(qVertexShader, ksVertexShaderCode);
		kqGL.compileShader(qVertexShader);

		// Compile the fragment shader
		const ksFragmentShaderCode = document.getElementById("idFragmantShader").innerHTML;
		let qFragmentShader = kqGL.createShader(kqGL.FRAGMENT_SHADER);
		kqGL.shaderSource(qFragmentShader, ksFragmentShaderCode);
		kqGL.compileShader(qFragmentShader);

		// Compile and link the program
		let qProgram = kqGL.createProgram();
		kqGL.attachShader(qProgram, qVertexShader);
		kqGL.attachShader(qProgram, qFragmentShader);
		kqGL.linkProgram(qProgram);
		//kqGL.useProgram(qProgram);
		this.mqShaderProgram = qProgram;
		
		if (!kqGL.getProgramParameter(qProgram, kqGL.LINK_STATUS)) {
			alert('The program failed to initialize', kqGL.getProgramInfoLog(qProgram));
		}
		
		// Create the buffers
		this.mfaPositions = [-1.0, 0.0, -1.0,  1.0, 0.0, -1.0,  1.0, 0.0, 1.0,  -1.0, 0.0, 1.0];
		let qPositionBuffer = kqGL.createBuffer();
		kqGL.bindBuffer(kqGL.ARRAY_BUFFER, qPositionBuffer);
		kqGL.bufferData(kqGL.ARRAY_BUFFER, new Float32Array(this.mfaPositions), kqGL.STATIC_DRAW);
		this.mqPositionBuffer = qPositionBuffer;
		
		this.miaIndices = [0, 2, 1,  0, 3, 2];
		let qIndexBuffer = kqGL.createBuffer();
		kqGL.bindBuffer(kqGL.ELEMENT_ARRAY_BUFFER, qIndexBuffer);
		kqGL.bufferData(kqGL.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.miaIndices), kqGL.STATIC_DRAW);

		this.mfaTextureCoordinates = [0.25, 0.25,  0.75, 0.25,  0.75, 0.75,  0.25, 0.75];	//[0.0, 0.0,  1.0, 0.0,  1.0, 1.0,  0.0, 1.0];
		let qTextureBuffer = kqGL.createBuffer();
		kqGL.bindBuffer(kqGL.ARRAY_BUFFER, qTextureBuffer);
		kqGL.bufferData(kqGL.ARRAY_BUFFER, new Float32Array(this.mfaTextureCoordinates), kqGL.STATIC_DRAW);
		this.mqTextureBuffer = qTextureBuffer;
		
		// Create the texture
		this.miTextureSize = 2;
		this.mfaTexture = new Uint8Array(4*this.miTextureSize*this.miTextureSize)
		const kiMax = (this.miTextureSize - 1)*(this.miTextureSize - 1);
		for (let i = 0; i <= this.miTextureSize; ++i) {
			for (let j = 0; j <= this.miTextureSize; ++j) {
				// Red, Green, Blue, Alpha
				this.mfaTexture[4*(this.miTextureSize*i + j)] = Math.round(Math.random()*255);
				this.mfaTexture[4*(this.miTextureSize*i + j) + 1] = Math.round(Math.random()*255);
				this.mfaTexture[4*(this.miTextureSize*i + j) + 2] = Math.round(Math.random()*255);
				this.mfaTexture[4*(this.miTextureSize*i + j) + 3] = 255;
				
				this.mfaTexture[4*(this.miTextureSize*i + j)] = ((i+j) % 2)*255;
				this.mfaTexture[4*(this.miTextureSize*i + j) + 1] = 0;
				this.mfaTexture[4*(this.miTextureSize*i + j) + 2] = 0;
				this.mfaTexture[4*(this.miTextureSize*i + j) + 3] = 255;
			}
		}
		
		this.miTextureID = kqGL.createTexture();
		kqGL.bindTexture(kqGL.TEXTURE_2D, this.miTextureID);
		kqGL.texImage2D(kqGL.TEXTURE_2D, 0, kqGL.RGBA, this.miTextureSize, this.miTextureSize, 0,
			kqGL.RGBA, kqGL.UNSIGNED_BYTE, this.mfaTexture);
		kqGL.texParameteri(kqGL.TEXTURE_2D, kqGL.TEXTURE_WRAP_S, kqGL.CLAMP_TO_EDGE);
		kqGL.texParameteri(kqGL.TEXTURE_2D, kqGL.TEXTURE_WRAP_T, kqGL.CLAMP_TO_EDGE);
		kqGL.texParameteri(kqGL.TEXTURE_2D, kqGL.TEXTURE_MIN_FILTER, kqGL.NEAREST);
		
		// Create the matrices
		this.mfaModel = Identity();
		Scale(this.mfaModel, [1, 0, 1]);

		const kdFOV = Math.PI*(45.0/180.0);
		const kdAspect = qCanvas.clientWidth/qCanvas.clientHeight;
		const kdNearPlane = 0.1;
		const kdFarPlane = 1000.0;
		this.mfaProjection = Perspective(kdFOV, kdAspect, kdNearPlane, kdFarPlane);
		
		this.mfaView = Identity();
		// Create the view matrix
		let daEye = [-3.0, 2.0, 0.0];
		let daCenter = [0.0, 0.0, 0.0];
		let daUp = [0.0, 1.0, 0.0];
		LookAt(this.mfaView, daEye, daCenter, daUp);
	}
}

function Identity() {
	let faI = new Float32Array(16);
	faI[0]  = 1.0; faI[1]  = 0.0; faI[2]  = 0.0; faI[3]  = 0.0;
	faI[4]  = 0.0; faI[5]  = 1.0; faI[6]  = 0.0; faI[7]  = 0.0;
	faI[8]  = 0.0; faI[9]  = 0.0; faI[10] = 1.0; faI[11] = 0.0;
	faI[12] = 0.0; faI[13] = 0.0; faI[14] = 0.0; faI[15] = 1.0;
	return faI;
}

function Scale(faM, faScale) {
	for (let i = 0; i < 3; ++i) {
		for (let j = 0; j < 4; ++j) {
			faM[j + i*4] *= faScale[i];
		}
	}
}

function RotateY(faM, fAngleRadians) {
	let dSin = Math.sin(fAngleRadians);
	let dCos = Math.cos(fAngleRadians);
	// Temp storage
	let faV = new Float32Array(8);
	// Perform the rotation to get the resulting x and z rows
	for (let i = 0; i < 4; ++i) {
		faV[i] = dCos*faM[i] + dSin*faM[i+8];
		faV[i + 4] = -dSin*faM[i] + dCos*faM[i+8];
	}
	// Copy the rotated rows back to the matrix
	for (let i = 0; i < 4; ++i) {
		faM[i] = faV[i];
		faM[i + 8] = faV[i+4];
	}
}

function Perspective(dFOV, dAspect, dNear, dFar) {
	let faM = new Float32Array(16);
	let dF = 1.0/Math.tan(dFOV/2.0);
	let dNF = 1/(dNear-dFar);
	faM[0] = dF/dAspect; faM[1] = 0.0;  faM[2] = 0.0;                 faM[3] = 0.0;
	faM[4] = 0.0;        faM[5] = dF;   faM[6] = 0.0;                 faM[7] = 0.0;
	faM[8] = 0.0;        faM[9] = 0.0;  faM[10] = (dNear+dFar)*dNF;   faM[11] = -1.0;
	faM[12] = 0.0;       faM[13] = 0.0; faM[14] = 2.0*dNear*dFar*dNF; faM[15] = 0.0;
	return faM;
}

function LookAt(faM, faEye, faCenter, faUp) {
	let faView = Difference(faEye, faCenter);
	Normalize(faView);
	let faX = Cross(faUp, faView);
	Normalize(faX);
	let faY = Cross(faView, faX);
	Normalize(faY);
	faM[0] = faX[0]; faM[1] = faY[0]; faM[2] = faView[0];  faM[3] = 0.0;
	faM[4] = faX[1]; faM[5] = faY[1]; faM[6] = faView[1];  faM[7] = 0.0;
	faM[8] = faX[2]; faM[9] = faY[2]; faM[10] = faView[2]; faM[11] = 0.0;
	faM[12] = -Dot(faX, faEye)
	faM[13] = -Dot(faY, faEye)
	faM[14] = -Dot(faView, faEye)
	faM[15] = 1.0;
}

function Dot(faV1, faV2) {
	let dDot = 0.0;
	for (let i = 0; i < 3; ++i) {
		dDot += faV1[i]*faV2[i];
	}
	return dDot;
}

function Difference(faV1, faV2) {
	let faDiff = new Float32Array(3);
	for (let i = 0; i < 3; ++i) {
		faDiff[i] = faV1[i] - faV2[i];
	}
	return faDiff;
}

function Cross(faV1, faV2) {
	let faCross = new Float32Array(3);
	for (let i = 0; i < 3; ++i) {
		faCross[i] = faV1[(i+1)%3]*faV2[(i+2)%3] - faV1[(i+2)%3]*faV2[(i+1)%3];
	}
	return faCross;
}

function Normalize(faV) {
	let dLength = 1.0/Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]);
	for (let i = 0; i < 3; ++i) {
		faV[i] *= dLength;
	}
}
 

Output

 
 

© 2007–2026 XoaX.net LLC. All rights reserved.