W tej części tutoriala zajmiemy się wykorzystaniem światła punktowego, czyli światła pochodzącego z konkretnego punktu naszej sceny. Tak samo jak w poprzednich tutorialach często będziemy odwoływać się do już zrealizowanych przez nas uprzednio funkcji wprowadzając do nich jedynie nieznaczne modyfikacje.
		W przeciwieństwie do tutoriala 11, w tym nie będziemy wykorzystywali event'ów myszy, a oprzemy się na automatycznej animacji. Tak więc z funkcji webGLStart() powinny zniknąć linijki odpowiedzialne za obsługę event'ów. Z uwagi na wprowadzenie nowego teksturowanego obiektu nastąpiła również jedna kosmetyczna zmiana nazwy funkcji z initTexture() na initTextures().
	
Przechodząc dalej do funkcji tick() musimy wprowadzić aktualizację dotyczącą animacji obiektów:
	
	
	  function tick() {
	    requestAnimFrame(tick);
	    drawScene();
	    animate();
	  }
	
	
	
		Funkcja animate() jest funkcją dość prostą. W trakcie swojego działania aktualizuje dwie zmienne globalne odpowiedzialne orbity poruszających się obiektów:
	
	  var lastTime = 0;
	  function animate() {
	    var timeNow = new Date().getTime();
	    if (lastTime != 0) {
	      var elapsed = timeNow - lastTime;
	
	      moonAngle += 0.05 * elapsed;
	      cubeAngle += 0.05 * elapsed;
	    }
	    lastTime = timeNow;
	  }
	
	
	
		Przechodząc dalej do funkcji drawScene() wprowadzamy wiele zmian. Sam początek jak możemy zauważyć niczym nie różni się od kodu z tutoriala 11:
	
	  function drawScene() {
	    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
	    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
	
	    mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
	
	    var lighting = document.getElementById("lighting").checked;
	    gl.uniform1i(shaderProgram.useLightingUniform, lighting);
	    if (lighting) {
	      gl.uniform3f(
	        shaderProgram.ambientColorUniform,
	        parseFloat(document.getElementById("ambientR").value),
	        parseFloat(document.getElementById("ambientG").value),
	        parseFloat(document.getElementById("ambientB").value)
	      );
	
	
	Zmiany zaczynają się dopiero po powyższym fragmencie kodu. Musimy do karty graficznej wysłać informacje o pozycji światła punktowego. W trakcie wysyłania danych światła kierunkowego musieliśmy je przekonwertować na wektor jednostkowy i odwrócić kierunek, w przypadku punktowego nic takiego nie jest konieczne:
      gl.uniform3f(
        shaderProgram.pointLightingLocationUniform,
        parseFloat(document.getElementById("lightPositionX").value),
        parseFloat(document.getElementById("lightPositionY").value),
        parseFloat(document.getElementById("lightPositionZ").value)
      );
	
	
	W dalszej kolejności określamy kolor światła punktowego:
      gl.uniform3f(
        shaderProgram.pointLightingColorUniform,
        parseFloat(document.getElementById("pointR").value),
        parseFloat(document.getElementById("pointG").value),
        parseFloat(document.getElementById("pointB").value)
      );
    }
	
	
	Mając zdefiniowane światło możemy przejść do rysowania sfery i sześcianu:
mat4.identity(mvMatrix); mat4.translate(mvMatrix, [0, 0, -20]); mvPushMatrix(); mat4.rotate(mvMatrix, degToRad(moonAngle), [0, 1, 0]); mat4.translate(mvMatrix, [5, 0, 0]); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, moonTexture); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.bindBuffer(gl.ARRAY_BUFFER, moonVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, moonVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, moonVertexTextureCoordBuffer); gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, moonVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, moonVertexNormalBuffer); gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, moonVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, moonVertexIndexBuffer); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, moonVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); mvPopMatrix(); mvPushMatrix(); mat4.rotate(mvMatrix, degToRad(cubeAngle), [0, 1, 0]); mat4.translate(mvMatrix, [5, 0, 0]); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexNormalBuffer); gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, cubeVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer); gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, crateTexture); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); mvPopMatrix(); }
		Tym sposobem mamy w pełni wykonaną funkcję drawScene(). Podąrzając dalej po listingu kodu widzimy funkcje initBuffers() zawierającą standardowy kod z poprzedniego tutorialu powielony dla obiektu sześcianu. Dalej znajduje się funkcja initTextures(), która w tym przypadku ładuje dwie różne tekstury zamiast jednej.
	
Kolejną i ostatnią bardzo ważną zmianą są nowe linijki we fragmentach kodu znajdujących się na samej górze listingu. W kodzie dla Vertex Shader'a znajduje się kilka istotnych zmian:
attribute vec3 aVertexPosition; attribute vec3 aVertexNormal; attribute vec2 aTextureCoord; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat3 uNMatrix; uniform vec3 uAmbientColor; uniform vec3 uPointLightingLocation; uniform vec3 uPointLightingColor;
Mamy już zmienne typu uniform odpowiadające za pozycję i kolor światła punktowego. Dalej:
	  uniform bool uUseLighting;
	
	  varying vec2 vTextureCoord;
	  varying vec3 vLightWeighting;
	
	  void main(void) {
	    vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
	    gl_Position = uPMatrix * mvPosition;
	
	
	To co zrobiliśmy powyżej to nic innego jak rozdzielenie kodu z listingu z tutoriala 11 na dwie części. Przed zastosowaniem tego światła punktowego do perspektywy musimy zatosować jeszcze jedną zmianę:
	    vTextureCoord = aTextureCoord;
	
	    if (!uUseLighting) {
	      vLightWeighting = vec3(1.0, 1.0, 1.0);
	    } else {
	      vec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz);
	
	
	Na koniec musimy podmienić jedynie nazwy zmiennych wykorzystywanych przy świetle kierunkowym na obecnie stosowane dla światła punktowego:
      vec3 transformedNormal = uNMatrix * aVertexNormal;
      float directionalLightWeighting = max(dot(transformedNormal, lightDirection), 0.0);
      vLightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting;
	
	
	Tym samym wygenerowaliśmy kolejny przykład wykorzystania oświetlenia, tym razem punktowego. Całość kodu dostępna jest pod przyciskiem poniżej, tak samo jak możliwość uruchomienia działającego przykładu.
| Pozycja: | X: | Y: | Z: | 
| Kolor: | R: | G: | B: | 
| Kolor: | R: | G: | B: |