学习笔记 | Orillusion-WebGPU小白入门(二)

(二)绘制三角形

/src/basicTriangle.ts

WebGPU架构&原理

学习笔记 | Orillusion-WebGPU小白入门(二)_第1张图片

学习笔记 | Orillusion-WebGPU小白入门(二)_第2张图片

学习笔记 | Orillusion-WebGPU小白入门(二)_第3张图片

初始化Device:获取Adapter和Device对象

在Html页面中添加Canvas画布,用CSS占满全屏

<head>
    <style>
        canvas {
            width:100%;
            height:100%;
        }
    style>
head>
<body>
    <canvas>canvas>
    <script> type="module" scr="/src/basicTriangle.ts"script>
body>
async function initwebgpu(){
    if(!navigator.gpu)
        throw new Error()
    // 请求一个GPUAdapter对象(浏览器对WebGPU的抽象代理,不能直接操作),返回promise【异步API】
    const adapter = await navigator.gpu.requestAdapter({
    // 设置的是一个期望值,更多是一个flag,一般可以不设置或者暴力设置为高性能
    powerPreference:'high-performance'
    })
    // 请求一个具体的、可操作的逻辑实例【API】
    if(!adapter)
        throw new Error() //避免device为空
    const device = await adapter.requestDevice({
    	requiredFeatures:['text'],
    	requiredLimits:{
		maxStorageBufferBindingSize=adapter.limits.maxStorageBufferBindingSize
    	}
    })
    
    /**
     * 可以通过Adapter读取浏览器对WebGPU实现了哪些功能和参数,但不能直接用它进行计算和绘制图形
     * 打印adapter,device的具体参数
     * consle.log(adapter,device)
     * adapter主要包含两个可读的对象:features、limits
     * limits:当前Web浏览器对WebGPU支持的一些参数的最大值
     * features:当前浏览器实现了哪些扩展功能
     * 遍历查看当前浏览器的扩展功能
     * adapter.features.forEach(v=>{
     	console.log(v)
     })
     * device和adapter的limits参数可能不一样,可在requestDevice()申请拓展功能和修改参数
     */
   
    //获得canvas对象,但是不能直接画图,它只是一个HTML的DOM节点
    const canvas = document.querySelector('canvas')
    //获取可以直接通过JS操作的逻辑画布
    if(!canvas)
        throw new Errow()
    const context = canvas.getContext('webgpu')

    
    //利用configure API配置画布,至少需要device和format参数
    //用getPreferredFormat API得到浏览器默认的颜色选项
    if(!context)
        throw new Errow()
    const format = context.getPreferredFormat(adapter)
    const size = [canvas.clientWidth * window.devicePixelRatio, canvas.clientHeight * window.devicePixelRatio]
    context?.configure({
        device,format,size,
        compositingAlphaMode:'opaque'
    })
    
    /**
     * 打印浏览器默认格式
     * bgra8unorm:以0-1小数表示的BGRA格式,支持大部分浏览器
     */
	console.log(format)
	
	// json在JS的简写形式:如果变量名和参数名一样的情况下(adapter:adapter),可以省略冒号后面的部分,以此简化代码,提高可读性
	return(adapter,device,context,format)
     
     
}

// initwebgpu() 测试时使用

AsyncFunction

AsyncFunction 构造函数用来创建新的 异步函数 对象,JavaScript 中每个异步函数都是 AsyncFunction 的对象。

注意,AsyncFunction 并不是一个全局对象,需要通过下面的方法来获取:

Object.getPrototypeOf(async function(){}).constructor

async 函数

async 函数是使用async关键字声明的函数。 async 函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function

GPU图形结构

学习笔记 | Orillusion-WebGPU小白入门(二)_第4张图片

点→线→三角面→多边形

学习笔记 | Orillusion-WebGPU小白入门(二)_第5张图片

开发者需要操作且只能控制的阶段只有:Vertx Shader和Fragment Shader

创建渲染管线

管线的本质:依次运行一些程序(Shader)

// 通过import把.wgsl当做单独的模块引用进来
import vertex from './shaders/triangle.vert.wgsl?raw'
// import默认无法引用纯字符串,必须是一种js或typescript文件,有export的对象才可以,否则ts无法解析这句话
// 利用vite语法环境特性,在后面加上?raw,就可以把纯文本直接引用并赋值给vert
// 在url后面加?raw既不是import标准语法,也不是typescript的语法,而是因为我们在tsconfig.json中引入了@vite/client这个定义包,这是vite加载字符串语法
import frag from './shaders/red.frag.wgsl?raw'

async function initwebgpu(){- //接上文初始化
}

async function initpipeline(device:GPUDevice,format:GPUTextrueFormat){ //typescript语法:需要声明device类型,否则没有提示
    
    // 根据index的不同输出三个不同点的信息
	const vertxShader = device.createShaderModule({
    // WGSL代码。WGSL是W3C为WebGpu制定的一套全新的Shading Language,和目前主流的Shader并没有实质区别
    // 一般不推荐直接把代码用字符串的形式写在函数里,可读性差也不便管理
        code:'vertex'
    }) 
    
    // 输出颜色
    const fragmentShader = device.createShaderModule({
        code:'frag'
    }) 
    
    // 创建并可返回pipeline的管线,有两种形式(同步、异步),在WebGPU推荐有异步用异步,尽量避免JS形成的挂起等待
    const pipeline = await device.createRenderPipelineAsync({
        vertex:{
            module:vertxShader,
            entryPoint:'main' //Shader的入口函数
        },
        fragment:{
            module:fragmentShader,
            entryPoint:'main',
            targets:[{
                format
            }] //表明输出的颜色格式,并且与画布设置的格式匹配
        }
        //绘图方式
        primitive:{
        	topology:'triangle-list'
    	}
    })
    return {pipeline}
}

async function run(){
	const {device,format} = await initwebgpu(),
    const {pipeline} = await initpipeline(device,format)
} 

录制渲染通道

function draw(device:GPUDevice,pipeline:GPURenderPipeline,context:GPUCanvasContext){
   //WebGPU的commandEncoder机制:把所有命令提前写入encoder当中,再进行一次性的提交给Native运行
   //创建Encoder对象(不是异步API,本质上没有跟WebGPU做复杂的交互)
    const encoder = device.createCommandEncoder()
    
    //..命令录制
    
    //beginRenderpass:类似于绘制图层,常用于特效、阴影的绘制。与encoder类似,返回一个Renderpass的object
    //Renderpass的结果会对应到相应的Canvas上
    const renderPass = encoder.beginRenderpass({
        
        //...Renderpass命令录制
        
        //清空画布,设置背景色
        //颜色附件:如何处理颜色信息
        colorAttachments:[{
            
            //view:设置通道输出在哪显示
            //createview:获取一个可以被GPU操作的view buffer
            view:context.getCurrentTexture().createView(),
 			
            //loadOp:在绘制前是否加载当前view的内容
            //clear:清空内容然后再绘制,相当于添加一个背景颜色or清空画布
            //load:再原有内容上接着绘制
            loadOp:'clear',
            
            //设置背景色(绘制前对view的操作)
            clearValue:{r:0,g:0,b:0,a:1}, //黑色
            //(绘制后对view的操作)
            //discard:丢弃结果,store:保留结果
            storeOp:'store'
        }]
    })
    	//加载pipeline(管线)
    	renderPass.setPipeline(pipeline)
    	
    	/* 实际场景可能设计多个pipeline和多个物体
    	 * 可以通过for循环遍历每个物体,动态设置每个物体对应的pipeline和draw()它们对应的顶点数
    	 * for(let i = 0; i< 10000; i++)
    	 *		renderPass.draw(4)
    	 */
    	
    	//运行管线
    	//用多少个线程去运行vertexShader,一般画什么图形的对应顶点数量
    	renderPass.draw(3)
    		
    	/* pipeline中的primitive的topology参数(vertex输出的顶点信息该怎么组合)
    	 * triangle-list:三个点形成三角面,四边形则需要6个点,draw(6)
    	 * point-list:将每一个点当做独立的点进行输出
    	 * line-list:每两个点组成一条线,1&2、3&4
    	 * line-strip:首尾连接的组合模式,1&2、2@3
    	 * triangle-strip:首尾连接的组合模式,1&2&3、234、345,四个点就可以绘制出四边形,两个三角面共用一条边,draw的参数也可以为5甚至是4,但是对顶点顺序要求很高,而且需要对其优化
    	 */
    
    //结束这个通道的Renderpass录制工作
    renderPass.end()
    
    //结束录制,返回buffer
    const buffer = encoder.finish()
    //将buffer传递给Dawn,Dawn在浏览器里操作GPU
    device.queue.submit([buffer])
    //submit之前,命令录制里的所有命令都没有执行、Dawn和GPU都没有真正工作,此时js线程是空闲的
    //submit也不是异步的API,但整个绘制过程相对于JS而言是一个完全独立的异步过程,这是与WebGL的同步操作的本质区别,也是WebGPU的核心特性
}

//上节课的
async function run(){
	const {device,format,context} = await initwebgpu()
    const {pipeline} = await initpipeline(device,format)
//这节课的
    draw(device,pipeline,context)
} 

学习笔记 | Orillusion-WebGPU小白入门(二)_第6张图片

你可能感兴趣的:(WebGPU学习笔记,学习,javascript,前端)