![Пример водяного знака поверх 3D модели Пример водяного знака поверх 3D модели](https://habrastorage.org/getpro/habr/upload_files/8e4/ae1/c1a/8e4ae1c1abb54353be60b3cce993551b.jpg)
В данном уроке напишем собственный шейдер, который будет накладывать текстуру поверх 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.
Пример реализации данного шейдера можно посмотреть по ссылке.