diff --git a/1st_encode/example.json b/1st_encode/example.json new file mode 100644 index 0000000..2d14928 --- /dev/null +++ b/1st_encode/example.json @@ -0,0 +1,271 @@ +{ + "height": 1080, + "width": 1080, + "elements": [ + { + "type": "path", + "data": { + "operations": [ + { + "type": "line", + "data": { + "target": { + "x": 15, + "y": 20 + } + } + }, + { + "type": "cubic", + "data": { + "target": { + "x": 18, + "y": 21 + }, + "control": { + "start": { + "x": 17, + "y": 22 + }, + "end": { + "x": 20, + "y": 23 + } + } + } + }, + { + "type": "cubic-smooth", + "data": { + "target": { + "x": 18, + "y": 21 + }, + "control": { + "x": 18, + "y": 21 + } + } + }, + { + "type": "quadratic", + "data": { + "target": { + "x": 18, + "y": 21 + }, + "control": { + "x": 18, + "y": 21 + } + } + }, + { + "type": "quadratic-smooth", + "data": { + "target": { + "x": 18, + "y": 21 + } + } + }, + { + "type": "arc", + "data": { + "center": { + "x": 18, + "y": 21 + }, + "x-axis": 40, + "y-axis": 50, + "angle": 6000 + } + }, + { + "type": "zeal" + } + ] + } + }, + { + "type": "polygon", + "data": { + "points": [ + { + "x": 10, + "y": 20 + }, + { + "x": 10, + "y": 20 + }, + { + "x": 10, + "y": 20 + }, + { + "x": 10, + "y": 20 + }, + { + "x": 10, + "y": 20 + } + ] + } + }, + { + "type": "round", + "data": { + "x-axis":30, + "y-axis":40, + "angle": 6000 + } + }, + { + "type": "group", + "group-layer": [1,2] + } + ], + "layers": [ + { + "element": 1, + "position": { + "type":"absolute", + "x": 18, + "y": 21 + }, + "level": 1, + "attached": [ + { + "type": "rotate", + "data": { + "clockwise": false, + "angle": 6000 + } + }, + { + "type": "zoom", + "data": { + "x-zoom": 1.0, + "y-zoom": 1.2 + } + }, + { + "type": "flip" + }, + { + "type": "shadow", + "data": {} + } + ] + }, + { + "element": 2, + "position": { + "type":"offset", + "x": 18, + "y": 21 + }, + "level": 1, + "attached": [] + } + ], + { + "element-group": false, + "target": 2, + "times": 5, + "operations": [ + { + "type": "move", + "data": { + "dx": 5, + "dy": 6 + } + }, + { + "type": "rotate", + "data": { + "center": { + "dx": 0, + "dy": 0 + }, + "dθ": 6000 + } + }, + { + "type": "zoom", + "data": { + "zoom": 1.5 + } + }, + { + "type": "flip" + } + ] + }, + { + "element-group": true, + "target": 5, + "times": 5, + "children": [ + [ + { + "type": "move", + "data": { + "dx": 5, + "dy": 6 + } + }, + { + "type": "rotate", + "data": { + "center": { + "dx": 0, + "dy": 0 + }, + "dθ": 6000 + } + }, + { + "type": "zoom", + "data": { + "zoom": 1.5 + } + }, + { + "type": "flip" + } + ], + [ + { + "type": "move", + "data": { + "dx": 5, + "dy": 6 + } + }, + { + "type": "rotate", + "data": { + "center": { + "dx": 0, + "dy": 0 + }, + "dθ": 6000 + } + }, + { + "type": "zoom", + "data": { + "zoom": 1.5 + } + }, + { + "type": "flip" + } + ] + ] + } + ] +} \ No newline at end of file diff --git a/2st_encode/2st_encode.assets/gbuffers.png b/2st_encode/2st_encode.assets/gbuffers.png new file mode 100644 index 0000000..85a953a Binary files /dev/null and b/2st_encode/2st_encode.assets/gbuffers.png differ diff --git a/2st_encode/2st_encode.assets/triangulation.png b/2st_encode/2st_encode.assets/triangulation.png new file mode 100644 index 0000000..441c458 Binary files /dev/null and b/2st_encode/2st_encode.assets/triangulation.png differ diff --git a/2st_encode/2st_encode.md b/2st_encode/2st_encode.md index 93b38b6..93eed2e 100644 --- a/2st_encode/2st_encode.md +++ b/2st_encode/2st_encode.md @@ -1,113 +1,156 @@ -二级编码大体结构及渲染流程如下: +目标:实现分辨率无关的、具有较高随机访问性能、相对节省显存的矢量图渲染,使得其使用方式与传统texture类似,便于在任意形状mesh中应用矢量图贴图。 -首先分为图元和索引两大块,图元内坐标范围为-1到1,一张图中可能有多个相同的图元,这些图元只需在图元缓存中保存一次,为了记录图元的位置和变换信息,对图元建立一个索引结构,同时为了方便求交,使用BVH层次包围盒结构,BVH每个结点包含leftChild,rightChild和bound即包围盒坐标,BVH的最后一个结点的leftChild为图元索引加上BVH数组长度,rightChild特殊处理为逆时针旋转角度。 +# 渲染流程 -图元由轮廓包围的封闭图形和线条构成,轮廓可以由直线、二阶贝塞尔曲线和三阶贝塞尔曲线组成,支持任意数量轮廓围成的图形的渲染,轮廓围成的图形中不能含有空腔,对于一个复杂的含有许多轮廓的封闭图形而言,应当尽可能将其分割成数个由三段轮廓线包围的广义三角形,并对这些广义三角形以及不构成图形的线条建立BVH索引,图元内BVH索引存储在外部BVH数组后。 +## 总体流程 -着色器接收的5个buffer: +1. 普通mesh将基础色、世界坐标、法线、金属度和粗糙度、彩绘编号(0)输出到gbuffer,彩绘mesh将世界坐标、法线、彩绘编号、彩绘纹理坐标输出到gbuffer; + +2. compute shader根据彩绘编号、彩绘纹理坐标gbuffer来计算彩绘纹理,更新基础色、金属度和粗糙度gbuffer; + +3. 根据基础色、世界坐标、法线、金属度和粗糙度gbuffer,以及光源计算光照。 + +![image-20220809141906110](2st_encode.assets/gbuffers.png) + +后续计划实现shadow mapping。 + +## 计算彩绘纹理流程 + +彩绘由许多不同的图元组成,一个图元可以由一系列贝塞尔曲线和贝塞尔曲线封闭的图形组成,同一图元可能在彩绘中反复出现,故计算彩绘纹理的过程就是根据矢量图纹理坐标,计算像素点对应哪个图元的哪个部位,得到颜色和材质,相当于在二维平面中做光线追踪。为了加速这一过程,将图元中的一个由数条曲线围成的封闭图形拆成多个由三条曲线围成的封闭图形,对每个图形和每段不构成封闭图形的贝塞尔曲线建立BVH层次包围盒加速结构,再对每个图元建立BVH。由此,计算每个像素点的过程即为遍历两个BVH找到像素点对应的图形或曲线,对于图形,判断点是否在图形内;对于线条,判断点到曲线的距离是否小于一定值,然后根据指定的样式计算其颜色和材质。 + +# 二级编码结构 + +首先分为图元和索引两大块,图元内坐标范围为-1到1,一张图中可能有多个相同的图元,这些图元只需在图元缓存中保存一次,为了记录图元的位置和变换信息,对图元建立一个索引结构,同时为了方便求交,使用BVH层次包围盒结构,BVH每个结点包含bvhChildren(每个结点的两个儿子,x分量为左儿子,y分量为右儿子)和bound(包围盒坐标,QVector4D(左下角x,左下角y,右上角x,右上角y)),BVH的最后一个结点的左儿子为图元索引加上BVH数组长度,右儿子特殊处理为图元zIndex和逆时针旋转角度。测试得到在重叠较少且不处理叶子结点内部的情况下,得到当叶子结点数量达到30000时,在我的笔记本(NVIDIA GeForce GTX 1650 Ti)上以1898x995分辨率渲染能维持32帧左右。 + +图元由轮廓包围的封闭图形和线条构成,封闭图形中允许有空洞,轮廓可以由直线、二阶贝塞尔曲线和三阶贝塞尔曲线组成。实验遍历一个图形的所有轮廓线判断是否有交点进而根据交点数判断点是否在图形内部的方法,得到当轮廓数量达到600时,在我的笔记本上以1898x995分辨率渲染只能维持13帧,考虑将图形分割并建立加速结构。 + +使用3种操作来绘制任意图元: + +1. 对一块矩形区域填充一次 +2. 对一块矩形区域擦除一次 +3. 一块矩形区域中包含一条曲线,该曲线在矩形区域中每一个x和y都一一对应,且曲线的端点落在矩形端点,在矩形区域中对曲线一侧的区域填充一次 + +对于每个像素维护一个int变量,填充时对其+1,擦除时对其-1,最终该变量的取值应为0或1,1即表示像素点在图形内部。 + +对于任意图形,均可使用一系列上述三种操作来绘制,生成这些操作的方法有待验证。 + + + +一开始我们考虑将图形做三角划分,再对划出的三角形建立BVH树,下面为按该方式制定的二级编码即缓存结构: + +![image-20220817102607509](2st_encode.assets/triangulation.png) + +compute shader接收的6个buffer: ```glsl -layout(std430, binding = 1) buffer bvhBuffer -{ - uint bvhLength; - uvec2 bvhChildren[]; -}; -layout(std430, binding = 2) buffer bvhBoundBuffer -{ - vec4 bvhBound[]; -}; -layout(std430, binding = 3) buffer elementOffsetBuffer +layout(std430, binding = 1) buffer paintingOffsetBuffer { /********************** - ** @x elementBvhRoot - ** @y elementBvhLength - ** @z pointsOffset - ** @w linesOffset + ** @x paintingBvhRoot + ** @y paintingBvhLength **********************/ - uvec4 elementOffsets[]; + uvec2 paintingOffsets[]; }; -layout(std430, binding = 4) buffer elementIndexBuffer + +layout(std430, binding = 2) buffer bvhBuffer { - uint elementIndex[]; //线和面 + uvec2 bvhChildren[]; }; -layout(std430, binding = 5) buffer elementDataBuffer +layout(std430, binding = 3) buffer bvhBoundBuffer { - float elementData[]; //点和Style + vec4 bvhBound[]; +}; +layout(std430, binding = 4) buffer elementOffsetBuffer +{ + /********************** + ** @[0] elementBvhRoot + ** @[1] elementBvhLength + ** @[2] pointsOffset + ** @[3] linesOffset + ** @[4] contoursOffset + **********************/ + uint elementOffset[][5]; +}; +layout(std430, binding = 5) buffer elementIndexBuffer +{ + uint elementIndexs[]; //线和面 +}; +layout(std430, binding = 6) buffer elementDataBuffer +{ + float elementData[]; //点和Style }; ``` -bvhLength为外部索引数组长度,bvhChildren为每个结点的两个儿子,x分量为左儿子,y分量为右儿子。 - -传入示例: +提供工具类合并多张彩绘的buffer,一张彩绘的参数示例: ```c++ - GLuint bvhChildren[] = {7/*rootBVH长度*/,0/*与显存对齐*/, + std::vector bvhChildren = { //root - 1,2, + 1,2, 3,4, 5,6, - 7,0, 7,30./360* 4294967296 /*右儿子用来表示旋转角度*/, 8,0, 7,0, + 7,0, 7,GLuint(30. / 360 * 65536 + 1 * 65536) /*右儿子用来表示旋转角度和zIndex*/, 8,0, 7,0, //elememt0 1,2, - 5+28/*contour索引,由于contour不定长,这里需要给到contour在elementIndex中位置*/,5+12/*style索引,在elementData中位置*/, 3,4, - 5+36,5+12, 5+32,5+12, + 5 + 0/*contour索引*/,5 + 12/*style索引,在elementData中位置*/, 3,4, + 5 + 2,5 + 12, 5 + 1,5 + 12, //elememt1 - 1+0/*line索引,element中第几条*/,1 + 25 + 1 + 0/*line索引,element中第几条*/,1 + 25 }; - QVector4D bvhBound[] = { + std::vector bvhBounds = { //root QVector4D(-1,-1,1,1), - QVector4D(-0.9,-0.9,-0.1,0.9), QVector4D(0.1, -0.9,0.9,0.9), + QVector4D(-0.9,-0.9,-0.1,0.9), QVector4D(0.1, -0.9,0.9,0.9), QVector4D(-0.8,-0.8,-0.2,-0.1), QVector4D(-0.7,0.2,-0.2,0.7), QVector4D(0.2,-0.8,0.8,-0.1), QVector4D(0.2,0.1,0.8,0.8), //elememt0 QVector4D(-1,-1,1,1), QVector4D(-1,-0.5,1,1), QVector4D(-1,-1,1,0.5), QVector4D(-1,-1,1,-0.5), QVector4D(-1,-0.5,1,0.5), - //elememt1 - QVector4D(-1,0,1,1), + //elememt1 + QVector4D(-1,0,1,1), }; - GLuint elementOffset[] = { + std::vector elementOffset = { //element0 7, //elementBvhRoot 5, //elementBvhLength 0, //pointsOffset 0, //linesOffset + 28, //contoursOffset //element1 12, //elementBvhRoot 1, //elementBvhLength 19, //pointsOffset 40, //linesOffset + 44 //contoursOffset }; - GLuint elementIndex[] = { + std::vector elementIndex = { //element0 //lines, 全部当作三阶贝塞尔, 每条线四个点索引 - 0,2,2,4, + 4,2,2,0, 0,0,1,1, 1,1,4,4, 1,1,5,5, 4,4,5,5, 1,1,3,3, 3,3,5,5, - //contours, 第一个元素指明轮廓段数,后面为lines索引 - 3, 0,1,2, - 3, 2,3,4, - 3, 3,5,6, - + //contours, 每个轮廓三个线索引,若轮廓由两条线构成则第一第二元素相同 + 0,1,2, + 2,3,4, + 3,5,6, //element2 //lines 0,1,2 }; - - GLfloat elementData[] = { + std::vector elementData = { //element0 //points -1,0.5, -1,-0.5, 0,1, 0,-1, 1,0.5, 1,-0.5, //fillStyle //fill - 0, + 0, //fillType 0, //单色 //fillColorMetallicRoughness @@ -130,3 +173,89 @@ bvhLength为外部索引数组长度,bvhChildren为每个结点的两个儿子 }; ``` +fillStyle格式: + +第一个元素:0,代表填充封闭图形 + +第二个元素:填充方式 + +​ 0,单色 + +​ 1,按直线渐变 + +​ 2,按点渐变 + +后续元素: + +对于单色:5个元素分别为R,G,B,Metallic,Roughness + +对于按直线渐变:起点坐标,终点坐标,段数,[{在直线中相对位置,颜色材质}, ...] + +对于按点渐变:点数,[{坐标,权重,颜色材质}, ...] + + + +strokeStyle格式: + +第一个元素:1,代表画线 + +第二个元素:线宽 + +第三个元素:0,圆角 + +​ 1,直角 + +​ 2,起点圆角终点直角 + +​ 3,起点直角终点圆角 + +第四个元素:线内填充方式 + +​ 0,单色 + +​ 1,按直线渐变 + +​ 2,按点渐变 + +​ 3,按当前线条渐变 + +第五元素:线外描边类型 + +​ 0,不描边 + +​ 1,双侧描边 + +​ 2,左侧描边 + +​ 3,右侧描边 + +第六个元素:线外描边宽度 + +第七个元素:线外描边方式 + +​ 0,单色 + +​ 1,分层设色 + +​ 2,渐变 + +后续元素: + +线内填充数据: + +对于单色:5个元素分别为R,G,B,Metallic,Roughness + +对于按直线渐变:起点坐标,终点坐标,段数,[{在直线中相对位置,权重,颜色材质}, ...] + +对于按点渐变:点数,[{坐标,权重,颜色材质}, ...] + +对于按当前线条渐变:段数,[{在线条中相对位置,权重,颜色材质}, ...] + +线外描边数据: + +对于单色:5个元素分别为R,G,B,Metallic,Roughness + +对于分层设色:层数,[{相对宽度,颜色材质}, ...] + +对于渐变:层数,[{相对位置(由里到外0~1),权重,颜色材质}, ...] + diff --git a/README.md b/README.md index afca8ff..f36999e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ +# 总体方案 + +一级编码(文件)为一张彩绘的最简表示,支持图层嵌套、迭代函数系统等操作; + +二级编码(缓存,数个SSBO)为实时渲染时所需的数据,只接收图元及其位置变换信息和加速结构 + +读取一级编码后,须将所有嵌套和迭代解开,得到一系列组成图案的最原始的部件即图元及其位置变换,并对图元和图元内部都建立加速结构,处理得到二级编码。 + # ArchitectureColoredPainting 编码方案 + ``` └─1st_encode 第一层编码 │ byte.md 二进制字节码编码方案