Текстура и освещение

Еще одна деталь — добавим в композицию текстуру (это как раз использование готового изображения). Тут все очень напоминает работу с цветом, что неудивительно: текстура в WebGL (как и в OpenGL) — это, по сути, закрашивание фигуры другой картинкой. Приступим:

Var myTexture:

Function initTexture() {

MyTexture = gl. treateTexture();

Рис. 68. Теперь настоящее 3D

MyTexture. image = new Image(): myTexture. image. onload = function () { LoadedTexture(neheTexture)

}

MyTexture. image. src = ”logo. gif”;

}

Сначала создаем глобальную переменную для хранения нашей текстуры (если вы создаете реальный проект и у вас несколько текстур, крайне рекомендую придумать более изящное решение), затем создаем объект текстуры. Так же как и при работе с canvas, используем картинку-основу после ее загрузки. Функция LoadedTexture реализуется следующим образом:

Function LoadedTexture(texture) {

Gl. bindTexture(gl. TEXTURE_2D, texture); gl. pixelStorei(gl. UNPACK_FLIP_Y_WEBGL, true);

Gl. texImage2D(gl. TEXTURE_2D, 0, gl. RGBA, gl. RGBA, gl. UNSIGNED_BYTE, texture. image); gl. texParameteri(gl. TEXTURE_2D, gl. TEXTURE_MAG_FILTER, gl. NEAREST); gl. texParameteri(gl. TEXTURE_2D, gl. TEXTURE_MIN_FILTER, gl. NEAREST); gl. bindTexture(gl. TEXTURE_2D, null);

}

Тут мы делаем много всяких странных вещей. Во-первых, мы связываем текстуру с объектом, объявляем ее «текущей» (как делали ранее для цвета), переворачиваем по вертикали (WebGL, в отличие от прочих веб-технологий, считает координаты снизу вверх). Следующими командами указывается размещение картинки в видеопамяти графической карты, затем загружаются туда она сама, ее характеристики и параметры масштабирования (то есть сообщается поведение текстуры при уменьшении и увеличении объекта).

Естественно, шейдеры для отрисовки сцены с текстурой у нас будут несколько другие:

<script id="shader-fs" type="x-shader/x-fragment"> precision mediump float;

#ifdef GL_ES precision highp float;

#endif

Varying vec4 vColor;

Void main(void) {

Gl_FragColor = vColor;

}

</script>

<script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; attribute vec4 aVertexColor;

Uniform mat4 uMVMatrix: uniform mat4 uPMatrix: varying vec4 vColor;

Void main(void) {

Gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vColor = aVertexColor;

}

</script>

В вершинном шейдере мы принимаем координаты текстуры (как и в случае с цветом) как свойства каждой из вершин и передаем их прямо в переменные.

Теперь свяжем структуру с нашим кубом, предварительно «натянув» ее на фигуру:

CubeVertexTextureCoordBuffer = gl. createBuffer();

Gl. bindBuffer(gl. ARRAY_BUFFER, cubeVertexTextureCoordBuffer);

Var textureCoords = [

Front face 0. 0, 0. 0,

1.0, 0.0,

1.0, 1.0,

0.0, 1.0,

Back face 1.0, 0.0,

1.0, 1.0,

0.0, 1.0,

0. 0, 0. 0,

Top face 0.0, 1.0,

0. 0, 0. 0,

1.0, 0.0,

1.0, 1.0,

Bottom face 1.0, 1.0,

0.0, 1.0,

0. 0, 0. 0,

1.0, 0.0,

Right face

1.0, 0.0,

1.0, 1.0,

0.0, 1.0,

0. 0, 0. 0,

Left face 0. 0, 0. 0,

1.0, 0.0,

1.0, 1.0,

0.0, 1.0,

];

Gl. bufferData(gl. ARRAY_BUFFER, new Float32Array(textureCoords), gl. STATIC_DRAW); cubeVertexTextureCoordBuffer. itemSize = 2; cubeVertexTextureCoordBuffer. numItems = 24; cubeBuffer. texture = cubeVertexTextureCoordBuffer;

Для установки этих координат мы считаем, что текстура имеет ширину 1.0 при высоте 1.0, таким образом: (0, 0) — левая нижняя

Часть, (1, 1) — верхняя правая. WebGL сама транслирует эти данные в реальное разрешение текстуры картинки.

Теперь применим ее при отрисовке:

Gl. bindBuffer(gl. ARRAY_BUFFER, cubeBuffer. texture);

Gl. vertexAttribPointer(shaderProgram. textureCoordAttribute, cubeBuffer. texture itemSize, gl. FLOAT, false, 0, 0);

Gl. activeTexture(gl. TEXTURE0): gl. bindTexture(gl. TEXTURE_2D, myTexture);

Тут тоже мало нового и все интуитивно понятно. Мы объявляем, что следует использовать ранее загруженную текстуру 0 — это та, которую мы перед этим загрузили (в WebGL текстуры нумеруются по порядку, всего можно использовать 32 текстуры).

Результат — на рис. 69 (чтобы в нашем примере не обременять себя лишним кодом, теперь ограничимся в дальнейшем только кубиком).

Рис. 69. Натягиваем текстуру

Последний аспект WebGL, про который я хотел рассказать, касается работы со светом.

Сначала, как это ни печально, мы расстанемся с нашим розовым фоном:

Сейчас наша цель — иметь возможность смоделировать источник света в рамках сцены. Такой источник должен, грубо говоря, делать светлой обращенные к нему стороны объекта и темной противоположную. В общем, освещенность — это очень большая и сложная тема, мы не будем сейчас вдаваться в дебри, а просто как данность примем тот факт, что для создания простого линейного освещения нам нужно задать вектор нормали к поверхности для источника света. Что мы немедленно и сделаем. Начнем с шейдеров:

<script id="shader-fs" type="x-shader/x-fragment"> precision mediump float;

Varying vec2 vTextureCoord: varying vec3 vLightWeighting;

Uniform sampler2D uSampler; void main(void) {

Vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord. s, vTextureCoord. t)); gl_FragColor = vec4(textureColor. rgb * vLightWeighting, textureColor. a);

}

</script>

Тут мы, как вы видите, извлекаем цвет из текстуры, от вершинного шейдера, который изменился гораздо сильнее:

<script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; attribute vec3 aVertexNormal; attribute vec2 aTextureCoord:

Uniform mat4 uMVMatrix; uniform mat4 uPMatrix; uniform mat3 uNMatrix;

Uniform vec3 uAmbientColor;

Uniform vec3 uLightingDirection; uniform vec3 uDirectionalColor;

Uniform bool uUseLighting;

Varying vec2 vTextureCoord

Void main(void) {

Gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = aTextureCoord;

If (!uUseLighting) {

VLightWeighting = vec3(1.0, 1.0, 1.0):

} else {

Vec3 transformedNormal = uNMatrix * aVertexNormal; float directionalLightWeighting = max(dot(transformedNormal, uLightingDirection), 0.0):

VLightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting:

}

}

</script>

А VertexNormal тут устанавливает нормали вершин, которые мы определяем в itBuffers. UNMatrix — наша нормальная матрица, а uUseLighting — универсальная форма, определяющая, есть ли осве щение.

UambientColor, uDirectionalColor, и uLightingDirection — значения параметров освещения, которые мы установим при рисовании.

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

Теперь вносим изменения в функцию setMatrixUniforms, которая копирует матрицы просмотра модели и проецирования в универсальные формы шейдера. Сюда мы добавим следующее:

Function setMatrixUniforms() {

Gl. uniformMatrix4fv(shaderProgram. pMatrixUniform, false, pMatrix):

Gl. uniformMatrix4fv(shaderProgram. mvMatrixUniform, false, mvMatrix):

Var normalMatrix = mat3.create();

Mat4.toInverseMat3(mvMatrix, normalMatrix):

Mat3.transpose(normalMatrix):

Gl. uniformMatrix3fv(shaderProgram. nMatrixUniform, false, normalMatrix):

}

Эти строчки копируют новую матрицу, основанную на матрице просмотра модели.

Теперь можно создавать саму нормаль:

CubeVertexNormalBuffer = gl. createBuffer();

Gl. bindBuffer(gl. ARRAY_BUFFER, cubeVertexNormalBuffer); var vertexNormals = [

Front face 0.0,  0.0,  1.0,

0.0,  0.0,  1.0,

0.0,  0.0,  1.0,

0.0,  0.0,  1.0,

Back face 0.0,  0.0,  -1.0,

0.0,  0.0,  -1.0,

0.0,  0.0,  -1.0,

0.0,  0.0,  -1.0,

Top face 0.0,  1.0,  0.0,

0.0,  1.0,  0.0,

0.0,  1.0,  0.0,

0.0,  1.0,  0.0,

Bottom face 0.0,  -1.0,  0.0,

0.0,  -1.0,  0.0,

0.0,  -1.0,  0.0,

0.0,  -1.0,  0.0,

Right face

1.0,  0.0,  0.0,

1.0,  0.0,  0.0,

1.0,  0.0,  0.0,

1.0,  0.0,  0.0,

Left face

1.0,

0.0,

0.0,

1.0,

0.0,

0.0,

1.0,

0.0,

0.0,

1.0,

0.0,

0.0

];

Gl. bufferData(gl. ARRAY_BUFFER, new Float32Array(vertexNormals), gl. STATIC_DRAW);

CubeVertexNormalBuffer. itemSize = 3; cubeVertexNormalBuffer. numItems = 24; cubeBuffer. normal = cubeVertexNormalBuffer;

При отрисовке:

Gl. bindBuffer(gl. ARRAY_BUFFER, cubeVertexNormalBuffer);

Gl. vertexAttribPointer(shaderProgram. vertexNormalAttribute, cubeVertexNormal;

С этим все понятно, далее (после отрисовки текстуры):

Gl. uniform1i(shaderProgram. samplerUniform, 0): gl. uniform1i(shaderProgram. useLightingUniform, 1); gl. uniform3f(shaderProgram. ambientColorUniform, 0.2,1.0,0.4); var lightingDirection = [-0.5,-0.5,-1.0]; var adjustedLD = vec3.create(); vec3.normalize(lightingDirection, adjustedLD): vec3.tcale(adjustedLD, -1);

Gl. uniform3fv(shaderProgram. lightingDirectionUniform, adjustedLD): gl. uniform3f(shaderProgram. directionalColorUniform,1.0,0.7,0.5):

Тут все не очень просто, но мы сейчас разберемся. Во-первых, мы сообщаем о необходимости использовать модель освещения. Затем задаем цвет общего (ненаправленного) освещения. Определяем его через RGB. Потом в переменную lightingDirection записываем направление освещения. Мы корректируем вектор направления освещения, используя модуль vec3, — он является частью glMatrix. Первая корректировка, normalize, изменяет масштаб до единичной длины вектора. Вторая корректировка — умножение вектора на -1 — необходима для изменения его направления. Затем с помощью функции gl. uniform3fv мы отправляем данные в универсальную форму шейдера. Туда же отправляем информацию по цветовой составляющей ненаправленного цвета.

На этом все, результат — на рис. 70 (я чуть-чуть изменил положение кубика для лучшей демонстрации эффекта).

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

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