知识点:向顶点着色器传递矩阵数据
1-基本思路
对于矩阵概念,我们这里就不做详解了。
矩阵是图形学的基础,如果有同学没有学过矩阵,可以看一下闫令琪老师的课。
我们接下要说的是attribute数据、uniform数据和顶点着色器、片元着色器的对应关系。
其实,这个对应关系和WebGL 是一样的。
-
attribute 变量
- 对应顶点相关的数据。这么说可能有点笼统,我们可以再详细点,attribute 对应的是与顶点存在一对一关系的数据。
- 只能出现在顶点着色器里
-
uniform 变量:
- 对应的是与点前渲染管线pipeline 里的所有顶点都存在关系的数据,它跟顶点的关系式一对全部的。
- 可以出现在顶点着色器里,也可以出现在片元着色器里。
我们可以把attribute 理解为基于某一个顶点的局部变量,而uniform 则是基于所有顶点的全局变量。
接下来,我们所要说的矩阵,就属于uniform 遍历,因为它是要对pipeline 里的所有顶点进行变换的。
2-用矩阵变换图形
接下我们会以模型矩阵为例,说一下如何缩放一个三角形。
我接着之前js向片元着色器传递参数的代码改了。
1.声明模型矩阵modelMatrix。
const modelMatrix=new Float32Array([
0.5,0,0,0,
0,0.5,0,0,
0,0,0.5,0,
0,0,0,1,
])
这个矩阵会把模型缩小一半。
2.在建立渲染管线的时候,建立用于存储模型矩阵的缓冲对象。
//模型矩阵的缓冲区
const modelBuffer = device.createBuffer({
size: 4 * 4 * 4, //行数*列数*BYTES_PER_ELEMENT
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
})
device.queue.writeBuffer(modelBuffer, 0, modelMatrix)
3.将模型矩阵的缓冲对象添加到BindGroup里,这样我们才能在着色器里通过BindGroup索引获取相应数据。
// 对buffer进行组合
const uniformGroup = device.createBindGroup({
// 布局
layout: pipeline.getBindGroupLayout(0),
// 添加buffer
entries: [
// 图形颜色
{
// 位置
binding: 0,
// 资源
resource: {
buffer: colorBuffer,
},
},
// 模型矩阵
{
// 位置
binding: 1,
// 资源
resource: {
buffer: modelBuffer,
},
},
],
})
在当前的BindGroup已经有两个uniform 数据了:
- 图形颜色,会在片元着色器里整体设置所有顶点所定义的片元区域的颜色。
- 模型矩阵,会在顶点着色器里变换所有顶点。
4.在顶点着色器里获取并应用模型矩阵。
@group(0) @binding(1) var<uniform> modelMatrix:mat4x4<f32>;
@vertex
fn main(@location(0) position : vec3<f32>) -> @builtin(position) vec4<f32> {
return modelMatrix*vec4<f32>(position, 1.0);
}
- @group(0) 对应BindGroup里的layout:pipeline.getBindGroupLayout(0)
- @binding(1) 对应BindGroup里的binding: 1
- @location(0) 对应Pipeline 里的shaderLocation: 0
现在我们便实现了模型矩阵对图形的缩放,效果如下:
整体代码如下:
- /src/matrix.ts
import positionVert from "./shaders/matrix.vert.wgsl?raw"
import colorFrag from "./shaders/color.frag.wgsl?raw"
// 初始化WebGPU
async function initWebGPU(canvas: HTMLCanvasElement) {
// 判断当前设备是否支持WebGPU
if (!navigator.gpu) throw new Error("Not Support WebGPU")
// 请求Adapter对象,GPU在浏览器中的抽象代理
const adapter = await navigator.gpu.requestAdapter({
/* 电源偏好
high-performance 高性能电源管理
low-power 节能电源管理模式
*/
powerPreference: "high-performance",
})
if (!adapter) throw new Error("No Adapter Found")
//请求GPU设备
const device = await adapter.requestDevice()
//获取WebGPU上下文对象
const context = canvas.getContext("webgpu") as GPUCanvasContext
//获取浏览器默认的颜色格式
const format = navigator.gpu.getPreferredCanvasFormat()
//设备分辨率
const devicePixelRatio = window.devicePixelRatio || 1
//canvas尺寸
const size = {
width: canvas.clientWidth * devicePixelRatio,
height: canvas.clientHeight * devicePixelRatio,
}
canvas.width = size.width
canvas.height =size.height
//配置WebGPU
context.configure({
device,
format,
// Alpha合成模式,opaque为不透明
alphaMode: "opaque",
})
return { device, context, format, size }
}
// 顶点点位
const vertex = new Float32Array([
// 0
0, 0.5, 0,
// 1
-0.5, -0.5, 0,
// 2
0.5, -0.5, 0.0,
])
// 顶点颜色
const color = new Float32Array([1, 1, 0, 1])
// 模型矩阵
const modelMatrix=new Float32Array([
0.5,0,0,0,
0,0.5,0,0,
0,0,0.5,0,
0,0,0,1,
])
// 创建渲染管线
async function initPipeline(device: GPUDevice, format: GPUTextureFormat) {
// 顶点缓冲区
const vertexBuffer = device.createBuffer({
// 顶点长度
size: vertex.byteLength,
// 用途,用于顶点着色,可写
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
})
// 写入数据
device.queue.writeBuffer(vertexBuffer, 0, vertex)
// 颜色缓冲区
const colorBuffer = device.createBuffer({
size: color.byteLength, //4 * 4,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
})
// 写入数据
device.queue.writeBuffer(colorBuffer, 0, color)
//模型矩阵的缓冲区
const modelBuffer = device.createBuffer({
size: 4 * 4 * 4, //行数*列数*BYTES_PER_ELEMENT
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
})
device.queue.writeBuffer(modelBuffer, 0, modelMatrix)
const descriptor: GPURenderPipelineDescriptor = {
// 顶点着色器
vertex: {
// 着色程序
module: device.createShaderModule({
code: positionVert,
}),
// 主函数
entryPoint: "main",
//缓冲数据,1个渲染管道可最多传入8个缓冲数据
buffers: [
{
// 顶点长度,以字节为单位
arrayStride: 3 * 4,
attributes: [
{
// 变量索引
shaderLocation: 0,
// 偏移
offset: 0,
// 参数格式
format: "float32x3",
},
],
},
],
},
// 片元着色器
fragment: {
// 着色程序
module: device.createShaderModule({
code: colorFrag,
}),
// 主函数
entryPoint: "main",
// 渲染目标
targets: [
{
// 颜色格式
format,
},
],
},
// 初始配置
primitive: {
//拓扑结构,triangle-list为绘制独立三角形
topology: "triangle-list",
},
// 渲染管线的布局
layout: "auto",
}
// 创建异步管线
const pipeline = await device.createRenderPipelineAsync(descriptor)
// 对buffer进行组合
const uniformGroup = device.createBindGroup({
// 布局
layout: pipeline.getBindGroupLayout(0),
// 添加buffer
entries: [
// 图形颜色
{
// 位置
binding: 0,
// 资源
resource: {
buffer: colorBuffer,
},
},
// 模型矩阵
{
// 位置
binding: 1,
// 资源
resource: {
buffer: modelBuffer,
},
},
],
})
//返回异步管线、顶点缓冲区、BindGroup
return { pipeline, vertexBuffer, uniformGroup }
}
// 编写绘图指令,并传递给本地的GPU设备
function draw(
device: GPUDevice,
context: GPUCanvasContext,
pipelineObj: {
pipeline: GPURenderPipeline
vertexBuffer: GPUBuffer
uniformGroup: GPUBindGroup
}
) {
// 创建指令编码器
const commandEncoder = device.createCommandEncoder()
// GPU纹理视图
const view = context.getCurrentTexture().createView()
// 渲染通道配置数据
const renderPassDescriptor: GPURenderPassDescriptor = {
// 颜色附件
colorAttachments: [
{
view: view,
// 绘图前是否清空view,建议清空clear
loadOp: "clear", // clear/load
// 清理画布的颜色
clearValue: { r: 0, g: 0, b: 0, a: 1.0 },
//绘制完成后,是否保留颜色信息
storeOp: "store", // store/discard
},
],
}
// 建立渲染通道,类似图层
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor)
// 传入渲染管线
passEncoder.setPipeline(pipelineObj.pipeline)
// 把顶点缓冲区写入渲染通道
passEncoder.setVertexBuffer(0, pipelineObj.vertexBuffer)
// 把含有颜色缓冲区的BindGroup写入渲染通道
passEncoder.setBindGroup(0, pipelineObj.uniformGroup)
// 绘图,3 个顶点
passEncoder.draw(3)
// 结束编码
passEncoder.end()
// 结束指令编写,并返回GPU指令缓冲区
const gpuCommandBuffer = commandEncoder.finish()
// 向GPU提交绘图指令,所有指令将在提交后执行
device.queue.submit([gpuCommandBuffer])
}
async function run() {
const canvas = document.querySelector("canvas")
if (!canvas) throw new Error("No Canvas")
// 初始化WebGPU
const { device, context, format } = await initWebGPU(canvas)
// 初始化Pipeline
const pipelineObj = await initPipeline(device, format)
// 绘图
draw(device, context, pipelineObj)
// 自适应窗口尺寸
window.addEventListener("resize", () => {
context.configure({
device,
format,
size: {
width: canvas.clientWidth * devicePixelRatio,
height: canvas.clientHeight * devicePixelRatio,
},
compositingAlphaMode: "opaque",
})
draw(device, context, pipelineObj)
})
}
run()
现在我们已经掌握向顶点着色器传递矩阵数据的方法,视图矩阵和投影矩阵也是按照同样原理来传递的,这个我会在后面的课程里说。
下节课,我们会先说一下多attribute变量,即在一个顶点着色器里写入多个与顶点相关的attribute变量,如顶点点位和颜色。