背景
场景中有大量的非连续线段,每条线段由大量的点构成(曲率较大),并且需要合并渲染,这时,一般考虑使用LineSegments画线,因为LineSegments底层是基于 gl.LINES 的WebGL标准进行绘制,v0 v1、 v1 v2、 v2 v3、 v3 v4.......
但是,这种方法会有一定代价。假设,一条曲线由5个点构成,除了首尾两个点,我们需要对中间的每个点额外拷贝一份 用于下个list段的起点,5个点要拷贝3个点,10个点要拷贝8个点,n个点要拷贝n-2个点,当点数较多时,这是一笔不小的额外开销
遵守WebGL性能优化第一原则:尽可能的减少点的数量,每个顶点都要执行顶点着色器,进行各种矩阵变换,及插值后到片元着色器的相应操作,点数太多会极大的影响性能
设想:能否不复制这些点,就能达到非连续线段的效果?
思路
使用Line类,即gl.LINE_STRIP模式绘制一条连续的线段,v0 v1、v2 v3、v4 v5......
每条线段结尾到下条线段开头 多出的折线 在片元着色器中 discard
Threejs实现
记录每条线的点数
每条线是一个独立的geometry,记录每条线的点数,得到 [line1_vertex_count, line2_vertex_count...],添加到合并后的Geometry
const stripIndexs = geometrys.map(item => item.attributes.position.count)
mergeGeometry.stripIndexs = stripIndexs
封装原始裁剪索引数据
记录每条线的最后一个点的索引及其索引+1,也就是这个每个折线处的两个点的索引所在合并后的mergeGeometry的顶点中的位置,比如,有三条线,每条线仅有首尾两个点(举例说明,实际n个点),则需要记录 1 2 3 4 这四个索引,如下
let cumulativeIndex = -1;
let originCropIndexes: Array<number> = [];
for (let i = 0; i < geometry.stripIndexs.length - 1; i++) {
cumulativeIndex += geometry.stripIndexs[i];
originCropIndexes.push(cumulativeIndex);
originCropIndexes.push(cumulativeIndex + 1);
}
封装合并几何体的缓冲数据:由裁剪索引组成的 IntArray
将上一步得到的原始裁剪索引数组,每个裁剪索引按序映射到 缓冲数组 initCroppingIndexes 中,如下,遍历合并线段的所有顶点,当前索引与裁剪索引相同则按序映射,没有则默认-1,得到 [-1, 1, 2, 3, 4, -1](依然拿上述举例)
let vertexCount = geometry.attributes.position.count;
let initCroppingIndexes = new Int16Array(vertexCount).fill(-1);
if (originCropIndexes.length) {
for (let i = 0; i < vertexCount; i++) {
for (let j = 0; j < originCropIndexes.length; j++) {
if (i == originCropIndexes[j]) {
initCroppingIndexes[i] = originCropIndexes[j];
break;
}
}
}
}
geometry.setAttribute('initCroppingIndex', new BufferAttribute(initCroppingIndexes, 1));
守住该有的线段!
数据处理并没有结束!如果现在直接到着色器中按裁剪索引去插值直接做discard,会把 1 2 3 4 顶点中的 2 3所组成的线段也discard掉,当然,这不是我们想要的,需要进一步封装相关数据用于后续着色器使用
如下图,需要再次记录 索引 2 3 4 5 6 7 ,并且连续的两对点都有不同的标识,这个至关重要,因为这些索引是要保留的所组成的线段,如果这些要保留的索引又是连续,又是相同的标识,是不是 2 ~ 7顶点间的线段又会都保留?2 3 、4 5、6 7组成的线段你是保留了,3 4、5 6线段是不是又没有剔除?陷入了无止境循环的局面....
拿上图举例,最终 continuousCroppingIndexes 所成型的数据是 [-1, -1, 0, 0, 1, 1, 0, 0, -1, -1。]如下代码
let stripIdentCount = 0;
let continuousCroppingIndexes = new Int16Array(vertexCount).fill(-1);
if (originCropIndexes.length) {
for (let i = 1; i < vertexCount - 1; i++) {
if (initCroppingIndexes[i] != -1 && initCroppingIndexes[i - 1] + 1 == initCroppingIndexes[i + 1] - 1) {
continuousCroppingIndexes[i] = stripIdentCount % 4 < 2 ? 0 : 1;
stripIdentCount++;
}
}
}
geometry.setAttribute('continuousCroppingIndex', new BufferAttribute(continuousCroppingIndexes, 1));
修改顶点着色器
这步很简单,将init裁剪索引缓冲数据和要保留的裁剪索引数据分别给赋顶点插值颜色,用于后续片元根据插值颜色做判断。
注意,这里continuousCroppingIndex缓冲数据是双重标识,0 0 1 1 0 0...
material.onBeforeCompile = (shader) => {
shader.vertexShader = shader.vertexShader.replace(
'void main() {',
[
'attribute float initCroppingIndex;',
'attribute float continuousCroppingIndex;',
'varying vec4 vColor;',
'varying vec4 vStripCrop;',
'void handleVaryingColor() {',
'int initIndex = int(initCroppingIndex);',
'if (gl_VertexID == initIndex) {',
'vColor = vec4(vec3(1.), 0.);',
'}',
'vStripCrop = vec4(vec2(1.), continuousCroppingIndex, 0.);',
'}',
'void main() {',
'handleVaryingColor();'
].join('\n')
);
};
修改片元着色器
可能举例更形象些:如下,1 2、3 4、5 6、7 8都会被裁剪,而并不会裁剪 2 3 、4 5 、6 7,因为该有的索引都做了成对的颜色标识,并且会区分奇偶对顶点的颜色!
如下,按需裁剪
material.onBeforeCompile = (shader) => {
shader.fragmentShader = shader.fragmentShader.replace(
'void main() {',
['varying vec4 vColor;', 'varying vec4 vStripCrop;', 'void main() {'].join('\n')
);
shader.fragmentShader = shader.fragmentShader.replace(
'vec4 diffuseColor = vec4( diffuse, opacity );',
[
'vec4 diffuseColor = vec4( diffuse, opacity );',
'vec4 vUnivCropColor = vec4(vec3(1.), 0.);',
'vec4 vEvenCropColor = vec4(vec2(1.), vec2(0.));',
'if(vColor == vUnivCropColor && vStripCrop != vUnivCropColor && vStripCrop != vEvenCropColor) {',
'discard;',
'}',
].join('\n')
);
};
完整代码
geometry和material分别是合并的几何体及其材质
const handleLineGeometryShader = (geometry, material) => {
let stripIdentCount = 0;
let cumulativeIndex = -1;
let vertexCount = geometry.attributes.position.count;
let originCropIndexes: Array<number> = [];
let initCroppingIndexes = new Int16Array(vertexCount).fill(-1);
let continuousCroppingIndexes = new Int16Array(vertexCount).fill(-1);
for (let i = 0; i < geometry.stripIndexs.length - 1; i++) {
cumulativeIndex += geometry.stripIndexs[i];
originCropIndexes.push(cumulativeIndex);
originCropIndexes.push(cumulativeIndex + 1);
}
if (originCropIndexes.length) {
for (let i = 0; i < vertexCount; i++) {
for (let j = 0; j < originCropIndexes.length; j++) {
if (i == originCropIndexes[j]) {
initCroppingIndexes[i] = originCropIndexes[j];
break;
}
}
}
for (let i = 1; i < vertexCount - 1; i++) {
if (initCroppingIndexes[i] != -1 && initCroppingIndexes[i - 1] + 1 == initCroppingIndexes[i + 1] - 1) {
continuousCroppingIndexes[i] = stripIdentCount % 4 < 2 ? 0 : 1;
stripIdentCount++;
}
}
}
// console.log(originCropIndexes, initCroppingIndexes, continuousCroppingIndexes);
geometry.setAttribute('initCroppingIndex', new BufferAttribute(initCroppingIndexes, 1));
geometry.setAttribute('continuousCroppingIndex', new BufferAttribute(continuousCroppingIndexes, 1));
beforeCompileLineMaterial(material);
};
const beforeCompileLineMaterial = (material) => {
material.onBeforeCompile = (shader) => {
shader.vertexShader = shader.vertexShader.replace(
'void main() {',
[
'attribute float initCroppingIndex;',
'attribute float continuousCroppingIndex;',
'varying vec4 vColor;',
'varying vec4 vStripCrop;',
'void handleVaryingColor() {',
'int initIndex = int(initCroppingIndex);',
'if (gl_VertexID == initIndex) {',
'vColor = vec4(vec3(1.), 0.);',
'}',
'vStripCrop = vec4(vec2(1.), continuousCroppingIndex, 0.);',
'}',
'void main() {',
'handleVaryingColor();'
].join('\n')
);
shader.fragmentShader = shader.fragmentShader.replace(
'void main() {',
['varying vec4 vColor;', 'varying vec4 vStripCrop;', 'void main() {'].join('\n')
);
shader.fragmentShader = shader.fragmentShader.replace(
'vec4 diffuseColor = vec4( diffuse, opacity );',
[
'vec4 diffuseColor = vec4( diffuse, opacity );',
'vec4 vUnivCropColor = vec4(vec3(1.), 0.);',
'vec4 vEvenCropColor = vec4(vec2(1.), vec2(0.));',
'if(vColor == vUnivCropColor && vStripCrop != vUnivCropColor && vStripCrop != vEvenCropColor) {',
'discard;',
'}',
].join('\n')
);
};
}
WebGL实现类似功能(简易版,便于测验)
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute float indexes;\n' +
'attribute float oneIndex;\n' +
'attribute float twoIndex;\n' +
'varying vec4 vColor;\n' +
'varying vec4 vStripCrop;\n' +
'void main() {\n' +
'int index = int(indexes);\n' +
'int oIndex = int(oneIndex);\n' +
// 'int tIndex = int(twoIndex);\n' +
'if (index == oIndex) {\n' +
'vColor = vec4(vec3(1.), 0.);\n' +
'}\n' +
'vStripCrop = vec4(vec2(1.), twoIndex, 0.);\n' +
'gl_Position = a_Position;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'precision mediump float;\n' +
'varying vec4 vColor;\n' +
'varying vec4 vStripCrop;\n' +
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'if(vColor == vec4(vec3(1.), 0.) && vStripCrop != vec4(vec3(1.), 0.) && vStripCrop != vec4(vec2(1.), 0., 0.)) {\n' +
'discard;\n' +
'}\n' +
'}\n';
function main() {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
var n = initVertexBuffers(gl);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.LINE_STRIP, 0, n);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
-0.6, -0.8, 0.6, -0.8, -0.6, -0.5, 0.6, -0.5, -0.6, -0.2, 0.6, -0.2, -0.6, 0.1, 0.6, 0.1, -0.6, 0.4, 0.6, 0.4
]);
var indexes = new Float32Array([
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
]);
var arr1 = new Float32Array([
-1, 1, 2, 3, 4, 5, 6, 7, 8, -1
]);
var arr2 = new Float32Array([
-1, -1, 0, 0, 1, 1, 0, 0, -1, -1
]);
var n = 10;
// Create a buffer object
var vertexBuffer = gl.createBuffer();
var indexBuffer = gl.createBuffer();
var oneBuffer = gl.createBuffer();
var twoBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
gl.bindBuffer(gl.ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, indexes, gl.STATIC_DRAW);
var indexes = gl.getAttribLocation(gl.program, 'indexes');
gl.vertexAttribPointer(indexes, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(indexes);
gl.bindBuffer(gl.ARRAY_BUFFER, oneBuffer);
gl.bufferData(gl.ARRAY_BUFFER, arr1, gl.STATIC_DRAW);
var oneIndex = gl.getAttribLocation(gl.program, 'oneIndex');
gl.vertexAttribPointer(oneIndex, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(oneIndex);
gl.bindBuffer(gl.ARRAY_BUFFER, twoBuffer);
gl.bufferData(gl.ARRAY_BUFFER, arr2, gl.STATIC_DRAW);
var twoIndex = gl.getAttribLocation(gl.program, 'twoIndex');
gl.vertexAttribPointer(twoIndex, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(twoIndex);
return n;
}
注意
- 自定义的顶点缓冲数据如果是int类型的,及时你js里面是int类型,在传入shader里面的时候,vertexpoint辅助函数有个参数也会给转成float类型!则需要float声明接收,后续使用int数据再次int转换即可
- uniform变量不能直接声明为数组类型。这是因为uniform变量是在整个渲染过程中保持不变的,而数组类型通常需要在编译时知道其大小
版权归原作者 山楂树の 所有, 如有侵权,请联系我们删除。