WebGPU图形编程(5):构建一个线框球体<学习引自徐博士教程>

一、前提

在代码开始之前,你需要自行准备配置环境文件或者你可以去GitHub下载简单配置好的文件:https://github.com/zhiwenhao-0807/webgpu.git

如果你对刚开始如何配置环境疑惑,可以去跟着第一篇博客去学习:https://www.cnblogs.com/wenhao0807/p/15806359.html

1.1认知

本节要实现一个球体线框三维实体,就需要对球坐标系有一个初步的认知,如果你有图形基础,可以看出图中采用的是右手坐标系;

创建球体线框,可以使用UV球体方法来创建,u代表经度(纵向)v代表纬度(横向),u和v在球体表面形成了网格,因为我们是创建线框,所以先要创建一个单位网格,一个单位网格由四条线段组成,但你只需要绘制两条实线段就可以了,你可以想象一下,遍历图中两条实线段,按照球体排列平铺就可以形成线框球体,如果你把四条都绘制出来,当然也可以生成线框球体,但会有重叠部分,并且多余绘制会浪费资源消耗;

好的,前提的一些基础概念说完了,接下来开始编程部分!

 二、编程部分

在编程开始之前,我想要简单一一说明下src下各工程文件的含义,相比之前对比,本节多了wireframe.ts和math-func.ts两个文件,并且代码结构有了相应的变化

helper.ts:和之前代码一致,没有改变,主要用途是封装一些基本调用GPU的方法,gl-matrix转换方法,供调用,它是通用的;

main.ts:主要代码文件,主要实现三维的功能渲染,本节main代码中,你可以看到代码量其实非常少,三维对象创建和矩阵转换被封装为wireframe和math-func两个文件

math-func.ts:用来做球体点位的矩阵转换;

shader.ts:着色器代码,<但着色器代码是文本类形式,没有提示代码块的功能,所以我们之前都把着色器代码转移到pipeline描述>

vertex_data.ts:描述封装单元格线段函数,在main调用;

wireframe.ts:三维对象创建,主要描述buffer、pipeline、bindgroup、renderpass,<这个文件包含了大量着色器和渲染代码>

大致流程,你可以理解为:矩阵转换球体顶点数据(math-func)—>绘制单元格线段、并遍历球体顶点数据(vertex_data)—>缓冲区描述及着色器设计和渲染(wireframe)—>调用封装好的顶点数据和着色器渲染动画(main)

 

 2.1.创建math-func.ts<矩阵转换为球体顶点数据>

1 import { vec3 } from "gl-matrix";
2 
3 export const SpherePosition = (radius:number,theta:number,phi:number,center:vec3 = [0,0,0]) =>{
4     const snt = Math.sin(theta*Math.PI/180);
5     const cnt = Math.cos(theta*Math.PI/180);
6     const snp = Math.sin(phi*Math.PI/180);
7     const cnp = Math.cos(phi*Math.PI/180);
8     return vec3.fromValues(radius*snt*cnp+center[0],radius*cnt+center[1],-radius*snt*snp+center[2]);
9 }

 2.2.创建vertex_data.ts<绘制单元块线段,并按照球体顶点数据遍历线段>

 1 import { SpherePosition } from "./math-func";
 2 import { vec3 } from "gl-matrix";
 3 
 4 export const SphereWireframeData = (radius:number,u:number,v:number,center:vec3 =[0,0,1]) =>{
 5     if(u<2 || v<2)return;
 6     let pts =[];
 7     let pt:vec3;
 8     for(let i=0;i<u;i++){
 9         let pt1:vec3[]=[];
10         for(let j=0;j<v;j++){
11             pt =SpherePosition(radius,i*180/(u-1),j*360/(v-1),center);
12             pt1.push(pt);
13         }
14         pts.push(pt1);
15     }
16    
17 
18     let p = [] as any;
19 let p0,p1,p2,p3;
20 for(let i=0;i<u-1;i++){
21     for(let j=0;j<v-1;j++){
22         p0=pts[i][j];
23         p1=pts[i+1][j];
24         p3=pts[i][j+1];
25         p.push([
26             p0[0],p0[1],p0[2],p1[0],p1[1],p1[2],
27             p0[0],p0[1],p0[2],p3[0],p3[1],p3[2]
28         ]);
29     }
30    
31 }
32 return new Float32Array(p.flat());
33 }

2.3.创建wifeframe.ts<缓冲区创建和着色器描述>

如果你按照前面的教程一步一步学习过来的,你去看代码结构并不会懵,如果你看不懂没关系,我的建议是慢慢来,先把代码copy,完成实现;之后你再详细去看webgpu的API。

我简单说一下这个代码的流程,import(调用其他已经实现的类/方法)—>CreateWireframe(定义一个异步函数)—>shader\pipeline(进行着色器和管线函数声明创建)—>uniform data(虽然叫统一数据,但这个代码块里包含模型和透视矩阵的设计)—>rotation\camera(声明对象旋转和相机)—>uniform buffer and layout(创建buffer并且进行资源绑定)—>draw(渲染管线、绘制顶点数据)

  1 //这是一个通用文件,可以为不同的三维对象创建线框,例如:球体、圆柱体、圆环
  2 import { InitGPU,CreateGPUBuffer,CreateGPUBufferUint,CreateTransforms,CreateViewProjection,CreateAnimation } from "./helper";
  3 import { Shaders } from "./shaders";
  4 import { vec3,mat4 } from "gl-matrix";
  5 const createCamera=require('3d-view-controls');
  6 
  7 export const CreateWireframe =async (wireframeData:Float32Array,isAnimation=true)=> {  //wireframeData是一个输入变量,来自mian第7行
  8 
  9     const gpu =await InitGPU();
 10     const device =gpu.device;
 11 //create vertex buffers
 12 const numberOfVertices=wireframeData.length/3;
 13 const vertexBuffer = CreateGPUBuffer(device,wireframeData);
 14 
 15 const shader =Shaders();  //引用Shaders文件,在当前文件描述着色器代码
 16 const pipeline = device.createRenderPipeline({ //创建控制顶点和片段着色器阶段管线代码块
 17     vertex:{
 18         module:device.createShaderModule({
 19             code:shader.vertex
 20         }),
 21         entryPoint:"main",
 22         buffers:[{
 23             arrayStride:12,
 24             attributes:[{
 25                 shaderLocation:0,
 26                 format:"float32x3",
 27                 offset:0
 28         }]
 29     }]  
 30 },
 31         fragment:{
 32             module:device.createShaderModule({
 33                 code:shader.fragment
 34             }),
 35             entryPoint:"main",
 36             targets:[
 37                 {
 38                     format:gpu.format as GPUTextureFormat
 39                 }
 40             ]
 41         },
 42         primitive:{
 43             topology:"line-list",
 44         }
 45 });    
 46         
 47 //create uniform data
 48 const modelMatrix = mat4.create();
 49 const mvpMatrix = mat4.create();
 50 let vMatrix = mat4.create();
 51 let vpMatrix = mat4.create();
 52 const vp =CreateViewProjection(gpu.canvas.width/gpu.canvas.height);
 53 vpMatrix=vp.viewProjectionMatrix;
 54 
 55 //add rotation and camera
 56 let rotation =vec3.fromValues(0,0,0);
 57 var camera = createCamera(gpu.canvas,vp.cameraOption);
 58 
 59 //create uniform buffer and layout
 60 const uniformBuffer = device.createBuffer({
 61     size:64,
 62     usage:GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
 63 });
 64 
 65 const uniformBindGroup =device.createBindGroup({
 66     layout:pipeline.getBindGroupLayout(0),
 67     entries:[{
 68         binding:0,
 69         resource:{
 70             buffer:uniformBuffer,
 71             offset:0,
 72             size:64
 73         }
 74     }]
 75 });
 76 
 77 let textureView = gpu.context.getCurrentTexture().createView();
 78 const renderPassDescription = {
 79     colorAttachments:[{
 80         view:textureView,
 81         loadValue:{r:0.2,g:0.247,b:0.314,a:1.0}, //backgroud color
 82         storeOp:'store'
 83     }]
 84 };
 85 
 86 function draw(){
 87     if(!isAnimation){
 88         if(camera.tick()){
 89             const pMatrix = vp.projectionMatrix;
 90             vMatrix = camera.matrix;
 91             mat4.multiply(vpMatrix,pMatrix,vMatrix);
 92         }
 93     }
 94     CreateTransforms(modelMatrix,[0,0,0],rotation);
 95     mat4.multiply(mvpMatrix,vpMatrix,modelMatrix);
 96     device.queue.writeBuffer(uniformBuffer,0,mvpMatrix as ArrayBuffer);
 97 
 98     textureView = gpu.context.getCurrentTexture().createView();
 99     renderPassDescription.colorAttachments[0].view = textureView;
100     const commandEncoder = device.createCommandEncoder();
101     const renderPass = commandEncoder.beginRenderPass(renderPassDescription as GPURenderPassDescriptor)
102     
103     renderPass.setPipeline(pipeline);    //渲染管线
104     renderPass.setVertexBuffer(0,vertexBuffer);  //渲染顶点缓冲区
105     renderPass.setBindGroup(0,uniformBindGroup); //渲染资源绑定组
106     renderPass.draw(numberOfVertices);  //渲染顶点
107     renderPass.endPass();  //结束渲染通道编码器
108 
109     device.queue.submit([commandEncoder.finish()]);
110 }
111 CreateAnimation(draw,rotation,isAnimation);
112 }

 2.4.main.ts文件<这是src最后一步,调用前面所有的函数方法实现>

 1 import { CreateWireframe } from './wireframe';
 2 import { SphereWireframeData } from './vertex_data';
 3 import { vec3 } from 'gl-matrix';
 4 import $ from 'jquery';
 5 
 6 const Create3DObject = async (radius:number, u:number, v:number, center:vec3, isAnimation:boolean) => {
 7     const wireframeData = SphereWireframeData(radius, u, v, center) as Float32Array;
 8     await CreateWireframe(wireframeData, isAnimation);
 9 }
10 
11 let radius = 2;  //半径
12 let u = 20;   //u横向等分
13 let v = 15;   //v纵向等分
14 let center:vec3 = [0,0,0];  //中心位置
15 let isAnimation = true;   //球体自动旋转
16 
17 Create3DObject(radius, u, v, center, isAnimation);
18 
19 $('#id-radio input:radio').on('click', function(){
20     let val = $('input[name="options"]:checked').val();
21     if(val === 'animation') isAnimation = true;
22     else isAnimation = false;
23     Create3DObject(radius, u, v, center, isAnimation);
24 });
25 
26 $('#btn-redraw').on('click', function(){
27     const val = $('#id-center').val();
28     center = val?.toString().split(',').map(Number) as vec3;
29     radius = parseFloat($('#id-radius').val()?.toString() as string);
30     u = parseInt($('#id-u').val()?.toString() as string);
31     v = parseInt($('#id-v').val()?.toString() as string);
32     Create3DObject(radius, u, v, center, isAnimation);  
33 });

2.5.index.html<最后就写你的网页布局和调用打包的bundle.js>

 1 <!DOCTYPE html>
 2 <head>
 3    <meta charset="utf-8">
 4    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 5    <title>WebGPU Step-by-Step 13</title>
 6    <meta name="description" content="">
 7    <meta name="viewport" content="width=device-width, initial-scale=1">
 8 </head>
 9 
10 <body>
11    <style>
12       .grid {
13       display: grid;
14       height: 100%;
15       grid-template-columns: repeat(8, 1fr);
16       grid-template-rows: 100%;
17    }
18    .grid1 {
19       display: grid;
20       height: 35px;
21       grid-template-columns: repeat(8, 1fr);
22       grid-template-rows: 35px;
23    }
24    .item1 {
25       grid-column: 1/3;
26    }
27    .item2 {
28       grid-column: 3/9;
29    }
30    .item3 {
31       grid-column: 1/3;
32    }
33    .item4 {
34       grid-column: 3/8;
35    }
36    
37    </style>
38    <div style="margin-left:20px;">  
39       <h1>Sphere Wireframe</h1><br>
40 
41       <div class="grid">
42          <div class="item1">
43             <h2>Motion Control</h2>
44             <div id="id-radio">
45                <label><input type="radio" name="options" value="animation" checked>Animation</label>
46                <label style="margin-left:30px;"><input type="radio" name="options" value="camera">Camera Control</label>   
47             </div>
48             <br>
49             <h2>Set Parameters</h2>  
50             <div class="grid1">
51                <div class="item3">center</div>
52                <div class="item4">
53                   <input id="id-center" type="text" value="0, 0, 0" />
54                </div>
55             </div>
56             <div class="grid1">
57                <div class="item3">radius</div>
58                <div class="item4">
59                   <input id="id-radius" type="text" value="2" />
60                </div>
61             </div>
62             <div class="grid1">
63                <div class="item3">u</div>
64                <div class="item4">
65                   <input id="id-u" type="text" value="20" />
66                </div>
67             </div>
68             <div class="grid1">
69                <div class="item3">v</div>
70                <div class="item4">
71                   <input id="id-v" type="text" value="15" />
72                </div>
73             </div>
74             <br><button type="button" id="btn-redraw"><b>Redraw</b></button>
75          </div>
76          <div class="item2">
77             <canvas id="canvas-webgpu" width="640" height="480"></canvas>
78          </div>
79       </div>
80    </div>
81 
82    <script src="main.bundle.js"></script>
83 </body>
84 </html>

三、 至此你的所有代码完毕,最后展示出来应该是下图

posted @ 2022-02-12 16:30  支阿怪🔥  阅读(537)  评论(0编辑  收藏  举报