WebGL Урок 16 — Рендеринг в текстуру

<< Урок 15

Материал в оригинале можно найти здесь

Добро пожаловать на мой шестнадцатый урок по WebGL! В нем мы познакомимся с очень полезной техникой: рендеринг 3D-сцены в текстуру, которая в дальнейшем может использоваться при отрисовке другой сцены. Это позволяет не только иметь сцену внутри другой сцены, как на онлайн-демонстрации к этому уроку, но и является основанием для таких возможностей как отбор (выбор 3D-объектов с помощью мыши), тени, отражение и множество других 3D-эффектов.

Вот как выглядит результат урока в браузере с поддержкой WebGL:

Здесь можно посмотреть онлайн-демонстрацию, если ваш браузер поддерживает WebGL. Здесь можно узнать, что делать, если браузер не поддерживает WebGL. Вы увидите модель белого ноутбука со всевозможными эффектами освещения, который мы разбирали в предыдущих уроках (включая блики на экране). Но более интересно то, что на экране ноутбука вы увидите еще одну 3D-сцену — вращающуюся Луну и ящик из тринадцатого урока. Я думаю, что вы поймете, что мы отрисовали сцену из тринадцатого урока в текстуру и затем использовали эту текстуру для экрана ноутбука.

Как же это все работает? Читайте дальше и узнаете.

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

Как и прежде здесь могут быть ошибки. Однако, благодаря помощи Marco Di Benedetto, создателя SpiderGL и Paul Brunt, известному по GLGE, а так же легиону тестеров (в частности, Stephen White), в этом уроке содержится гораздо меньше ошибок, чем могло бы быть. Конечно же, все ошибки на моей совести, поэтому прошу вас без сомнений сообщать мне о них :).

Вы можете посмотреть код этого примера двумя способами: посмотреть исходный код страницы с демонстрацией или, если вы используете GitHub, вы можете копировать урок (и другие уроки) из репозитория.

Когда вы скопируете код, откройте index.html в текстовом редакторе. По сравнению с предыдущими уроками файл содержит довольно мало изменений, поэтому давайте начнем с самого низа страницы и будем продвигаться вверх. Для начала webGLStart. Как и обычно, новые части выделены красным:


  function webGLStart() {
    var canvas = document.getElementById("lesson16-canvas");
    initGL(canvas);
    initTextureFramebuffer();
    initShaders();
    initBuffers();
    initTextures();
    loadLaptop();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);

    tick();
  }

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

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

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

Итак, довольно теории, пора приступить к коду. Вы найдете Функцию initTextureFramebuffer где-то в конце первой трети файла.


  var rttFramebuffer;
  var rttTexture;

  function initTextureFramebuffer() {

Перед объявлением функции идут глобальные переменные для хранения фреймбуфера, в который мы собираемся рендерить сцену, которая будет отображаться на экране планшета, и для хранения текстуры, которая будет хранить результаты рендеринга в этот фреймбуфер (к которому нам понадобится доступ при отрисовке ноутбука). Далее по функции:


    rttFramebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
    rttFramebuffer.width = 512;
    rttFramebuffer.height = 512;

Первым делом мы создаем сам фреймбуфер, а затем привычной функцией (аналогично текстурам, буферам и прочим) мы назначаем его текущим — то есть буфером, с которым далее будут работать функции. Мы также сохраняем ширину и высоту сцены, которая будет отрисована в буфер. Эти атрибуты не являются частью фреймбуфера, мы просто используем возможность JavaScript назначать объекту произвольные свойства, так как они понадобятся нам позже при работе с фреймбуфером. Я выбрал размер 512×512 пикселей. Как вы помните, размер текстуры должен быть степенью двойки, и мне показалось, что 256×256 была слишком угловатой, а 1024×1024 не сильно улучшала вид.

Теперь создадим объект текстуры и установим привычные параметры:


    rttTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, rttTexture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);

Но здесь не обошлось без одного отличия: gl.texImage2D имеет другие входные параметры:


      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttFramebuffer.width, rttFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

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

Хорошо, у нас есть пустая текстура, которая может хранить значения цветов нашей отрисованной сцены. Теперь создадим буфер глубины для хранения информации о этой самой глубине:


    var renderbuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height);

Здесь мы создали объект рендер-буфера. Это тип объектов для резервирования области памяти, которую мы планируем связать с фреймбуфером. Мы устанавливаем его текущим (как и в случае с текстурами, фреймбуфером и всем остальным, WebGL имеет текущий рендер-буфер), и затем вызываем gl.renderbufferStorage, чтобы сказать WebGL, что текущему рендер-буферу требуется достаточно памяти для хранения 16-битных значений глубины по всему буферу с заданной шириной и высотой.

Далее:


    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

Мы прикрепляем все к текущему фреймбуферу (вспомните, что мы назначили новый буфер текущим сразу после его создания в начале функции). Затем мы указываем, что наша текстура будет пространством для рендеринга цветов (gl.COLOR_ATTACHMENT0), а созданный буфер глубины будет содержать информацию о глубине (gl.DEPTH_ATTACHMENT).

Теперь вся память фреймбуфера настроена и WebGL знает, куда производить рендеринг. После этого мы возвращаем значения текущей текстуры, рендер-буфера и фреймбуфера обратно к значениям по умолчанию:


    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  }

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


  var laptopAngle = 0;

  function drawScene() {
    gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
    drawSceneOnLaptopScreen();

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    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);

В свете вышеизложенных объяснений, происходящее здесь должно быть достаточно очевидным: мы переключаемся с исходного фреймбуфера, который отрисовывает сцену на страницу HTML, на фреймбуфер, который отрисовывает в текстуру и который мы создали в initTextureFramebuffer. Далее мы вызываем функцию drawSceneOnLaptopScreen для отрисовки сцены на экран ноутбука (точнее, отрисовка в RTT-фреймбуфер [RTT — render-to-texture, рендеринг в текстуру]), а после этого мы переключаемся обратно на исходный фреймбуфер. Перед дальнейшим разбором drawScene стоит уделить внимание функции drawSceneOnLaptopScreen. Я не буду дублировать ее здесь, потому что она совсем не сложная — это просто урезанная версия функции drawScene из тринадцатого урока! Дело в том, что код отрисовки до текущего момента времени не имел представления, куда он выполняет рендеринг. Он просто отрисовывал в текущий фреймбуфер. Небольшие отличия заключаются в упрощении — например, мы убрали подвижный источник света и другие вещи из тринадцатого урока, которые несущественны для текущего урока.

Итак, когда первые три строчки кода функции drawScene выполнены, мы получаем сцену из тринадцатого урока, отрисованную в текстуру. Оставшаяся часть drawScene просто отрисовывает ноутбук и использует эту готовую текстуру для экрана. Начинаем мы с обычного кода по настройке матрицы модель-вид и повороту ноутбука на угол laptopAngle (который, как и в других уроках, изменяется в функции animate, которая вызывается при каждой отрисовке, что и заставляет ноутбук вращаться):


    mat4.identity(mvMatrix);

    mvPushMatrix();

    mat4.translate(mvMatrix, [0, -0.4, -2.2]);
    mat4.rotate(mvMatrix, degToRad(laptopAngle), [0, 1, 0]);
    mat4.rotate(mvMatrix, degToRad(-90), [1, 0, 0]);

Теперь мы передаем цвета и координаты наших источников освещения в видеокарту, все как обычно:


    gl.uniform1i(shaderProgram.showSpecularHighlightsUniform, true);
    gl.uniform3f(shaderProgram.pointLightingLocationUniform, -1, 2, -1);

    gl.uniform3f(shaderProgram.ambientLightingColorUniform, 0.2, 0.2, 0.2);
    gl.uniform3f(shaderProgram.pointLightingDiffuseColorUniform, 0.8, 0.8, 0.8);
    gl.uniform3f(shaderProgram.pointLightingSpecularColorUniform, 0.8, 0.8, 0.8);

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


    // The laptop body is quite shiny and has no texture.  It reflects lots of specular light
    gl.uniform3f(shaderProgram.materialAmbientColorUniform, 1.0, 1.0, 1.0);
    gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 1.0, 1.0, 1.0);
    gl.uniform3f(shaderProgram.materialSpecularColorUniform, 1.5, 1.5, 1.5);
    gl.uniform1f(shaderProgram.materialShininessUniform, 5);
    gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 0.0, 0.0, 0.0);
    gl.uniform1i(shaderProgram.useTexturesUniform, false);

Теперь, если все координаты вершин ноутбука загружены, мы приступаем к отрисовке. Этот код должен выглядеть достаточно знакомым, особенно после четырнадцатого урока (из которого он скопирован)


    if (laptopVertexPositionBuffer) {
      gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexPositionBuffer);
      gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, laptopVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

      gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexTextureCoordBuffer);
      gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, laptopVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

      gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexNormalBuffer);
      gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, laptopVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);

      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, laptopVertexIndexBuffer);
      setMatrixUniforms();
      gl.drawElements(gl.TRIANGLES, laptopVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
    }

И вот у нас отрисован корпус ноутбука. Далее необходимо отрисовать экран. Параметры освещения уже заданы, и теперь нам нужно установить излучательный цвет:


    gl.uniform3f(shaderProgram.materialAmbientColorUniform, 0.0, 0.0, 0.0);
    gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 0.0, 0.0, 0.0);
    gl.uniform3f(shaderProgram.materialSpecularColorUniform, 0.5, 0.5, 0.5);
    gl.uniform1f(shaderProgram.materialShininessUniform, 20);
    gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 1.5, 1.5, 1.5);
    gl.uniform1i(shaderProgram.useTexturesUniform, true);

Что же такое излучательный цвет? Экраны таких вещей как ноутбуки не просто отражают цвет — они испускаю его. Мы хотим, чтобы цвет экрана определялся преимущественно цветом текстуры, а не световыми эффектами. Мы могли бы добиться этого эффекта с помощью изменения uniform-переменных, которые отвечают за цвет, чтобы перед отрисовкой сцены точечное освещение было выключено, а фоновое освещение в это время поднято до 100%, а затем восстанавливать старые значения. Но это было бы не совсем правильным: все-таки свечение — это свойство экрана, а не освещения. В этом конкретном случае мы могли бы просто использовать фоновое освещение, так как оно белого цвета. Поэтому установка фонового цвета в 1.5, 1.5, 1.5 давало бы правильный эффект. Но если бы кто-то в дальнейшем изменил фоновое освещение, то цвет экрана бы тоже изменился, а это уже выглядит странно. Ведь монитор не начинает светить красным, если сидеть с ним в комнате с красной лампой. Поэтому мы используем новую uniform-переменную для излучательного цвета, который управляется небольшим фрагментом кода шейдера (мы рассмотрим его позже).

(Замечание: стоит помнить, что излучательный цвет объекта сцены не влияет на другие объекты вокруг — то есть излучательный цвет не превращает объект в источник освещения. Это просто подход, при котором объект имеет цвет, который не зависит от освещения сцены.)

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

Хорошо, двигаемся дальше. Мы привязываем буферы, которые определяют атрибуты вершин экрана ноутбука:


    gl.bindBuffer(gl.ARRAY_BUFFER, laptopScreenVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, laptopScreenVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, laptopScreenVertexNormalBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, laptopScreenVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, laptopScreenVertexTextureCoordBuffer);
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, laptopScreenVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

Далее мы указываем, что мы будем использовать текстуру, на которую мы отрисовали сцену ранее:


    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, rttTexture);
    gl.uniform1i(shaderProgram.samplerUniform, 0);

И последним делом отрисовываем экран:


    setMatrixUniforms();
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, laptopScreenVertexPositionBuffer.numItems);

    mvPopMatrix();
  }

Теперь можно немного расслабиться ;). Все необходимое для рендеринга сцены в текстуру и дальнейшего использования этой текстуры было сделано.

Теперь давайте быстро пройдемся по другим отличиям от предыдущих уроков. Пара функций loadLaptop и handleLoadedLaptop загружают JSON-модель ноутбука. Концептуально здесь ничего не поменялось по сравнению с кодом для загрузки чайника из четырнадцатого урока. Есть также кусочек кода в конце функции initBuffers для инициализации буфера вершин для экрана ноутбука. Это немного некрасиво и будет улучшено в следующих версиях этого урока (значения должны загружаться из JSON, но сейчас находятся в коде).

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


  precision mediump float;

  varying vec2 vTextureCoord;
  varying vec3 vTransformedNormal;
  varying vec4 vPosition;

  uniform vec3 uMaterialAmbientColor;
  uniform vec3 uMaterialDiffuseColor;
  uniform vec3 uMaterialSpecularColor;
  uniform float uMaterialShininess;
  uniform vec3 uMaterialEmissiveColor;

  uniform bool uShowSpecularHighlights;
  uniform bool uUseTextures;

  uniform vec3 uAmbientLightingColor;

  uniform vec3 uPointLightingLocation;
  uniform vec3 uPointLightingDiffuseColor;
  uniform vec3 uPointLightingSpecularColor;

  uniform sampler2D uSampler;

  void main(void) {
    vec3 ambientLightWeighting = uAmbientLightingColor;

    vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
    vec3 normal = normalize(vTransformedNormal);

    vec3 specularLightWeighting = vec3(0.0, 0.0, 0.0);
    if (uShowSpecularHighlights) {
      vec3 eyeDirection = normalize(-vPosition.xyz);
      vec3 reflectionDirection = reflect(-lightDirection, normal);

      float specularLightBrightness = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
      specularLightWeighting = uPointLightingSpecularColor * specularLightBrightness;
    }

    float diffuseLightBrightness = max(dot(normal, lightDirection), 0.0);
    vec3 diffuseLightWeighting = uPointLightingDiffuseColor * diffuseLightBrightness;

    vec3 materialAmbientColor = uMaterialAmbientColor;
    vec3 materialDiffuseColor = uMaterialDiffuseColor;
    vec3 materialSpecularColor = uMaterialSpecularColor;
    vec3 materialEmissiveColor = uMaterialEmissiveColor;
    float alpha = 1.0;
    if (uUseTextures) {
      vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
      materialAmbientColor = materialAmbientColor * textureColor.rgb;
      materialDiffuseColor = materialDiffuseColor * textureColor.rgb;
      materialEmissiveColor = materialEmissiveColor * textureColor.rgb;
      alpha = textureColor.a;
    }
    gl_FragColor = vec4(
      materialAmbientColor * ambientLightWeighting
      + materialDiffuseColor * diffuseLightWeighting
      + materialSpecularColor * specularLightWeighting
      + materialEmissiveColor,
      alpha
    );
  }

И на этом уже действительно все! В этом уроке мы рассмотрели рендеринг сцены в текстуру и использовании ее в другой сцене, а также затронули работу с цветами материалов. В следующем уроке я покажу, как кое-что действительно полезное с помощью этого: GPU picking, с помощью чего можно моделировать 3D-сцены, с которыми люди могут взаимодействовать через клики мышью на объекты.

<< Урок 15

5 thoughts on “WebGL Урок 16 — Рендеринг в текстуру

  • 26.08.2017 at 09:53
    Permalink

    Скажите, если я нарисовал что-то во фреймбуфер, могу я вывести его на экран какой-нибудь простой командой?

    Ответить
  • 26.08.2017 at 12:54
    Permalink

    И ещё вопрос — можно ли прикрепить к фреймбуферу несколько текстур? Сейчас пытаюсь рисовать полупрозрачные шарики, нашёл алгоритм для OpenGL, где к фреймбуферу приделаны две текстуры и шейдер рисует то в одну, то в другую примерно так
    gl_FragData[0] = … ;
    gl_FragData[1] = … ;
    Будет ли это работать в WebGL? Если сделать так
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture0, 0);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, texture1, 0);

    Ответить
    • 18.10.2017 at 09:43
      Permalink

      конечно будет. Только надо, что бы шейдере первая строка была:

      #extension GL_EXT_draw_buffers : require
      ….

      А в инициализации webgl:
      gl = canvas.getContext(‘experimental-webgl’, {…});

      extMRT = this.gl.getExtension(‘WEBGL_draw_buffers’);

      fb = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
      gl.framebufferTexture2D(gl.FRAMEBUFFER, extMRT.COLOR_ATTACHMENT0_WEBGL, gl.TEXTURE_2D, targetTexture1, 0);
      gl.framebufferTexture2D(gl.FRAMEBUFFER, extMRT.COLOR_ATTACHMENT1_WEBGL, gl.TEXTURE_2D, targetTexture2, 0);

      extMRT.drawBuffersWEBGL([
      extMRT.COLOR_ATTACHMENT0_WEBGL,
      extMRT.COLOR_ATTACHMENT1_WEBGL
      ]);

      Ответить
  • 27.08.2017 at 13:47
    Permalink

    И ещё один вопрос, если не трудно. Допустим, у меня есть картинка, нарисованная в текстуру. Как мне применить к ней (целиком) некоторый фрагментный шейдер? Допустим, я хочу сделать все цвета бледнее или ещё что-то в этом роде. Нужен ли какой-то вершинный шейдер, нужна ли какая-то команда draw?

    Ответить

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

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