美文网首页程序员
shadertoy 移植到本地(1):具体实现

shadertoy 移植到本地(1):具体实现

作者: ansey | 来源:发表于2022-06-30 01:42 被阅读0次

    程序架构

    html + js
    使用typescript 工程构建 js

    步骤一

    写一个空的 html 程序

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>shadertoy player</title>
    </head>
    
    <body style="height:100%;margin:0;padding:0; overflow: hidden; background-color: black;">
        <canvas id="webglCanvas" tabindex="0" style="position:absolute;height:100%;width:100%;">
            plass use a browser that supports "canvas"
        </canvas>
    </body>
    <script>
    conosole.log("hello world!");
    //js 执行入口
    </script>
    </html>
    

    步骤二

    用canvas通过webglAPI 绘制全屏像素

    • 获取 webgl渲染上下文对象
    //shadertoy 的shader 使用的gl es 300 的语法,需要使用webgl2.
    let webgl2 = canvas.getContext("webgl2");
    
    • 着色器程序
    //顶点着色器 字符串
    let baseVS = `#version 300 es
            in vec2 a_Position; //顶点 二维坐标 
            void main() {
                gl_Position = vec4(a_Position.xy, 0.0 , 1.0);
            }`;
    //片元着色器 字符串
    let baseFS = `#version 300 es
            out vec4 color;
            void main(){
                color =  vec4(1.0 , 0.0 , 0.0 , 1.0);  //所有坐标像素输出红色
            }`;
    //用着色器字符串,创建 gl的着色器对象
    
    //创建 顶点、片元 着色器对象
    let vs = gl.createShader(gl.VERTEX_SHADER);
    let fs = gl.createShader(gl.FRAGMENT_SHADER);
    //上传着色器的代码文本
    gl.shaderSource(vs, baseVS );
    gl.shaderSource(fs, baseFS );
    //编译着色器
    gl.compileShader(vs);
    gl.compileShader(fs);
    //创建 gl程序
    let program = gl.createProgram();
    //将 着色器 绑定到 gl程序 ,并链接, 着色器到GPU准备工作的最后一步
    gl.attachShader(program, vs);
    gl.attachShader(program, fs);
    gl.linkProgram(program);
    //指定当前 使用的gl程序
    gl.useProgram(program);
    
    • 一个三角形顶点数据
    //准备顶点数据
    //什么只有一个三角形? 我们只需像素渲染覆盖全屏(一个大三角形足以),只需要使用 gl_FragCoord + iResolution 来算定位像素UV。
    //        0
    //      /   \
    //     /     \
    //   2 ------- 1
    //
    let posArr = [0, 3, 2, -1, -2, -1];  //三个顶点坐标,两个为一组二维顶点。
    //创建 缓冲区对象
    let glPosBuffer= gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, glPosBuffer);  //指定当前被操作的 缓冲区对象
    //给当前缓冲区对象(GPU显存),上传顶点位置数据
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(posArr), gl.STATIC_DRAW);            
    
    //为着色器 Attrib 字段分配 ,指定上面的顶点位置缓冲区作为输入数据
    //先获取 Attrib 的 名为 "a_Position" 字段的地址
    let aPositionAddr = gl.getAttribLocation(program, "a_Position");
    gl.bindBuffer(gl.ARRAY_BUFFER, glPosBuffer);//指定当前被操作的 缓冲区对象
    //告诉GPU,"a_Position" 字段,如何从缓冲区中读取数据
    gl.vertexAttribPointer(aPositionAddr, 2, gl.FLOAT, false, 0, 0);  
    //激活启用 设置Attrib 字段的设置。
    gl.enableVertexAttribArray(aPositionAddr);
    
    
    • 绘制渲染
    //请求GPU 安当前状态进行绘制
    gl.drawArrays(gl.TRIANGLES, 0, 3);  //三角形类 ,从 buffer0 索引位置开始,绘3个长度的顶点数据.
    

    得到一个全屏单红色绘制画面

    全屏单色渲染.png

    步骤三

    ok,基础的准备工作完成了,接下来就可以,将shadertoy 的着色片段插入到我们的 片元着色中,然后渲染就可以得到,理论上与shadertoy一致的效果。

    • 修改 片元着色器 代码
    //片元着色器 字符串
    let baseFS = `#version 300 es
            out vec4 color;
            //下面是 uniform 字段定义部分 (仅教程,这里只实现两个基础 字段)
            uniform vec3      iResolution;
            uniform float     iTime;
    
            //下面一行作为插入位置,它是一段特定的注释,作为识别并替换成 shadertoy片段 代码用。
            //=#*INSERT_LOCATION*#=
    
            void main(){
                vec4 col = vec4(0.0 , 0.0 , 0.0 , 1.0);
                mainImage(col , gl_FragCoord.xy);     //改函数是 shadertoy 固定接口,它会输出一个颜色。
                color = col ;
            }`;
    
    • 插入到片元着色器代码中
    //shadertoy 的代码
    let sToyTest= `
    void mainImage( out vec4 fragColor, in vec2 fragCoord )
        {
            // Normalized pixel coordinates (from 0 to 1)
            vec2 uv = fragCoord/iResolution.xy;
        
            // Time varying pixel color
            vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
        
            // Output to screen
            fragColor = vec4(col,1.0);
        }
    `;
    //片元着色器代码中 插入 shadertoy 代码
    baseFS = baseFS.replace(`//=#*INSERT_LOCATION*#=`, sToyTest);
    
    
    • unifrom字段的输入
    let totalTimeSec = 0;
    
    //shaderToy 内置uniform 上传
    //获取 uniform  字段名为  "iResolution  " 的地址
    let iResolutionAddr = gl.getUniformLocation(program, "iResolution");
    //给iResolutionAddr 字段设置数据 ,是canvas的像素宽高
    gl.uniform3f(iResolutionAddr, gl.canvas.width, gl.canvas.height, 1);
    //获取 uniform  字段名为  "iTime" 的地址 
    let iTimeAddr = gl.getUniformLocation(program, "iTime");
    //给iTimeAddr字段设置数据,是开始运行到当前的计时
    gl.uniform1f(iTimeAddr, totalTimeSec);
    
    

    得到一个全屏颜色交替的画面

    shadertoy测试渲染画面.png

    步骤四

    上面只是进行了一次绘制,想要绘制动画效果,就需要在绘制完一帧后连续绘下一帧,并一致持续下去,所有我们需要一个循环。

    //循环渲染
    let time = Date.now();
    let totalTimeSec = 0;
    let loop = () => {
        let nowTime = Date.now();
        let dt = (nowTime - time) * 0.001;
        totalTimeSec += dt;
        time = nowTime;
        //uniform 更新
        //shaderToy 内置uniform 上传
        //获取 uniform  字段名为  "iResolution  " 的地址
        let iResolutionAddr = gl.getUniformLocation(program, "iResolution");
        //给iResolutionAddr 字段设置数据 ,是canvas的像素宽高
        gl.uniform3f(iResolutionAddr, gl.canvas.width, gl.canvas.height, 1);
        //获取 uniform  字段名为  "iTime" 的地址 
        let iTimeAddr = gl.getUniformLocation(program, "iTime");
        //给iTimeAddr字段设置数据,是开始运行到当前的计时
        gl.uniform1f(iTimeAddr, totalTimeSec);
    
        //请求GPU 安当前状态进行绘制
        gl.drawArrays(gl.TRIANGLES, 0, 3);  //三角形类 ,从 buffer0 索引位置开始,绘3个长度的顶点数据.
        //接下一次循环刷新,让loop 函数反复执行
        requestAnimationFrame(loop);    //注意:这里遇到一个坑,如果用setTimeout 作为循环泵,会有严重的卡顿情况
    };
    
    //第一次触发执行loop,启动循环
    loop();
    
    

    得到一个全屏颜色交替变化的画面

    shadertoy测试GIF.gif

    补充

    shadertoy网站链接

    已实现shadertoyNativePlayer播放器在 github上,可用于借鉴.
    几个样例:

    cap01.png
    cap02.png
    cap03.png
    cap04.png
    cap05.png

    相关文章

      网友评论

        本文标题:shadertoy 移植到本地(1):具体实现

        本文链接:https://www.haomeiwen.com/subject/wlamvrtx.html