W czternastym z kolei tutorialu zajmiemy się refleksami i ładowaniem modeli z plików JSON. Refleksy zostały już wstępnie wprowadzone w tutorialu 7, tutaj natomiast nieco rozszerzymy ich wykorzystanie.
Analizując kod przykładu od góry, pierwszym w kolejności jest fragment odpowiedzialny za renderowanie oświetlenia per-fragment. Zmieniliśmy zmienne odpowiedzialne za kolor światła punktowego na jasność i rozproszenie:
precision mediump float; varying vec2 vTextureCoord; varying vec3 vTransformedNormal; varying vec4 vPosition; uniform float uMaterialShininess; uniform bool uShowSpecularHighlights; uniform bool uUseLighting; uniform bool uUseTextures; uniform vec3 uAmbientColor; uniform vec3 uPointLightingLocation; uniform vec3 uPointLightingSpecularColor; uniform vec3 uPointLightingDiffuseColor; uniform sampler2D uSampler;
Powyższy kod nie wymaga chyba tłumaczenia, dlatego też przejdziemy dalej do wnętrza shader'a. Dajemy możliwość włączenia/wyłączenia oświetlenia i musimy takie zdarzenie obsłużyć:
	  void main(void) {
	    vec3 lightWeighting;
	    if (!uUseLighting) {
	      lightWeighting = vec3(1.0, 1.0, 1.0);
	    } else {
	      vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
	      vec3 normal = normalize(vTransformedNormal);
	
	      float specularLightWeighting = 0.0;
	      if (uShowSpecularHighlights) {
	
	
	W powyższym fragmencie następuje wyliczanie kierunku światła tak jak robimy to w przypadku zwykłego oświetlenia per-fragment. W celu w miarę realistycznego odwzorowania refleksów konieczne jest ustalenie kierunku patrzenia użytkownika, który względem dowolnego punktu wyrysowanego na scenie jest negatywem:
vec3 eyeDirection = normalize(-vPosition.xyz);
Refleks pojawiający się na powierzchni obiektu ma kierunek przeciwny do kierunku padającego na obiekt światła:
vec3 reflectionDirection = reflect(-lightDirection, normal);
Aby dokończyć generowanie refleksu należy jeszcze dodać jedną linijkę odpowiedzialną za ważenie jego światła:
        specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
      }
	
	
	Mając wygenerowany refleks, można zająć się rozproszeniem światła, postępując podobnie jak wcześniej:
float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0);
Mając zdefiniowane oba ważenia stosujemy je w połączeniu ze światłem do określenia ogólnego ważenia światła:
      lightWeighting = uAmbientColor
	        + uPointLightingSpecularColor * specularLightWeighting
	        + uPointLightingDiffuseColor * diffuseLightWeighting;
	    }
	
	
	Wyliczone ważenie stosujemy do nałożonych na model tekstur:
	    vec4 fragmentColor;
	    if (uUseTextures) {
	      fragmentColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
	    } else {
	      fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
	    }
	    gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);
	  }
	
	
	
		Na tym kończymy modyfikacje kodu shader'ów. Funkcja initShaders() powinna wrócić do swojej pierwotnej prostej postaci, ponieważ w realizowanym przykładzie przekazujemy tylko jeden program do renderowania w karcie graficznej.
	
		Jak zostało wspomniane na początku, w tutorialu będzie również mowa o ładowaniu modeli z plików JSON. Kod funkcji loadTeapot() wczytującej obiekt imbryka znajduje się poniżej:
	
	  function loadTeapot() {
	    var request = new XMLHttpRequest();
	    request.open("GET", "tutorials/tutorial14/Teapot.json");
	    request.onreadystatechange = function() {
	      if (request.readyState == 4) {
	        handleLoadedTeapot(JSON.parse(request.responseText));
	      }
	    }
	    request.send();
	  }
	
	
	
		Kiedy ładowanie modelu zostanie zakończone request.readyState == 4 wywoływana jest akcja konwertująca JSON'a na dane, które jesteśmy w stanie używać czyli tablice webGL'a:
	
	  var teapotVertexPositionBuffer;
	  var teapotVertexNormalBuffer;
	  var teapotVertexTextureCoordBuffer;
	  var teapotVertexIndexBuffer;
	  function handleLoadedTeapot(teapotData) {
	    teapotVertexNormalBuffer = gl.createBuffer();
	    gl.bindBuffer(gl.ARRAY_BUFFER, teapotVertexNormalBuffer);
	    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(teapotData.vertexNormals), gl.STATIC_DRAW);
	    teapotVertexNormalBuffer.itemSize = 3;
	    teapotVertexNormalBuffer.numItems = teapotData.vertexNormals.length / 3;
	
	    teapotVertexTextureCoordBuffer = gl.createBuffer();
	    gl.bindBuffer(gl.ARRAY_BUFFER, teapotVertexTextureCoordBuffer);
	    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(teapotData.vertexTextureCoords), gl.STATIC_DRAW);
	    teapotVertexTextureCoordBuffer.itemSize = 2;
	    teapotVertexTextureCoordBuffer.numItems = teapotData.vertexTextureCoords.length / 2;
	
	    teapotVertexPositionBuffer = gl.createBuffer();
	    gl.bindBuffer(gl.ARRAY_BUFFER, teapotVertexPositionBuffer);
	    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(teapotData.vertexPositions), gl.STATIC_DRAW);
	    teapotVertexPositionBuffer.itemSize = 3;
	    teapotVertexPositionBuffer.numItems = teapotData.vertexPositions.length / 3;
	
	    teapotVertexIndexBuffer = gl.createBuffer();
	    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, teapotVertexIndexBuffer);
	    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(teapotData.indices), gl.STATIC_DRAW);
	    teapotVertexIndexBuffer.itemSize = 1;
	    teapotVertexIndexBuffer.numItems = teapotData.indices.length;
	
	    document.getElementById("loadingtext").textContent = "";
	  }
	
	
	
		Na tym możemy zakończyć głębszą analizę kodu. Zainteresowani mogą jeszcze rzucić okiem na zawartość funkcji drawScene() i animate(), gdyż imbryk jest animowany.
	
| Jasność: | 
| Pozycja: | X: | Y: | Z: | 
| Kolor refleksu: | R: | G: | B: | 
| Kolor rozproszenia: | R: | G: | B: | 
| Kolor: | R: | G: | B: |