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: |