美文网首页
Rotating Cube

Rotating Cube

作者: 不决书 | 来源:发表于2024-01-14 07:50 被阅读0次

代码分析说明:

// 数学库
import { mat4, vec3 } from 'wgpu-matrix';
import { makeSample, SampleInit } from '../../components/SampleLayout';
// cube 顶点相关数组
import {
  cubeVertexArray,
  cubeVertexSize,
  cubeUVOffset,
  cubePositionOffset,
  cubeVertexCount,
} from '../../meshes/cube';

import basicVertWGSL from '../../shaders/basic.vert.wgsl';
import vertexPositionColorWGSL from '../../shaders/vertexPositionColor.frag.wgsl';

const init: SampleInit = async ({ canvas, pageState }) => {
  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();

  if (!pageState.active) return;
  const context = canvas.getContext('webgpu') as GPUCanvasContext;

  const devicePixelRatio = window.devicePixelRatio;
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format: presentationFormat,
    alphaMode: 'premultiplied',
  });

  // Create a vertex buffer from the cube data.
  // 创建顶点buffer 
  const verticesBuffer = device.createBuffer({
    size: cubeVertexArray.byteLength,
    usage: GPUBufferUsage.VERTEX, // 顶点数据buffer
    mappedAtCreation: true,  // 创建后立即映射内存
  });

 //  将顶点数据映射 顶点数据
  new Float32Array(verticesBuffer.getMappedRange()).set(cubeVertexArray);
 // 取消映射,数据映射完毕后必须需求映射,不然对应的显存与内存都无法操作
  verticesBuffer.unmap();

  const pipeline = device.createRenderPipeline({
    layout: 'auto',
    vertex: {
      module: device.createShaderModule({
        code: basicVertWGSL,
      }),
      entryPoint: 'main',
      buffers: [  //  buffer的数据格式设置
        {
          arrayStride: cubeVertexSize,  // 每一个顶点数据的大小
          attributes: [
            {
              // position 顶点数据
              shaderLocation: 0,    // 顶点数据的插槽位置
              offset: cubePositionOffset,   // 顶点数据的偏移量
              format: 'float32x4',   // 顶点数据的格式
            },
            {
              // uv
              shaderLocation: 1,    // uv数据的插槽
              offset: cubeUVOffset, // uv数据的偏移量
              format: 'float32x2',  // uv数据的格式
            },
          ],
        },
      ],
    },
    fragment: {
      module: device.createShaderModule({
        code: vertexPositionColorWGSL,
      }),
      entryPoint: 'main',
      targets: [
        {
          format: presentationFormat,
        },
      ],
    },
    primitive: {
      topology: 'triangle-list',

      // Backface culling since the cube is solid piece of geometry.
      // Faces pointing away from the camera will be occluded by faces
      // pointing toward the camera.
      // 设置背面剔除
      cullMode: 'back',
    },

    // Enable depth testing so that the fragment closest to the camera
    // is rendered in front.
   // 开启深度测试 
    depthStencil: {
      depthWriteEnabled: true, // 启用深度写
      depthCompare: 'less',  // 深度对比,小于
      format: 'depth24plus',  // 使用24位深度值
    },
  });

  // 创建深度目标纹理
  const depthTexture = device.createTexture({
    size: [canvas.width, canvas.height],
    format: 'depth24plus',
    usage: GPUTextureUsage.RENDER_ATTACHMENT,
  });

  // uniform的大小 矩阵为 4 * 4 * 4
  const uniformBufferSize = 4 * 16; // 4x4 matrix
  // 创建一个uniform buffer
  const uniformBuffer = device.createBuffer({
    size: uniformBufferSize,
    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  });

  // 创建bindGroup对象
  const uniformBindGroup = device.createBindGroup({
    layout: pipeline.getBindGroupLayout(0),  // 设置布局为渲染管线的bindGroupLayout 0 
    entries: [
      {
        binding: 0,   // 设置bind
        resource: { 
          buffer: uniformBuffer, // 设置buffer
        },
      },
    ],
  });


  const renderPassDescriptor: GPURenderPassDescriptor = {
  // 颜色附件   
 colorAttachments: [
      {
        view: undefined, // Assigned later

        clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
        loadOp: 'clear',
        storeOp: 'store',
      },
    ],
    // 深度模版附件
    depthStencilAttachment: {
      view: depthTexture.createView(),

      depthClearValue: 1.0,
      depthLoadOp: 'clear',
      depthStoreOp: 'store',
    },
  };
  
 // 屏幕长宽比
  const aspect = canvas.width / canvas.height;
  // 设置投影矩阵
  const projectionMatrix = mat4.perspective(
    (2 * Math.PI) / 5,
    aspect,
    1,
    100.0
  );
   // 定义mvp 矩阵
  const modelViewProjectionMatrix = mat4.create();

  function getTransformationMatrix() {
    // 定义视图矩阵
    const viewMatrix = mat4.identity();
    // 平移视图矩阵
    mat4.translate(viewMatrix, vec3.fromValues(0, 0, -4), viewMatrix);
    const now = Date.now() / 1000;
    // 旋转视图矩阵,相当于旋转相机
    mat4.rotate(
      viewMatrix,
      vec3.fromValues(Math.sin(now), Math.cos(now), 0),
      1,
      viewMatrix
    );
    // 计算MVP矩阵
    mat4.multiply(projectionMatrix, viewMatrix, modelViewProjectionMatrix);

    return modelViewProjectionMatrix as Float32Array;
  }

  function frame() {
    // Sample is no longer the active page.
    if (!pageState.active) return;

    const transformationMatrix = getTransformationMatrix();
    // 将矩阵数据写入到uniform buffer中
    device.queue.writeBuffer(
      uniformBuffer,
      0,
      transformationMatrix.buffer,
      transformationMatrix.byteOffset,
      transformationMatrix.byteLength
    );
    // 设置颜色附件的view
    renderPassDescriptor.colorAttachments[0].view = context
      .getCurrentTexture()
      .createView();
  
    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    passEncoder.setPipeline(pipeline);
    // 设置绑定组 0
    passEncoder.setBindGroup(0, uniformBindGroup);
    passEncoder.setVertexBuffer(0, verticesBuffer);
    passEncoder.draw(cubeVertexCount);
    passEncoder.end();
    device.queue.submit([commandEncoder.finish()]);

    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
};

顶点着色器

// 定义uniform 为结构体
struct Uniforms {
  modelViewProjectionMatrix : mat4x4<f32>, 
}
// 绑定组为 0  绑定插槽 0  
@binding(0) @group(0) var<uniform> uniforms : Uniforms;

// 返回值结构体
struct VertexOutput {
  // 内建 位置
  @builtin(position) Position : vec4<f32>,
  // 插槽 0 uv 相对于片元着色器的
  @location(0) fragUV : vec2<f32>,
  // 插槽 1 位置 相对于片元着色器的
  @location(1) fragPosition: vec4<f32>,
}

@vertex
fn main(
  // 入参,插槽0 传递顶点数据
  @location(0) position : vec4<f32>,
 // 入参, 插槽 1 传递uv数据 
  @location(1) uv : vec2<f32>
) -> VertexOutput {
  // 定义返回值,在wgsl中,只有var 申明的变量是可修改的
  var output : VertexOutput;
  output.Position = uniforms.modelViewProjectionMatrix * position;
  output.fragUV = uv;
  output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0));
  return output;
}

片元着色器

@fragment
fn main(
  // 入参,对于与顶点着色器返回值的插槽 0 
  @location(0) fragUV: vec2<f32>,
// 入参,对于与顶点着色器返回值的插槽 1 
  @location(1) fragPosition: vec4<f32>
) -> @location(0) vec4<f32> {
  return fragPosition;
}

顶点数据定义

// 每个顶点的数据长度,包括了位置、颜色、uv
export const cubeVertexSize = 4 * 10; // Byte size of one cube vertex.
// 位置的偏移量
export const cubePositionOffset = 0;
// 颜色的偏移量
export const cubeColorOffset = 4 * 4; // Byte offset of cube vertex color attribute.
// uv的偏移量
export const cubeUVOffset = 4 * 8;
// 总共的顶点数,用户传递给编码器的draw函数
export const cubeVertexCount = 36;

// prettier-ignore
export const cubeVertexArray = new Float32Array([
  // float4 position, float4 color, float2 uv,
  1, -1, 1, 1,   1, 0, 1, 1,  0, 1,
  -1, -1, 1, 1,  0, 0, 1, 1,  1, 1,
  -1, -1, -1, 1, 0, 0, 0, 1,  1, 0,
  1, -1, -1, 1,  1, 0, 0, 1,  0, 0,
  1, -1, 1, 1,   1, 0, 1, 1,  0, 1,
  -1, -1, -1, 1, 0, 0, 0, 1,  1, 0,

  1, 1, 1, 1,    1, 1, 1, 1,  0, 1,
  1, -1, 1, 1,   1, 0, 1, 1,  1, 1,
  1, -1, -1, 1,  1, 0, 0, 1,  1, 0,
  1, 1, -1, 1,   1, 1, 0, 1,  0, 0,
  1, 1, 1, 1,    1, 1, 1, 1,  0, 1,
  1, -1, -1, 1,  1, 0, 0, 1,  1, 0,

  -1, 1, 1, 1,   0, 1, 1, 1,  0, 1,
  1, 1, 1, 1,    1, 1, 1, 1,  1, 1,
  1, 1, -1, 1,   1, 1, 0, 1,  1, 0,
  -1, 1, -1, 1,  0, 1, 0, 1,  0, 0,
  -1, 1, 1, 1,   0, 1, 1, 1,  0, 1,
  1, 1, -1, 1,   1, 1, 0, 1,  1, 0,

  -1, -1, 1, 1,  0, 0, 1, 1,  0, 1,
  -1, 1, 1, 1,   0, 1, 1, 1,  1, 1,
  -1, 1, -1, 1,  0, 1, 0, 1,  1, 0,
  -1, -1, -1, 1, 0, 0, 0, 1,  0, 0,
  -1, -1, 1, 1,  0, 0, 1, 1,  0, 1,
  -1, 1, -1, 1,  0, 1, 0, 1,  1, 0,

  1, 1, 1, 1,    1, 1, 1, 1,  0, 1,
  -1, 1, 1, 1,   0, 1, 1, 1,  1, 1,
  -1, -1, 1, 1,  0, 0, 1, 1,  1, 0,
  -1, -1, 1, 1,  0, 0, 1, 1,  1, 0,
  1, -1, 1, 1,   1, 0, 1, 1,  0, 0,
  1, 1, 1, 1,    1, 1, 1, 1,  0, 1,

  1, -1, -1, 1,  1, 0, 0, 1,  0, 1,
  -1, -1, -1, 1, 0, 0, 0, 1,  1, 1,
  -1, 1, -1, 1,  0, 1, 0, 1,  1, 0,
  1, 1, -1, 1,   1, 1, 0, 1,  0, 0,
  1, -1, -1, 1,  1, 0, 0, 1,  0, 1,
  -1, 1, -1, 1,  0, 1, 0, 1,  1, 0,
]);

总结步骤

  1. 立方体为三维物体,所以需要开启深度测试,在 device.createRenderPipeline的参数中,加入depthStencil的配置,以及背面剔除cullMode: 'back'
  2. 数据不再Shader中,需要使用device.createBuffer创建buffer, 然后把顶点的数据通过 new Float32Array(verticesBuffer.getMappedRange()).set(cubeVertexArray) 将数据添加到缓存区,,然后使用verticesBuffer.unmap取消映射
  3. 加入了MVP矩阵,需要uniform buffer 传递,通过device.createBuffer创建buffer, ,并设置用例为usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
  4. 设置 uniform的bindgroup
  5. 使用数据库,创建MVP矩阵,并通过 device.queue.writeBuffer 将矩阵数据写入到缓存区
  6. 在渲染管线中,设置buffer的数据布局格式
  7. 创建深度纹理
  8. 编码器中绑定uniform bindgroup
  9. 添加深度附件,用户深度的测试
  10. 顶点的buffer通过创建渲染管线的参数来设置,device.queue.writeBuffer写入,并在编码器中设置buffer passEncoder.setVertexBuffer(0, verticesBuffer), 而 uniform buffer 是通过编码器的passEncoder.setBindGroup(0, uniformBindGroup) 来设置

相关文章

网友评论

      本文标题:Rotating Cube

      本文链接:https://www.haomeiwen.com/subject/dttqodtx.html