shadertoy 移植到本地(1):具体实现
2022-06-30 本文已影响0人
ansey
程序架构
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个长度的顶点数据.
得到一个全屏单红色绘制画面
步骤三
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();
得到一个全屏颜色交替变化的画面
补充
已实现shadertoyNativePlayer播放器在 github上,可用于借鉴.
几个样例:
cap02.png
cap03.png
cap04.png
cap05.png