Пример водяного знака поверх 3D модели
Пример водяного знака поверх 3D модели

В данном уроке напишем собственный шейдер, который будет накладывать текстуру поверх 3D сцены с прозрачностью и сохранением пропорций изображения. Для этого будем использовать post-processing.

Под нашу задачу уже есть частичное решение в виде класса TexturePass, который работает на основе базового шейдера CopyShader. Данный шейдер может разместить поверх сцены текстуру с прозрачностью, но без сохранения пропорций. Как раз эту проблему мы и будем решать в нашем собственном шейдере.

Для начала добавим в TexturePass возможность передавать свой шейдер.

class TexturePass extends Pass
{
  constructor(map, opacity, shader = undefined)
  {
    super();
    if (shader === undefined && CopyShader === undefined)
    {
      console.error('TexturePass relies on CopyShader');
    }
    ...
  }
  ...
}

Далее создадим класс TexturePassExtend и унаследуем его от TexturePass.

class TexturePassExtend extends TexturePass
{
  constructor(map, opacity)
  {
    super(map, opacity, TexturePassExtendShader);
  }
 
  setRenderSize(size)
  {
    if (this.map === undefined)
      return;
    
    const mapWidth = this.map.image.width;
    const mapHeight = this.map.image.height;
    const mapRatio = mapWidth / mapHeight;
    
    const scaleX = mapWidth / size.x;
    const scaleY = (mapWidth / mapRatio) / size.y;
    
    this.uniforms['scaleX'].value = scaleX;
    this.uniforms['scaleY'].value = scaleY;
  }
}

Здесь мы передаем в TexturePass наш шейдер TexturePassExtendShader и описываем метод setRenderSize для корректировки поведения шейдера при смене размеров холста сцены.

Для написания собственного шейдера за основу возьмем CopyShader. Нас в первую очередь будет интересовать вершинный шейдер.

var TexturePassExtendShader = {
 
  uniforms:
    {
      "tDiffuse": {value: null},
      "opacity": {value: 1.0},
      "scaleX": {value: 1},
      "scaleY": {value: 1}
    },
 
  vertexShader:
    [
      "uniform float scaleX;",
      "uniform float scaleY;",
      "varying vec2 vUv;",
      "void main() {",
      "  vUv = uv;",
      "  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
      "  gl_Position.x *= scaleX;",
      "  gl_Position.y *= scaleY;",
      "}"
    ].join("\n"),
 
  fragmentShader:
    [
      "uniform sampler2D tDiffuse;",
      "uniform float opacity;",
      "varying vec2 vUv;",
      "void main() {",
      "  gl_FragColor = texture2D(tDiffuse, vUv);",
      "  gl_FragColor.a *= opacity;",
      "}"
    ].join("\n")
};

В вершинном шейдере мы корректируем размеры текстуры по ширине и высоте через переменные scaleX и scaleY, которые мы и корректируем в методе setRenderSize с целью сохранения пропорций нашей текстуры.

Надеюсь, что моя статья сподвигнет многих разработчиков пробовать писать собственные решения на языке шейдеров GLSL.

Пример реализации данного шейдера можно посмотреть по ссылке.

Комментарии (5)


  1. Imp5
    30.06.2023 08:20
    +5

    И убираем водяной знак...


    1. site3d Автор
      30.06.2023 08:20

      Спасибо за демонстрацию. В любом случае, цель статьи - показать, как можно такой шейдер написать, а как его использовать - дело другое. Надеюсь, что кому-то будет полезно.


    1. iliazeus
      30.06.2023 08:20

      Там и модельку вполне скачать можно; URL есть в window.site3dWidget_1249.model.path.


      1. site3d Автор
        30.06.2023 08:20

        Спасибо за замечание к безопасности кода.


    1. skymal4ik
      30.06.2023 08:20

      И сколько реальных % людей может это сделать?) а те, кто могут и будут - ещё меньше :)