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

程序架构

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容