Точечное освещение

На этот раз мы немного усложним освещение из прошлой записи — а именно перейдем от направленного освещения к точечному освещению, добавим динамики источнику света и введем еще несколько параметров настройки освещения.

Основное отличие точечного освещения от направленного состоит в том, что источник света имеет заданную позицию внутри сцены. Точечное освещение уже было подробно рассмотрено в двенадцатом уроке, поэтому не будем снова вдаваться в подробности и сосредоточимся на отличиях.

Действительно новая вещь, которую нам еще не приходилось рассматривать, заключается в том, что вращать мы на этот раз будем не объект сцены (в роли которого выступает чайник), а источник освещения. Специально для этих нужд выделена матрица uLightMatrix. Код фрагментного шейдера претерпел небольшие изменения — в него добавился расчет направления освещения, который ранее при направленном освещении передавался в готовом виде через uniform-переменную:


        vec4 lightLocation = uLightMatrix * vec4(uPointLightLocation, 1.0);
        vec3 lightDirection = lightLocation.xyz - vVertex.xyz;

uLightMatrix и есть та самая матрица, которая заставляет свет вращаться. Умножая uLightMatrix на uPointLightLocation мы получаем актуальные координаты освещения с учетом преобразований. Затем мы отнимаем координаты вершины от координат освещение, чтобы получить направление из одной точки в другую — это и будет вектором направления освещения lightDirection.

Вот и все, что нам было нужно добавить в шейдер для перехода от направленного освещения к точечному источнику. Теперь взглянем на то, как определяется местоположение «лампочки».

Как вы заметили, положение источника освещения задается матрицей uLightMatrix. Первым делом создаем ссылку на эту матрицу в функции создания программы:


        shaderProgram.lightMatrixUniform = gl.getUniformLocation(shaderProgram, "uLightMatrix");

Затем в функции drawScene устанавливаем нужное значение матриц:


        mat4.identity(mvMatrix);
        
        mat4.translate(mvMatrix, mvMatrix, [0.0, 0, -40.0]);
        mat4.rotateX(mvMatrix, mvMatrix, degToRad(20));
        
        lightMatrix = mat4.identity(lightMatrix);
        mat4.rotateY(lightMatrix, lightMatrix, degToRad(rY));
        mat4.translate(lightMatrix, lightMatrix, [0.0, 0.0, -1000.0]);

Как видите, чайник и источник освещения устанавливаются разными матрицами, и, следовательно, никак не зависят друг от друга. Чайник просто отдаляется от нас на 40 единиц и немного поворачивается по оси X — то есть наклоняется в нашу сторону. В таком положении он и остается на всем протяжении выполнения программы. А источник освещения вращается вокруг нас (не вокруг чайника, а именно вокруг начала системы координат) с радиусом 1000 единиц. Для нашего случая это вполне допустимо, так как чайник стоит на месте. В противном случае нужно бы было вносить корректировку в положение освещения, чтобы источник освещения сначала смещался и поворачивался на те же величины, что и чайник, а потом бы уже вращался вокруг чайника с учетом всех трансформаций.

Теперь осталось лишь передать матрицу в программу WebGL:


        gl.uniformMatrix4fv(currentProgram.lightMatrixUniform, false, lightMatrix);

В планах на эту запись осталось добавить примитивное фоновое освещение, на что мы выделим два параметра — цвет фонового освещения и его яркость (силу). В тело HTML-документа добавились, соответственно, два поля ввода:


    <input class="color {onImmediateChange:'updateMaterialColor(this);'}" value="8C9CFF" />
    Цвет материала
    <br />
    <input class="color {onImmediateChange:'updateAmbientLightColor(this);'}" value="FFFFFF" />
    Цвет фонового освещения
    <br />
    <input type="number" value="2" onchange="updateAmbientLightWeight(this)" />
    Сила фонового освещения
    <br />
    <br />
    <canvas id="canvas" width="500" height="500"></canvas>

Функция updateAmbientLightColor устанавливает цвет фонового освещения (аналогично функции updateMaterialColor, рассмотренной в предыдущей записи), а updateAmbientLightWeight задает силу освещения. Теперь посмотрим, как uniform-переменные, которые управляются в этих функциях, используются в шейдерах:


    precision mediump float;

    uniform vec3 uColor;
    
    varying vec4 vVertex;
    varying vec3 vNormal;
    varying vec4 vFinalColor;
    
    uniform mat4 uLightMatrix;
    
    uniform vec3 uPointLightLocation;
    
    uniform vec3 uAmbientLightColor;
    uniform float uAmbientLightWeight;

    const float shiness = 30.0;
    
    void main(void) {
        vec4 lightLocation = uLightMatrix * vec4(uPointLightLocation, 1.0);
        vec3 lightDirection = lightLocation.xyz - vVertex.xyz;
        
        vec3 N = normalize(vNormal);
        vec3 L = normalize(lightDirection);
        float lambertComponent = max(dot(N, -L), 0.0);
        vec3 diffuseLight = uColor * lambertComponent;
        
        vec3 eyeVec = -vec3(vVertex.xyz);
        vec3 R = normalize(eyeVec);
        vec3 E = reflect(L, N);
        float specular = pow(max(dot(E, R), 0.0), shiness);
        vec3 specularLight = uColor * specular; 
        
        vec3 sumColor = uAmbientLightColor * uAmbientLightWeight / 20.0 + diffuseLight + specularLight;
        
        gl_FragColor = vec4(sumColor, 1.0);
    }

Красным цветом выделены строки, которые отличаются от шейдера предыдущей записи. Первые две строчки тела шейдера (строки 20-21) рассчитывают направление освещения. Далее шейдер остается без изменений вплоть до расчета итогового цвета sumColor. Здесь цвет фона умножается на коэффициент, который увеличивается в зависимости от поля ввода с шагом 0.05, в результате чего мы получаем готовое фоновое освещение.

Готово! Все основные моменты рассмотрены. Есть еще и другие отличия (например, загрузка модели чайника в формате JSON заменила тор в формате XML), но все они так или иначе были рассмотрены ранее. Ну а теперь можно полюбоваться закатами и восходами на чайнике 🙂

Ссылки:

Точечное освещение
Метки:    

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *