前言
我当前的WebGl案例因为各种原因,没有使用任何前端框架,直接干写的。
这也就导致了着色器写起来没有提示。
前几天我看见到一个朋友用vue3写webgl,挺好的。
所以就托他帮我写了这么一篇文章,他让我随意发,在此深表感谢。
一、环境准备
二、vscode 插件安装
Vue Volar extension Pack(支持vue项目的扩展包)
EditorConfig(在不同的编辑器和IDE之间保持编码风格和样式规范的工具)
WebGL GLSL Editor(WebGL GLSL着色器的语言支持)
三、项目初始化
-
执行 npm create vite@latest
注:若你的电脑是window,建议使用cmd终端安装,而不是Powershell;若运行卡顿,请使用cnpm
-
依次执行
- 输入项目名称
- 选择框架 Vue
- Customize with create-vue
- 余下选项 除cypress(我在这里安装总是会卡住) 全部选YES
- 运行项目
cd webgl-vue3
npm install
npm run lint
npm run dev
- 添加必要配置文件
- .editorconfig (配置EditorConfig)
root = true
# 匹配全部文件
[*]
charset = utf-8
# 缩进方式-配置此处就不用配置prettier了
indent_style = tab
tab_width = 4
# 每行多少个字符开始折行-配置此处就不用配置prettier了
max_line_length=120
end_of_line = lf
# 在文件结尾插入新行
insert_final_newline = true
# 删除一行中的前后空格
trim_trailing_whitespace = true
- .prettierrc.json (代码格式化工具配置)
{
"singleQuote": true, // 使用单引号
"semi": false, // 不使用分号
"htmlWhitespaceSensitivity": "ignore" // 指定 HTML 元素是在「内容内换行」还是在「属性与右尖括号内换行」
}
- env.d.ts(声明一下.vue、.vert、.frag 文件,否则 TS 编译器不认识)
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const componnet: DefineComponent<{}, {}, any>
export default componnet
}
declare module '*.vert' {
const value: string
export default value
}
declare module '*.frag' {
const value: string
export default value
}
- package.json 下添加 "type": "module"
四、安装插件
-
ant-design-vue (UI库)
-
vite-plugin-glsl(导入GLSL着色器文件的插件, 并在vite.config.ts中配置)
import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import glsl from 'vite-plugin-glsl' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue({ reactivityTransform: true, }), vueJsx(), glsl({ include: [ // Glob pattern, or array of glob patterns to import '**/*.glsl', '**/*.wgsl', '**/*.vert', '**/*.frag', '**/*.vs', '**/*.fs', ], exclude: undefined, // Glob pattern, or array of glob patterns to ignore warnDuplicatedImports: true, // Warn if the same chunk was imported multiple times defaultExtension: 'glsl', // Shader suffix when no extension is specified compress: false, // Compress output shader code root: '/', }), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, })
五、添加路由、菜单
- 注册 Antd
- ./src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
const app = createApp(App)
app.use(createPinia()).use(router).use(Antd).mount('#app')
- 封装菜单
- ./src/components/MenuItem.vue
<template>
<template v-for="route in routes">
<a-sub-menu v-if="isMenu(route)" :key="genkey(route.path)">
<template #title>
<span :title="route.name">{{ route.name }}</span>
</template>
<!-- 递归调用自己 -->
<MenuItem v-if="hasChildren(route.children)" :routes="route.children" :prefix="genkey(route.path)" />
</a-sub-menu>
<template v-else>
<RouterLink :to="genkey(route.path)" :key="genkey(route.path)" :title="route.name">
<a-menu-item :key="genkey(route.path)">
{{ route.name }}
</a-menu-item>
</RouterLink>
</template>
</template>
</template>
<script setup lang="ts">
import type { MenuType } from '@/router/index'
import { RouterLink } from 'vue-router'
import { toRefs, withDefaults, unref } from 'vue'
type PropsType = {
routes?: MenuType[]
prefix?: string
}
const props = withDefaults(defineProps<PropsType>(), {
routes: () => [],
prefix: '',
})
const { routes, prefix } = toRefs(props)
function genkey(path: string): string {
if (unref(prefix) === '') return path
return [unref(prefix), path].join('/')
}
function isMenu(route: MenuType): boolean {
return Array.isArray(route.children)
}
function hasChildren(children?: MenuType[]): boolean {
return Array.isArray(children) && children.length > 0
}
</script>
- ./src/components/MenuList.vue
<template>
<a-menu v-model:selectedKeys="selectedKeys" v-model:openKeys="openKeys" theme="dark" mode="inline">
<MenuItem :routes="routes" prefix="" />
</a-menu>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { routes } from '@/router/index'
import MenuItem from './MenuItem.vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const selectedKeys = ref<string[]>([])
const openKeys = ref<string[]>([])
function setMenuCurrent() {
const { matched } = route
openKeys.value = matched.slice(0, matched.length - 1).map((i) => i.path)
selectedKeys.value = [route.path]
}
onMounted(() => setMenuCurrent())
watch(() => route.path, setMenuCurrent)
</script>
- 修改页面布局
- ./src/App.vue
<template>
<a-layout style="min-height: 100vh">
<a-layout-sider class="AppSider">
<MenuList />
</a-layout-sider>
<a-layout>
<a-layout-content class="AppContainer">
<RouterView :key="$route.path" />
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script lang="ts" setup>
import { RouterView } from 'vue-router'
import MenuList from '@/components/MenuList.vue'
</script>
<style>
.AppContainer {
background-color: #000;
color: #fff;
}
</style>
- 修改路由
- ./src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { AsyncComponentLoader, Component } from 'vue'
export interface MenuType {
path: string
name?: string
component: AsyncComponentLoader<Component>
children?: MenuType[]
}
export const routes: MenuType[] = [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue'),
},
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes,
})
export default router
- 修改页面
- ./src/views/shader.frag
// 片元着色器
precision mediump float;
varying vec4 v_Color;
void main(){
gl_FragColor = v_Color;
}
-
./src/views/shader.vert
// 顶点着色器 attribute vec4 a_Position; attribute vec4 a_Color; varying vec4 v_Color; void main(){ gl_Position = a_Position; gl_PointSize = 10.0; v_Color = a_Color; }
-
./src/views/HomeView.vue
<template> <canvas ref="canvasRef" /> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' import vertShaderSource from './shader.vert' import fragShaderSource from './shader.frag' const canvasRef = ref<HTMLCanvasElement | null>(null) let canvas: HTMLCanvasElement let gl: WebGLRenderingContext let program: WebGLProgram //7个一组,前3个为顶点坐标,后4个为颜色坐标 // prettier-ignore let sources = new Float32Array([ 0, 0.2, 0, 1, 0, 1, 1, -0.2, -0.2, 0, 1, 1, 0, 1, 0.2, -0.2, 0, 0, 1, 1, 1 ]) const elementBytes = Float32Array.BYTES_PER_ELEMENT const vertSize = 3 const colorSize = 4 const categorySize = vertSize + colorSize const vertIndex = 0 // 顶点索引 const colorIndex = vertSize * elementBytes // 颜色索引 const sourceSize = sources.length / categorySize // 共有多少个顶点 // 创建shader function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader { const shader = gl.createShader(type) if (!shader) throw new Error('shader 创建失败') gl.shaderSource(shader, source) gl.compileShader(shader) return shader } // 创建proram function createProgram(gl: WebGLRenderingContext, vexShaderSource: string, fragShaderSource: string): WebGLProgram { const program = gl.createProgram() if (!program) throw new Error('program创建错误') const vexShader = createShader(gl, gl.VERTEX_SHADER, vexShaderSource) const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragShaderSource) gl.attachShader(program, vexShader) gl.attachShader(program, fragShader) gl.linkProgram(program) gl.useProgram(program) return program } // 渲染 function render(): void { gl = canvas.getContext('webgl') as WebGLRenderingContext program = createProgram(gl, vertShaderSource, fragShaderSource) gl.clearColor(0, 0, 0, 1) gl.clear(gl.COLOR_BUFFER_BIT) const buffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sources), gl.STATIC_DRAW) const a_Position = gl.getAttribLocation(program, 'a_Position') gl.vertexAttribPointer(a_Position, vertSize, gl.FLOAT, false, categorySize * elementBytes, vertIndex) gl.enableVertexAttribArray(a_Position) const a_Color = gl.getAttribLocation(program, 'a_Color') gl.vertexAttribPointer(a_Color, colorSize, gl.FLOAT, false, categorySize * elementBytes, colorIndex) gl.enableVertexAttribArray(a_Color) gl.drawArrays(gl.TRIANGLES, 0, sourceSize) } function init(): void { canvas = canvasRef.value as HTMLCanvasElement onWindowResize() render() } function onWindowResize() { const container = document.querySelector('.AppContainer') as HTMLElement const { width, height } = container.getBoundingClientRect() canvas.width = width canvas.height = height - 20 } onMounted(init) </script>
最终效果如下: