# draw **Repository Path**: withoutRock/draw ## Basic Information - **Project Name**: draw - **Description**: 这是一个画图的仓库。 绘图的手段有 css canvas webgl svg 等 - **Primary Language**: JavaScript - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2021-08-23 - **Last Updated**: 2025-12-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # draw ## 介绍 这是一个画图的仓库 画图的手段有svg canvas webgl 部分页面演示地址 https://withoutrock.gitee.io/draw/ ## 内容目录 ### 起步 ```Mermaid graph TD A(获取上下文对象getContext webgl\n 设置canvas尺寸 这个尺寸就是canvas坐标系的尺寸) --> B[创建程序对象 gl.createProgram] B --> C{创建着色器对象 gl.createShader} C --gl.VERTEX_SHADER顶点着色器--> D[装载着色器代码gl.shaderSource vertShader, vs] C --gl.FRAGMENT_SHADER片元着色器--> E[装载着色器代码 gl.shaderSource vertShader, fs] D --> F(编译着色器 gl.compileShader shader) E --> F F --> G(把着色器对象装进程序gl.attachShader shader) G --> H(链接上下文和程序对象 gl.link program ) H --> I(激活启动程序gl.useProgram program) ``` #### js 向着色器写入数据。 ##### 获取着色器变量 `gl.getAttribLocation(name) ` `gl.getUniformLocation(name)` 这两个方法分别对应我们在着色器里 用 attribute 和 uniform 修饰的变量。 返回值是一个数值索引。 通过这个索引我们才能去修改对应的变量 attribute 是每个顶点的私有数据,就像我们对象实例的属性一样 ownkeys uniform 则是全部顶点的公共数据, 就像我们对象的原型链上的属性一样。 ##### 修改着色器变量 修改变量时就要考虑变量的数据类型了 例如 `gl.uniform4f(location, v0, v1, v2, v3)` 这里的uniform不用说,对应修饰符 uniform 1代表分量是1,如果是2就代表分量是2,二维向量可以这么理解。 f 代表float 浮点小数, 这个值也可以是i int 整型,也就是 uniform1i() 参数一location就是前面获取的索引。 如果这里写 `gl.uniform1fv(location, value)`, 后面多了个v ,表示全部数据将用一个数组表示,区别就如同 call 和apply的区别。 ###### 向量 矩阵 一维向量就按普通数值理解 uniform1f vertexAttrib3f(a_Position, .5, .5, 0) 。 这种方式只能写入一个顶点的数据,无法写入多顶点的数据 uniformMatrix4fv() 一般来说矩阵都是公用数据。 这里的4fv表示是一个4维的浮点数方阵,用一个length为16的浮点数组表示 ###### 多点数据 需要写入多顶点数据,就要用到缓冲区。 使用缓冲区也是三步 1 先建立缓冲区、绑定缓冲区, buffer = gl.createBuffer() ;gl.bindbuffer() 2 再初始化缓冲区,写入数据。 gl.bufferData(target: number, data: any, usage: number) 3 告诉着色器应该怎么从缓冲区中读取数据。gl.vertexAttribPointer(a_Position, size, type, normalize:boolean,stride,offset) offset就是代表这个数据起始列,stride 就是两个顶点数据之间的间隔,比如还有一个颜色数据的分量是2 ,那么这里就是2,如果除了颜色还有一个亮度数据分量为1,那就是3 。 可以认为前两步是建立了一个数据库,第三步才是把数据库里的指定数据传入着色器。 建立数据库的数据源必须是定型数组 例如Float32Array([]) 目的是为了提升传输效率, 定型数组除了不能修改之外,拥有和普通js数组一样的属性和方法。 ### 纹理 除了直接给片元上色之外,还可以用sampler2D取样器 从一个图像源获取色值按一定的坐标关系映射到片元。 简单一点的理解就和css的背景图差不多。通过图钉来设置要获取图像源的区域,也叫uv坐标映射如同css的background-position. 图钉的单位分别是图像源的一个宽度和一个高度 ### 光 #### 着色频率 常见的着色频率有三种: 逐三角形着色flat shading:模型的每个面都有统一的法线 逐顶点着色 gouraud shaing: 模型的每个顶点都拥有各自的法线 每三个点构成的三角形内部的点的法线会通过三角形的插值算法计算出来。 逐片元着色 模型的每个片元都拥有各自的法线,由phong 提出。把顶点着色的法线归一化即为片元着色 主要就是参照的法线不同,导致最后的反射光计算结果不同。 #### 结论 当顶点数量很少时: 逐三角形着色效果生硬,渲染速度最快。 逐顶点着色效果较为平滑,会失去高光,渲染速度适中。 逐片元着色效果最为平滑,渲染速度最慢。 当顶点数量适中时,若对效果要求不苛刻,适合用顶点着色。因为其具有平滑的效果和高光,渲染速度也适中。 当顶点达到一定的数量,三种着色频率的着色效果都是一样的,且逐片元着色频率的渲染速度不一定会比其他的两种着色频率慢。 #### 光源 光源一般分 点光源和 平行光。除此之外还有一些人造光源模型 ##### 筒灯 是平行光,但在一定范围内会衰减至无 是对平行光的限制,在一定范围内会衰减至无 ##### 锥形灯 对点光源的限制, 会衰减 ### 帧缓冲区 webl 绘图是顶点着色器定形, 片元着色器填充片元,然后绘制到画布上。 在绘制到画布上之前, 这些数据就存储在帧缓冲区(内存)里。建立一个帧缓冲区,可以把这些绘图数据拦截存储下来,当做纹理处理。 ### 投影贴图 这个图是怎么绘制出来的。 这个图是把视点与光源重合,视线与光线重合得到的一幅图, 我们要存储的是,这幅图在相机世界里的每个点位的 z值 。 不明白的一点就是这个贴图在真正相机的深度是怎么得到的。 应该就是看做,相机从光源处往其他位置移动,光源没变,但是相机世界变了,之前的贴图的世界坐标位也就变了,这样就拿到了贴图的最新相机坐标位。 不对,应该是把当前视线看到的点,转为光源相机世界的点位,然后进行比较 判断一个物体是否在阴影内,就是比较其深度z 和投影贴图的深度z 如果它的深度大于贴图的那么就在阴影内 ,不在的正常绘制,在的需要调低亮度。 ### 纹理映射 普通的纹理映射是线性的 #### 等距圆柱投影贴图 这里的等距是把 球体上的经纬线展开后的平面贴图 经纬线的间距相等 圆柱投影, 用圆柱围住一个球体,侧面与球体相切, 把点光源放在球心,点光源会把球体投射到圆柱上,从而得到球体的圆柱投影。 本初子午线 就是0度 #### 球坐标, 方位角θ(从z轴正半轴开始 ,[0,2π]) 和 极角φ(从y轴正半轴开始,[0,π]) 。 和经纬度很像,但是。看看值域就知道,还是不一样的。 有了球坐标,我们就可以想象,这样列出一个球的坐标。 首先一个半径,初始从x轴开始绕 y轴旋转 。 当她转了一周之后,就会绕z轴逆时针转动一小格比如 20, 然后继续绕 y轴旋转,如此反复。 就是先横向扫描一圈。完了之后再纵向走一点,继续横向转圈扫描。 直到把球体扫完。 #### 地理坐标 也就是经纬度 经度0, 纬度φ 。 经度,从x轴开始 逆时针为正,顺指针为负,范围 [-π, π] 东经 为正 西经为负 纬度,从x 或 z轴开始 逆时针为正,顺指针为负,范围 [-π/2, π/2], 北纬为正,南纬为负。 对于点(r,0,φ ) ,转为三维直角坐标(r*cosφ * cos0,r*sinφ,-r*cosφ*sin0); ### VR 不曾想vr竟是把贴图 贴到球体上,不过贴的是等圆柱投影图,并不是一般的照片。 #### 搭建场景 将全景贴图 贴到立方体或者球体上 #### 变换场景 把相机视点放进 立方体或者球体内部,然后通过相机轨道变换 。 但是,我们一般使用的是正交相机,之前正交相机的缩放是利用近大远小的方式,这种缩放,其放大效果十分有限,需要改为zoom缩放。 作为vr ,要避免相机视野跑出几何体之外,避免相机视点移动,因此,需要能禁用平移,控制缩放范围。 #### 遇到问题 three.js里的向量对象是如何支持扩展运算符的,并没有看见它实现迭代器 在浏览器里看见是有的,确实更新了。 x y 方向上的单位 都对应画布的宽高 ,但是z方向的意义是 远近,超出这个范围就不可见。 其单位不好度量, 把z轴旋转到 y轴,那它的单位就是y的单位, 旋转到x就是x的单位, 画布事实上就是二维的, 我们看到的就是三维在二维上的投影,所以z方向的单位确实不可度量 如果想画出正立方体,需要保证视口是个正方形 顶点只关心位置和大小,不关心方向,不管你如何旋转变换最终在屏幕上也是一个正方形 四边平行于视口范围。 ```javascript *[ Symbol.iterator ]() { yield this.x; yield this.y; yield this.z; } ``` ## 未解决的问题 ```glsl float grid2 (vec2 uv , float w, float repeat) { uv=1. - 2.* abs( fract(uv * repeat) -.5 ) ; float dx = dFdx(uv.x ) ; float dy = dFdy(uv.y + uv.x) ; // w = w* repeat ; return uv.x< dx*w || uv.y < dy*w ? 1.:0. ; // return smoothstep(dx , dx/repeat,uv.x );// 用smoothstep这里有点奇怪,一直都是半个周期 } ``` # TODO ## 已完成 轨道控制器 极角无限制版 (圆角)折线管道 uv连续且均匀的宽度线 车削升级版(Blender) 曲线修改器 建模版 (有少许限制) 简易形变 扭曲形变 曲线转网格 基础 曲线转网格 曲线半径 曲线转网格 曲线倾斜 ## 待办 天空盒实现 立方体纹理 等距圆柱投影贴图 树形生成器 模型预览加个viewHelper GridHelper 表面波纹扩散 松开鼠标后回到原位的轨迹球,也就是记录鼠标按下一次的的增量,而不是累加。 可参考arcball wieframeRender 另一种线框 渲染方法,可惜依赖顶点顺序 更有厚度感的体积云 raymarch 更灵活的相机控制 ## 已废弃 ## 进行中 欧拉流体模拟 # 优化 模型查看器 操作提示,效果是hover,但是实际实现使用纹理文字。