Vertex buffers/Index buffer

目录

Introduction

Index buffer creation

Using an index buffer


Introduction

在真实世界应用程序中渲染的3D网格通常会在多个三角形之间共享顶点。即使是像绘制矩形这样简单的事情,这种情况也已经发生了:

绘制矩形需要两个三角形,这意味着我们需要一个包含6个顶点的顶点缓冲区。问题是需要复制两个顶点的数据,导致50%的冗余。这只会在更复杂的网格中变得更糟,其中顶点在平均3个三角形中重复使用。解决这个问题的方法是使用索引缓冲区。

索引缓冲区本质上是指向顶点缓冲区的指针数组。它允许您重新排序顶点数据,并为多个顶点重用现有数据。上图演示了如果我们有一个包含四个唯一顶点的顶点缓冲区,那么矩形的索引缓冲区会是什么样子。前三个索引定义右上三角形,后三个索引为左下三角形定义顶点。

Index buffer creation

在本章中,我们将修改顶点数据并添加索引数据,以绘制一个如图所示的矩形。修改顶点数据以表示四个角:

const std::vector<Vertex> vertices = {
    {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
    {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
    {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
    {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
};

左上角为红色,右上角为绿色,右下角为蓝色,左下角为白色。我们将添加一个新的数组索引来表示索引缓冲区的内容。它应该与插图中的索引相匹配,以绘制右上三角和左下三角。

const std::vector<uint16_t> indices = {
    0, 1, 2, 2, 3, 0
};

根据顶点中的条目数量,可以为索引缓冲区使用uint16_t或uint32_t。我们现在可以坚持uint16_t,因为我们使用的唯一顶点少于65535个。

就像顶点数据一样,索引需要上传到VkBuffer中,GPU才能访问它们。定义两个新的类成员以保存索引缓冲区的资源:

VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;

我们现在要添加的createIndexBuffer函数几乎与createVertexBuffer相同:

void initVulkan() {
    ...
    createVertexBuffer();
    createIndexBuffer();
    ...
}

void createIndexBuffer() {
    VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);

    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
    memcpy(data, indices.data(), (size_t) bufferSize);
    vkUnmapMemory(device, stagingBufferMemory);

    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);

    copyBuffer(stagingBuffer, indexBuffer, bufferSize);

    vkDestroyBuffer(device, stagingBuffer, nullptr);
    vkFreeMemory(device, stagingBufferMemory, nullptr);
}

只有两个显著的差异。bufferSize现在等于索引数乘以索引类型的大小,uint16_t或uint32_t。indexBuffer的用法应该是VK_BUFFER_USAGE_INDEX_BUFFER_BIT,而不是VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,这是有意义的。除此之外,过程完全相同。我们创建一个暂存缓冲区,将内容复制到索引,然后将其复制到最终的设备本地索引缓冲区。

索引缓冲区应该在程序结束时清理,就像顶点缓冲区一样:

void cleanup() {
    cleanupSwapChain();

    vkDestroyBuffer(device, indexBuffer, nullptr);
    vkFreeMemory(device, indexBufferMemory, nullptr);

    vkDestroyBuffer(device, vertexBuffer, nullptr);
    vkFreeMemory(device, vertexBufferMemory, nullptr);

    ...
}

Using an index buffer

使用索引缓冲区进行绘图涉及对recordCommandBuffer的两个更改。我们首先需要绑定索引缓冲区,就像我们对顶点缓冲区所做的那样。不同的是,只能有一个索引缓冲区。不幸的是,不可能为每个顶点属性使用不同的索引,因此即使只有一个属性发生变化,我们仍然必须完全复制顶点数据。

vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);

vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16);

索引缓冲区与vkCmdBindIndexBuffer绑定,其中包含索引缓冲区、其中的字节偏移量和索引数据类型作为参数。如前所述,可能的类型是VK_INDEX_TYPE_UINT16和VK_INDX_TYPE_UINT32。

仅仅绑定索引缓冲区还没有改变任何东西,我们还需要改变绘图命令,告诉Vulkan使用索引缓冲区。删除vkCmdDraw行并将其替换为vkCmdDrawIndexed:

vkCmdDrawIndexed(commandBuffer, static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);

对该函数的调用与vkCmdDraw非常相似。前两个参数指定索引的数量和实例的数量。我们没有使用实例,所以只需指定一个实例。索引数表示将传递给顶点着色器的顶点数。下一个参数指定索引缓冲区中的偏移量,使用值1将导致显卡开始读取第二个索引。倒数第二个参数指定要添加到索引缓冲区中的索引的偏移量。最后一个参数指定实例化的偏移量,我们不使用该偏移量。

现在运行程序,您应该看到以下内容:

现在您知道如何通过使用索引缓冲区重用顶点来节省内存。在我们将要加载复杂3D模型的未来章节中,这将变得尤为重要。

上一章已经提到,您应该从单个内存分配中分配多个资源,如缓冲区,但实际上您应该更进一步。驱动程序开发人员建议您也将多个缓冲区(如顶点和索引缓冲区)存储到单个VkBuffer中,并在vkCmdBindVertexBuffers等命令中使用偏移量。优点是在这种情况下,您的数据更易于缓存,因为它们更靠近。当然,如果在相同的渲染操作期间没有使用多个资源,甚至可以将相同的内存块重新用于多个资源(前提是刷新了它们的数据)。这被称为别名,一些Vulkan函数具有显式标志来指定要执行此操作。

for(let i = 0; i < 270; i++) { const vertexBuffers1 = []; const colorVbos1 = []; const layerIndexVbos1 = []; const IndexVbos1 = []; const vaos1 = []; for(let i = 0; i < 200; i++) { const vao = gl.createVertexArray(); gl.bindVertexArray(vao); const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vPosition); vertexBuffers.push(vertexBuffer); const colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, colorss, gl.STATIC_DRAW); gl.vertexAttribPointer(vcolor, 4, gl.UNSIGNED_BYTE, true, 0, 0); gl.enableVertexAttribArray(vcolor); colorVbos1.push(colorBuffer); const layerBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, layerBuffer); gl.bufferData(gl.ARRAY_BUFFER, layerIndexss, gl.STATIC_DRAW); gl.vertexAttribPointer(vlayerIndex, 1, gl.UNSIGNED_BYTE, false, 0, 0); gl.enableVertexAttribArray(vlayerIndex); layerIndexVbos1.push(layerBuffer); const IndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, IndexBuffer); gl.bufferData(gl.ARRAY_BUFFER, Indexss, gl.STATIC_DRAW); gl.vertexAttribPointer(vIndex, 4, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vIndex); IndexVbos1.push(IndexBuffer); vaos1.push(vao); } gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindVertexArray(null); for (let vbo of vertexBuffers1) { gl.deleteBuffer(vbo); } for (let vbo of colorVbos1) { gl.deleteBuffer(vbo); } for (let vbo of layerIndexVbos1) { gl.deleteBuffer(vbo); } for (let vbo of IndexVbos1) { gl.deleteBuffer(vbo); } for (let vao of vaos1) { gl.deleteVertexArray(vao); } }这种操作显存上涨比上一种更严重,为什么
03-14
解析如下代码: import BaseLayerViewGL2D from "@arcgis/core/views/2d/layers/BaseLayerViewGL2D.js"; import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer.js"; import * as reactiveUtils from "@arcgis/core/core/reactiveUtils.js"; const CustomLayerView2D = BaseLayerViewGL2D.createSubclass({ // Locations of the two vertex attributes that we use. They // will be bound to the shader program before linking. aPosition: 0, aOffset: 1, aTexcoord: 2, aDistance: 3, aUpright: 4, constructor() { // Geometrical transformations that must be recomputed // from scratch at every frame. this.transform = mat3.create(); this.rotation = mat3.create(); this.translationToCenter = vec2.create(); this.screenTranslation = vec2.create(); this.display = mat3.create(); // Whether the vertex and index buffers need to be updated // due to a change in the layer data. this.needsUpdate = false; }, // Called once a custom layer is added to the map.layers collection and this layer view is instantiated. attach() { const gl = this.context; // We listen for changes to the graphics collection of the layer // and trigger the generation of new frames. A frame rendered while // `needsUpdate` is true may cause an update of the vertex and // index buffers. const requestUpdate = () => { // Tessellate graphics. this.promises = []; this.layer.graphics.forEach(this.processGraphic.bind(this)); Promise.all(this.promises).then((meshes) => { this.meshes = meshes; this.needsUpdate = true; this.requestRender(); }); }; this.watcher = reactiveUtils.watch( () => this.layer.graphics, requestUpdate, { initial: true } ); // Define and compile shaders. const vertexSource = ` precision highp float; uniform mat3 u_transform; uniform mat3 u_rotation; uniform mat3 u_display; attribute vec2 a_position; attribute vec2 a_offset; attribute vec2 a_texcoord; attribute float a_distance; attribute float a_upright; varying vec2 v_texcoord; void main(void) { vec3 transformedOffset = mix(u_rotation * vec3(a_offset, 0.0), vec3(a_offset, 0.0), a_upright); gl_Position.xy = (u_display * (u_transform * vec3(a_position, 1.0) + transformedOffset)).xy; gl_Position.zw = vec2(0.0, 1.0); v_texcoord = a_texcoord; }`; const fragmentSource = ` precision highp float; varying vec2 v_texcoord; void main(void) { gl_FragColor = vec4(v_texcoord, 0.9, 0.5); }`; const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexSource); gl.compileShader(vertexShader); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); // Create the shader program. this.program = gl.createProgram(); gl.attachShader(this.program, vertexShader); gl.attachShader(this.program, fragmentShader); // Bind attributes. gl.bindAttribLocation(this.program, this.aPosition, "a_position"); gl.bindAttribLocation(this.program, this.aOffset, "a_offset"); gl.bindAttribLocation(this.program, this.aTexcoord, "a_texcoord"); gl.bindAttribLocation(this.program, this.aDistance, "a_distance"); gl.bindAttribLocation(this.program, this.aUpright, "a_upright"); // Link. gl.linkProgram(this.program); // Shader objects are not needed anymore. gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); // Retrieve uniform locations once and for all. this.uTransform = gl.getUniformLocation( this.program, "u_transform" ); this.uRotation = gl.getUniformLocation( this.program, "u_rotation" ); this.uDisplay = gl.getUniformLocation(this.program, "u_display"); // Create the vertex and index buffer. They are initially empty. We need to track the // size of the index buffer because we use indexed drawing. this.vertexBuffer = gl.createBuffer(); this.indexBuffer = gl.createBuffer(); // Number of indices in the index buffer. this.indexBufferSize = 0; // When certain conditions occur, we update the buffers and re-compute and re-encode // all the attributes. When buffer update occurs, we also take note of the current center // of the view state, and we reset a vector called `translationToCenter` to [0, 0], meaning that the // current center is the same as it was when the attributes were recomputed. this.centerAtLastUpdate = vec2.fromValues( this.view.state.center[0], this.view.state.center[1] ); }, // Called once a custom layer is removed from the map.layers collection and this layer view is destroyed. detach() { // Stop watching the `layer.graphics` collection. this.watcher.remove(); const gl = this.context; // Delete buffers and programs. gl.deleteBuffer(this.vertexBuffer); gl.deleteBuffer(this.indexBuffer); gl.deleteProgram(this.program); }, // Called every time a frame is rendered. render(renderParameters) { const gl = renderParameters.context; const state = renderParameters.state; // Update vertex positions. This may trigger an update of // the vertex coordinates contained in the vertex buffer. // There are three kinds of updates: // - Modification of the layer.graphics collection ==> Buffer update // - The view state becomes non-stationary ==> Only view update, no buffer update // - The view state becomes stationary ==> Buffer update this.updatePositions(renderParameters); // If there is nothing to render we return. if (this.indexBufferSize === 0) { return; } // Update view `transform` matrix; it converts from map units to pixels. mat3.identity(this.transform); this.screenTranslation[0] = (state.pixelRatio * state.size[0]) / 2; this.screenTranslation[1] = (state.pixelRatio * state.size[1]) / 2; mat3.translate( this.transform, this.transform, this.screenTranslation ); mat3.rotate( this.transform, this.transform, (Math.PI * state.rotation) / 180 ); mat3.scale(this.transform, this.transform, [ state.pixelRatio / state.resolution, -state.pixelRatio / state.resolution ]); mat3.translate( this.transform, this.transform, this.translationToCenter ); // Update view `rotate` matrix; it is the rotation component of the full `transform` matrix. mat3.identity(this.rotation); mat3.rotate( this.rotation, this.rotation, (Math.PI * state.rotation) / 180 ); // Update view `display` matrix; it converts from pixels to normalized device coordinates. mat3.identity(this.display); mat3.translate(this.display, this.display, [-1, 1]); mat3.scale(this.display, this.display, [ 2 / (state.pixelRatio * state.size[0]), -2 / (state.pixelRatio * state.size[1]) ]); // Draw. gl.useProgram(this.program); gl.uniformMatrix3fv(this.uTransform, false, this.transform); gl.uniformMatrix3fv(this.uRotation, false, this.rotation); gl.uniformMatrix3fv(this.uDisplay, false, this.display); gl.uniform1f(this.uCurrentTime, performance.now() / 1000.0); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.enableVertexAttribArray(this.aPosition); gl.enableVertexAttribArray(this.aOffset); gl.enableVertexAttribArray(this.aTexcoord); gl.enableVertexAttribArray(this.aDistance); gl.enableVertexAttribArray(this.aUpright); gl.vertexAttribPointer(this.aPosition, 2, gl.FLOAT, false, 32, 0); gl.vertexAttribPointer(this.aOffset, 2, gl.FLOAT, false, 32, 8); gl.vertexAttribPointer(this.aTexcoord, 2, gl.FLOAT, false, 32, 16); gl.vertexAttribPointer(this.aDistance, 1, gl.FLOAT, false, 32, 24); gl.vertexAttribPointer(this.aUpright, 1, gl.FLOAT, false, 32, 28); gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl.disable(gl.CULL_FACE); gl.drawElements( gl.TRIANGLES, this.indexBufferSize, gl.UNSIGNED_SHORT, 0 ); // Request new render because markers are animated. this.requestRender(); }, processGraphic(g) { switch (g.geometry.type) { case "extent": this.promises.push( this.tessellateExtent(g.geometry).then( (mesh) => { return { mesh: mesh, attributes: g.attributes, symbol: g.symbol }; } ) ); break; case "point": this.promises.push( this.tessellatePoint(g.geometry, { x: (g.attributes && g.attributes.x != null) ? g.attributes.x : -16, y: (g.attributes && g.attributes.y != null) ? g.attributes.y : 16, width: (g.attributes && g.attributes.width != null) ? g.attributes.width : 32, height: (g.attributes && g.attributes.height != null) ? g.attributes.height : 32, }).then( (mesh) => { return { mesh: mesh, attributes: g.attributes, symbol: g.symbol }; }) ); break; case "multipoint": this.promises.push( this.tessellateMultipoint(g.geometry, { x: (g.attributes && g.attributes.x != null) ? g.attributes.x : -16, y: (g.attributes && g.attributes.y != null) ? g.attributes.y : 16, width: (g.attributes && g.attributes.width != null) ? g.attributes.width : 32, height: (g.attributes && g.attributes.height != null) ? g.attributes.height : 32, }).then( (mesh) => { return { mesh: mesh, attributes: g.attributes, symbol: g.symbol }; } ) ); break; case "polyline": this.promises.push( this.tessellatePolyline( g.geometry, (g.attributes && g.attributes.width != null) ? g.attributes.width : 20 ).then( (mesh) => { return { mesh: mesh, attributes: g.attributes, symbol: g.symbol }; } ) ); break; case "polygon": this.promises.push( this.tessellatePolygon(g.geometry).then( (mesh) => { return { mesh: mesh, attributes: g.attributes, symbol: g.symbol }; } ) ); break; } }, // Called internally from render(). updatePositions(renderParameters) { const gl = renderParameters.context; const stationary = renderParameters.stationary; const state = renderParameters.state; // If we are not stationary we simply update the `translationToCenter` vector. if (!stationary) { vec2.sub( this.translationToCenter, this.centerAtLastUpdate, state.center ); this.requestRender(); return; } // If we are stationary, the `layer.graphics` collection has not changed, and // we are centered on the `centerAtLastUpdate`, we do nothing. if ( !this.needsUpdate && this.translationToCenter[0] === 0 && this.translationToCenter[1] === 0 ) { return; } // Otherwise, we record the new encoded center, which imply a reset of the `translationToCenter` vector, // we record the update time, and we proceed to update the buffers. this.centerAtLastUpdate.set(state.center); this.translationToCenter[0] = 0; this.translationToCenter[1] = 0; this.needsUpdate = false; // Generate vertex data. const vertexCount = this.meshes.reduce((vertexCount, item) => { return vertexCount + item.mesh.vertices.length; }, 0); const indexCount = this.meshes.reduce((indexCount, item) => { return indexCount + item.mesh.indices.length; }, 0); const vertexData = new Float32Array(vertexCount * 8); const indexData = new Uint16Array(indexCount); let currentVertex = 0; let currentIndex = 0; for (let meshIndex = 0; meshIndex < this.meshes.length; ++meshIndex) { const item = this.meshes[meshIndex]; const mesh = item.mesh; const upright = (item.attributes && item.attributes.upright) ? 1 : 0; for (let i = 0; i < mesh.indices.length; ++i) { const idx = mesh.indices[i]; indexData[currentIndex] = currentVertex + idx; currentIndex++; } for (let i = 0; i < mesh.vertices.length; ++i) { const v = mesh.vertices[i]; vertexData[currentVertex * 8 + 0] = v.x - this.centerAtLastUpdate[0]; vertexData[currentVertex * 8 + 1] = v.y - this.centerAtLastUpdate[1]; vertexData[currentVertex * 8 + 2] = v.xOffset; vertexData[currentVertex * 8 + 3] = v.yOffset; vertexData[currentVertex * 8 + 4] = v.uTexcoord; vertexData[currentVertex * 8 + 5] = v.vTexcoord; vertexData[currentVertex * 8 + 6] = v.distance; vertexData[currentVertex * 8 + 7] = upright; currentVertex++; } } // Upload data to the GPU gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW); // Record number of indices. this.indexBufferSize = indexCount; } }); const CustomLayer = GraphicsLayer.createSubclass({ createLayerView: function (view) { if (view.type === "2d") { return new CustomLayerView2D({ view: view, layer: this }); } } }); export default CustomLayer;
最新发布
09-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值