WebGL 2D: простой как Web

без GL

WebGL 2D:
простой как Web

Коротаев Александр

@mamu_eval
github.com/lekzd

  • Фронтенд разработчик в Tinkoff.ru
  • Spb-frontend
  • подкаст Drinkcast

web-standards-ru/cfp-list

WebGL
выглядит как магия

Развитие за 9 лет

WebGL

  • версия 1
  • версия 2
  • WebGPU ?

API не из Web

        gl.activeTexture(gl.TEXTURE0)
        gl.bindTexture(gl.TEXTURE_2D, texture)
        gl.texImage2D(this.gl.TEXTURE_2D, ..., image)
        gl.uniform1i(gl.getUniformLocation('u_image'), 0);
    
https://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences

API не из Web

        setState(arg1)
        setState(arg1, arg2)
        setState(arg1, ..., arg9)
        // but you can't
        let state = getState()
    

WebGL это

Нужна новая либа?

Canvas API -> WebGL

gameclosure/webgl-2d

Никто не должен понять WebGL

Пререндеринг
рисуем заранее

Как это выглядит

Демо
https://webglfundamentals.org/webgl/lessons/webgl-image-processing.html

Алгоритм

Самое важное

            const texture = gl.createTexture()
            const canvas = document.createElement('canvas')
             
            gl.activeTexture(gl.TEXTURE0)
            gl.bindTexture(gl.TEXTURE_2D, texture)
            gl.texImage2D(gl.TEXTURE_2D, ..., canvas)
        

Создание текстуры

            const texture = gl.createTexture()
            const canvas = document.createElement('canvas')
             
            gl.activeTexture(gl.TEXTURE0)
            gl.bindTexture(gl.TEXTURE_2D, texture)
            gl.texImage2D(gl.TEXTURE_2D, ..., canvas)
        

Canvas как текстура

            const texture = gl.createTexture()
            const canvas = document.createElement('canvas')
             
            gl.activeTexture(gl.TEXTURE0)
            gl.bindTexture(gl.TEXTURE_2D, texture)
            gl.texImage2D(gl.TEXTURE_2D, ..., canvas)
        

Первый шейдер

        uniform sampler2D u_background;
        varying vec2 v_texCoord;
         
        void main() {
          gl_FragColor = texture2D(u_background, v_texCoord);
        }
    

Больше картинок!

+ =

Уже знакомый код

        const texture = gl.createTexture()
        const canvas = document.createElement('canvas')
         
        gl.activeTexture(gl.TEXTURE0)
        gl.bindTexture(gl.TEXTURE_2D, texture)
        gl.texImage2D(gl.TEXTURE_2D, ..., canvas)
    
https://webglfundamentals.org/webgl/lessons/ru/webgl-2-textures.html

            // получаем ссылки на сэмплеры
            var u_image0 = gl.getUniformLocation(program, "u_image0")
            var u_image1 = gl.getUniformLocation(program, "u_image1")
             
            // задаём, какой текстурный блок использовать при рендеринге
            gl.uniform1i(u_image0, 0)
            gl.uniform1i(u_image1, 1)
             
            // привязываем текстуру к текстурному блоку
            gl.activeTexture(gl.TEXTURE0)
            gl.bindTexture(gl.TEXTURE_2D, textures[0])
            gl.activeTexture(gl.TEXTURE1)
            gl.bindTexture(gl.TEXTURE_2D, textures[1])
        

Создаем и храним ссылки на текстуры

        const textures = []
        function addTexture(canvas, name) {
          const texture = gl.createTexture()
          const location = gl.getUniformLocation(program, name)
          textures.push({texture, canvas, location})
        }
    

Обновляем текстуры

        textures.forEach(({texture, canvas, location}, i) => {
          gl.activeTexture(gl[`TEXTURE${i}`])
          gl.bindTexture(gl.TEXTURE_2D, texture)
          gl.texImage2D(this.gl.TEXTURE_2D, ..., canvas)
          gl.uniform1i(location, i);
        })
    

Активация текстурного блока

        textures.forEach(({texture, canvas, location}, i) => {
          gl.activeTexture(gl[`TEXTURE${i}`])
          gl.bindTexture(gl.TEXTURE_2D, texture)
          gl.texImage2D(this.gl.TEXTURE_2D, ..., canvas)
          gl.uniform1i(location, i);
        })
    

Передача Canvas

        textures.forEach(({texture, canvas, location}, i) => {
          gl.activeTexture(gl[`TEXTURE${i}`])
          gl.bindTexture(gl.TEXTURE_2D, texture)
          gl.texImage2D(this.gl.TEXTURE_2D, ..., canvas)
          gl.uniform1i(location, i);
        })
    

2 текстуры в шейдере

        uniform sampler2D u_background;
        uniform sampler2D u_foreground;
        varying vec2 v_texCoord;
        void main() {
          gl_FragColor = texture2D(u_background, v_texCoord)
              + texture2D(u_foreground, v_texCoord);
        }
    

+
=

*
=

/
=
Шейдеры
Эффекты поверх canvas

Evil Glitch

Ссылка на игру

Про тег <script>

Но Webpack лучше

        rules: [
        ...
          {
              test: /\.frag?$/,
              use: 'raw-loader',
          }]
    

😍😍😍

        import * as fragmentShader from './shader.frag'
        import * as vertexShader from './shader.vert'
    

WebGL utils

https://webglfundamentals.org/docs/module-webgl-utils.html
        const shaders = [vertexShader, fragmentShader]
        const program = createProgramFromSources(gl, shaders)
    

Ссылки по шейдерам

Демо

React + WebGL
используем JSX

Что можно взять

Почему Pixi.js?





https://codesandbox.io/s/q7oj1p0jo6

Демо

React-pixi-fiber

        <Container>
            {state.enemies.map(e => (<Enemy {...e}/>))}
            {state.explosions.map(e => (<Explosion {...e}/>))}
            <Actor {...state.hero}/>
            <Header x={0} y={0} text={state.kills}/>
        <Container>
    

React-pixi-fiber

На прод?
молимся и деплоим

На проде

React-pixi-fiber

❤️ Демка всего за день
❤️ Адаптивность
❤️ Ретина

Игра как интерфейс

        <Clickable onClick={_=>this.setState({screen: 'game'})}>
          <Text text="Играть" />
        </Clickable>
         
        {this.renderScreen(this.state.screen)}
    

Ретина

        const OPTIONS = {
          backgroundColor: color('#12bcf6'),
          autoResize: true,
          resolution: window.devicePixelRatio || 1,
        };
    

Адаптивность

        <Button width={clamp(props.width * .6, 100, 200)}>
    
        Button {
          width: 60%;
          min-width: 100px;
          max-width: 200px;
        }
    

SVG

Рендерится в растр, потому растягиваем его в 2 раза

        <svg width="100" height="100" 
          viewBox="0 0 50 50" ... >
          ...
        </svg>
    

setState() 60 FPS

        componentDidMount() {
          this.props.app.ticker.add(this.onTick);
        }
         
        onTick() {
          this.setState({enemies, actor, time...})
        }
    

Долой setState()

pixi-viewport

Custom component

            import {CustomPIXIComponent} from 'react-pixi-fiber';
            import Viewport from 'pixi-viewport';
             
            const TYPE = 'Viewport';
            const behavior = {
              customDisplayObject: props => {
                return new Viewport({
                  screenWidth: props.app.renderer.width,
                  screenHeight: props.app.renderer.height,
                  worldWidth: 1000,
                  worldHeight: 1000,
                })
              },
            };
            
            export default CustomPIXIComponent(behavior, TYPE);
        

Custom component

        <Viewport app={app}>
          {/* add display objects here */}
        </Viewport>
    

Больше вьюпортов

            <MountainsViewport app={app}>
              <Mountains />
            </MountainsViewport>
            <PlatformsViewport app={app}>
              {platforms.map(p => (<Platform {...p}/>))
            </PlatformsViewport>
            <ActorViewport app={app}>
              <Actor />
            </ActorViewport>
        

Это почти как iframe

React + Pixi Выводы

🏅 быстрое прототипирование
😍 developer experience
🍷 знакомый стек
Выводы

Ускоряет отрисовку?

Эффекты?

Ссылки

???