大白话react第十七章React 与 WebGL 项目进阶优化及拓展

大白话react第十七章React 与 WebGL 项目进阶优化及拓展

1. 引入物理引擎

在 React 和 WebGL 结合的项目里,加入物理引擎能让 3D 场景更真实,就像在现实世界里物体有重力、碰撞等效果一样。这里我们用 cannon-es 这个物理引擎库。

jsx 复制代码
// 引入 React 的 useEffect 和 useRef 钩子
import React, { useEffect, useRef } from'react';
// 引入 three.js 用于创建 3D 场景
import * as THREE from 'three';
// 引入 cannon-es 物理引擎库
import * as CANNON from 'cannon-es';

const PhysicsComponent = () => {
    // 创建一个 ref 用于引用存放 3D 场景的 DOM 元素
    const containerRef = useRef(null);

    useEffect(() => {
        // 创建 three.js 的场景
        const scene = new THREE.Scene();
        // 创建透视相机,设置视角、宽高比、近裁剪面和远裁剪面
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        // 创建 WebGL 渲染器
        const renderer = new THREE.WebGLRenderer();
        // 设置渲染器的大小为窗口大小
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 将渲染器的 DOM 元素添加到 ref 对应的 DOM 元素中
        containerRef.current.appendChild(renderer.domElement);

        // 创建 cannon-es 的物理世界
        const world = new CANNON.World();
        // 设置物理世界的重力,这里模拟向下的重力
        world.gravity.set(0, -9.82, 0);

        // 创建一个地面的物理材质
        const groundMaterial = new CANNON.Material('groundMaterial');
        // 创建地面的形状,这里是平面
        const groundShape = new CANNON.Plane();
        // 创建地面的刚体
        const groundBody = new CANNON.Body({
            mass: 0, // 质量为 0 表示静止物体
            shape: groundShape,
            material: groundMaterial
        });
        // 旋转地面使其水平
        groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
        // 将地面刚体添加到物理世界
        world.addBody(groundBody);

        // 创建一个立方体的物理材质
        const boxMaterial = new CANNON.Material('boxMaterial');
        // 创建立方体的形状
        const boxShape = new CANNON.Box(new CANNON.Vec3(1, 1, 1));
        // 创建立方体的刚体,设置质量和位置
        const boxBody = new CANNON.Body({
            mass: 1,
            position: new CANNON.Vec3(0, 10, 0),
            shape: boxShape,
            material: boxMaterial
        });
        // 将立方体刚体添加到物理世界
        world.addBody(boxBody);

        // 创建地面的 3D 模型
        const groundGeometry = new THREE.PlaneGeometry(10, 10);
        const groundMaterialThree = new THREE.MeshBasicMaterial({ color: 0x808080, side: THREE.DoubleSide });
        const groundMesh = new THREE.Mesh(groundGeometry, groundMaterialThree);
        groundMesh.rotation.x = -Math.PI / 2;
        scene.add(groundMesh);

        // 创建立方体的 3D 模型
        const boxGeometry = new THREE.BoxGeometry(2, 2, 2);
        const boxMaterialThree = new THREE.MeshBasicMaterial({ color: 0xff0000 });
        const boxMesh = new THREE.Mesh(boxGeometry, boxMaterialThree);
        scene.add(boxMesh);

        // 设置相机位置
        camera.position.z = 5;

        // 定义渲染函数
        const animate = () => {
            // 请求下一帧动画
            requestAnimationFrame(animate);
            // 更新物理世界
            world.step(1 / 60);
            // 将立方体刚体的位置和旋转同步到 3D 模型上
            boxMesh.position.copy(boxBody.position);
            boxMesh.quaternion.copy(boxBody.quaternion);
            // 渲染场景
            renderer.render(scene, camera);
        };

        // 开始动画循环
        animate();

        // 组件卸载时清理资源
        return () => {
            containerRef.current.removeChild(renderer.domElement);
        };
    }, []);

    return (
        // 创建一个 div 用于存放 3D 场景
        <div ref={containerRef} />
    );
};

export default PhysicsComponent;
2. 实现光照与阴影效果

光照和阴影能让 3D 场景更有立体感和真实感。在 three.js 里可以很方便地实现不同类型的光照和阴影效果。

jsx 复制代码
// 引入 React 的 useEffect 和 useRef 钩子
import React, { useEffect, useRef } from'react';
// 引入 three.js 用于创建 3D 场景
import * as THREE from 'three';

const LightingAndShadowsComponent = () => {
    // 创建一个 ref 用于引用存放 3D 场景的 DOM 元素
    const containerRef = useRef(null);

    useEffect(() => {
        // 创建 three.js 的场景
        const scene = new THREE.Scene();
        // 创建透视相机,设置视角、宽高比、近裁剪面和远裁剪面
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        // 创建 WebGL 渲染器
        const renderer = new THREE.WebGLRenderer();
        // 设置渲染器的大小为窗口大小
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 开启渲染器的阴影支持
        renderer.shadowMap.enabled = true;
        // 将渲染器的 DOM 元素添加到 ref 对应的 DOM 元素中
        containerRef.current.appendChild(renderer.domElement);

        // 创建一个平面几何体
        const planeGeometry = new THREE.PlaneGeometry(10, 10);
        // 创建平面的材质
        const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
        // 创建平面的 3D 模型
        const plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.rotation.x = -Math.PI / 2;
        // 设置平面接收阴影
        plane.receiveShadow = true;
        scene.add(plane);

        // 创建一个立方体几何体
        const cubeGeometry = new THREE.BoxGeometry(2, 2, 2);
        // 创建立方体的材质
        const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
        // 创建立方体的 3D 模型
        const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.position.y = 1;
        // 设置立方体投射阴影
        cube.castShadow = true;
        scene.add(cube);

        // 创建一个点光源,设置颜色和强度
        const pointLight = new THREE.PointLight(0xffffff, 1);
        pointLight.position.set(2, 5, 2);
        // 开启点光源的阴影投射
        pointLight.castShadow = true;
        scene.add(pointLight);

        // 设置相机位置
        camera.position.z = 5;

        // 定义渲染函数
        const animate = () => {
            // 请求下一帧动画
            requestAnimationFrame(animate);
            // 旋转立方体
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;
            // 渲染场景
            renderer.render(scene, camera);
        };

        // 开始动画循环
        animate();

        // 组件卸载时清理资源
        return () => {
            containerRef.current.removeChild(renderer.domElement);
        };
    }, []);

    return (
        // 创建一个 div 用于存放 3D 场景
        <div ref={containerRef} />
    );
};

export default LightingAndShadowsComponent;
3. 支持用户交互

让用户可以和 3D 场景进行交互,比如点击、拖动等操作,能提升用户体验。这里我们用 three.jsRaycaster 来实现点击检测。

jsx 复制代码
// 引入 React 的 useEffect、useRef 和 useState 钩子
import React, { useEffect, useRef, useState } from'react';
// 引入 three.js 用于创建 3D 场景
import * as THREE from 'three';

const InteractionComponent = () => {
    // 创建一个 ref 用于引用存放 3D 场景的 DOM 元素
    const containerRef = useRef(null);
    // 创建一个状态来存储点击的物体
    const [clickedObject, setClickedObject] = useState(null);

    useEffect(() => {
        // 创建 three.js 的场景
        const scene = new THREE.Scene();
        // 创建透视相机,设置视角、宽高比、近裁剪面和远裁剪面
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        // 创建 WebGL 渲染器
        const renderer = new THREE.WebGLRenderer();
        // 设置渲染器的大小为窗口大小
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 将渲染器的 DOM 元素添加到 ref 对应的 DOM 元素中
        containerRef.current.appendChild(renderer.domElement);

        // 创建一个立方体几何体
        const cubeGeometry = new THREE.BoxGeometry(2, 2, 2);
        // 创建立方体的材质
        const cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
        // 创建立方体的 3D 模型
        const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        scene.add(cube);

        // 设置相机位置
        camera.position.z = 5;

        // 创建一个射线投射器,用于检测点击
        const raycaster = new THREE.Raycaster();
        // 创建一个二维向量,用于存储鼠标位置
        const mouse = new THREE.Vector2();

        // 定义鼠标点击事件处理函数
        const onMouseClick = (event) => {
            // 计算鼠标在标准化设备坐标中的位置
            mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
            // 通过鼠标位置更新射线投射器
            raycaster.setFromCamera(mouse, camera);
            // 获取射线与场景中物体的交点
            const intersects = raycaster.intersectObjects(scene.children);
            if (intersects.length > 0) {
                // 如果有交点,设置点击的物体状态
                setClickedObject(intersects[0].object);
            } else {
                // 没有交点则清空点击的物体状态
                setClickedObject(null);
            }
        };

        // 监听鼠标点击事件
        window.addEventListener('click', onMouseClick);

        // 定义渲染函数
        const animate = () => {
            // 请求下一帧动画
            requestAnimationFrame(animate);
            // 渲染场景
            renderer.render(scene, camera);
        };

        // 开始动画循环
        animate();

        // 组件卸载时清理资源
        return () => {
            containerRef.current.removeChild(renderer.domElement);
            // 移除鼠标点击事件监听
            window.removeEventListener('click', onMouseClick);
        };
    }, []);

    return (
        <div>
            {/* 创建一个 div 用于存放 3D 场景 */}
            <div ref={containerRef} />
            {clickedObject && (
                // 如果有点击的物体,显示提示信息
                <p>你点击了一个物体!</p>
            )}
        </div>
    );
};

export default InteractionComponent;

通过这些进阶的优化和拓展,你的 React 与 WebGL 项目会变得更加丰富、真实和有趣,用户体验也会大大提升。

如何优化WebGL的渲染性能?

优化 WebGL 渲染性能的方法

1. 减少绘制调用次数

在 WebGL 里,每次绘制调用都需要一些额外的开销。如果能把多次绘制合并成一次,就能减少这些开销,提高性能。就好像你去超市买东西,一次多买点,少跑几趟,效率就高了。

javascript 复制代码
// 初始化 WebGL 上下文
function initWebGL(canvas) {
    try {
        // 尝试获取 WebGL 上下文
        return canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    } catch (e) {
        // 如果获取失败,输出错误信息
        console.error("无法获取 WebGL 上下文", e);
        return null;
    }
}

// 合并几何体的函数
function mergeGeometries(geometries) {
    let positions = [];
    let indices = [];
    let indexOffset = 0;

    // 遍历所有几何体
    geometries.forEach(geometry => {
        // 将当前几何体的顶点位置添加到总的顶点位置数组中
        positions = positions.concat(geometry.positions);
        // 将当前几何体的索引添加到总的索引数组中,并根据偏移量调整索引值
        indices = indices.concat(geometry.indices.map(index => index + indexOffset));
        // 更新索引偏移量
        indexOffset += geometry.positions.length / 3;
    });

    return {
        positions: positions,
        indices: indices
    };
}

// 创建一个简单的几何体
function createGeometry() {
    // 定义顶点位置
    const positions = [
        -0.5, -0.5, 0,
        0.5, -0.5, 0,
        0.5, 0.5, 0,
        -0.5, 0.5, 0
    ];
    // 定义索引
    const indices = [
        0, 1, 2,
        2, 3, 0
    ];

    return {
        positions: positions,
        indices: indices
    };
}

// 主函数
function main() {
    // 获取 canvas 元素
    const canvas = document.getElementById("glCanvas");
    // 初始化 WebGL 上下文
    const gl = initWebGL(canvas);

    if (!gl) {
        // 如果上下文获取失败,输出错误信息并返回
        console.error("无法初始化 WebGL");
        return;
    }

    // 创建两个几何体
    const geometry1 = createGeometry();
    const geometry2 = createGeometry();
    // 合并这两个几何体
    const mergedGeometry = mergeGeometries([geometry1, geometry2]);

    // 创建顶点缓冲区
    const positionBuffer = gl.createBuffer();
    // 绑定顶点缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    // 将合并后的顶点位置数据写入缓冲区
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mergedGeometry.positions), gl.STATIC_DRAW);

    // 创建索引缓冲区
    const indexBuffer = gl.createBuffer();
    // 绑定索引缓冲区
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    // 将合并后的索引数据写入缓冲区
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(mergedGeometry.indices), gl.STATIC_DRAW);

    // 顶点着色器代码
    const vertexShaderSource = `
        attribute vec3 a_position;
        void main() {
            gl_Position = vec4(a_position, 1.0);
        }
    `;
    // 片段着色器代码
    const fragmentShaderSource = `
        precision mediump float;
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `;

    // 创建顶点着色器对象
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    // 将顶点着色器代码放入着色器对象
    gl.shaderSource(vertexShader, vertexShaderSource);
    // 编译顶点着色器
    gl.compileShader(vertexShader);

    // 创建片段着色器对象
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    // 将片段着色器代码放入着色器对象
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    // 编译片段着色器
    gl.compileShader(fragmentShader);

    // 创建着色器程序对象
    const shaderProgram = gl.createProgram();
    // 将顶点着色器附加到着色器程序
    gl.attachShader(shaderProgram, vertexShader);
    // 将片段着色器附加到着色器程序
    gl.attachShader(shaderProgram, fragmentShader);
    // 链接着色器程序
    gl.linkProgram(shaderProgram);
    // 使用着色器程序
    gl.useProgram(shaderProgram);

    // 获取顶点属性的位置
    const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "a_position");
    // 启用顶点属性
    gl.enableVertexAttribArray(positionAttributeLocation);
    // 绑定顶点缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    // 设置顶点属性指针
    gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);

    // 设置视口大小
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    // 清空颜色缓冲区
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 绘制合并后的几何体
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.drawElements(gl.TRIANGLES, mergedGeometry.indices.length, gl.UNSIGNED_SHORT, 0);
}

// 调用主函数
main();
2. 优化纹理使用

纹理是影响性能的一个重要因素。大尺寸的纹理会占用很多内存,加载和处理起来也慢。所以要尽量压缩纹理,并且按需加载。就像你搬家,东西能压缩就压缩,需要用的时候再拿出来。

javascript 复制代码
// 初始化 WebGL 上下文
function initWebGL(canvas) {
    try {
        // 尝试获取 WebGL 上下文
        return canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    } catch (e) {
        // 如果获取失败,输出错误信息
        console.error("无法获取 WebGL 上下文", e);
        return null;
    }
}

// 加载纹理的函数
function loadTexture(gl, url) {
    // 创建纹理对象
    const texture = gl.createTexture();
    // 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // 设置纹理的初始参数
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
        new Uint8Array([0, 0, 255, 255]));

    // 创建图像对象
    const image = new Image();
    image.onload = function () {
        // 图像加载完成后,绑定纹理对象
        gl.bindTexture(gl.TEXTURE_2D, texture);
        // 将图像数据上传到纹理
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

        // 检查图像尺寸是否是 2 的幂次方
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
            // 如果是 2 的幂次方,生成多级纹理
            gl.generateMipmap(gl.TEXTURE_2D);
        } else {
            // 如果不是 2 的幂次方,设置纹理过滤模式
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        }
    };
    // 设置图像的源地址
    image.src = url;

    return texture;
}

// 判断一个数是否是 2 的幂次方
function isPowerOf2(value) {
    return (value & (value - 1)) === 0;
}

// 主函数
function main() {
    // 获取 canvas 元素
    const canvas = document.getElementById("glCanvas");
    // 初始化 WebGL 上下文
    const gl = initWebGL(canvas);

    if (!gl) {
        // 如果上下文获取失败,输出错误信息并返回
        console.error("无法初始化 WebGL");
        return;
    }

    // 加载纹理
    const texture = loadTexture(gl, "compressed_texture.jpg");

    // 顶点着色器代码
    const vertexShaderSource = `
        attribute vec3 a_position;
        attribute vec2 a_texCoord;
        varying vec2 v_texCoord;
        void main() {
            gl_Position = vec4(a_position, 1.0);
            v_texCoord = a_texCoord;
        }
    `;
    // 片段着色器代码
    const fragmentShaderSource = `
        precision mediump float;
        uniform sampler2D u_texture;
        varying vec2 v_texCoord;
        void main() {
            gl_FragColor = texture2D(u_texture, v_texCoord);
        }
    `;

    // 创建顶点着色器对象
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    // 将顶点着色器代码放入着色器对象
    gl.shaderSource(vertexShader, vertexShaderSource);
    // 编译顶点着色器
    gl.compileShader(vertexShader);

    // 创建片段着色器对象
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    // 将片段着色器代码放入着色器对象
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    // 编译片段着色器
    gl.compileShader(fragmentShader);

    // 创建着色器程序对象
    const shaderProgram = gl.createProgram();
    // 将顶点着色器附加到着色器程序
    gl.attachShader(shaderProgram, vertexShader);
    // 将片段着色器附加到着色器程序
    gl.attachShader(shaderProgram, fragmentShader);
    // 链接着色器程序
    gl.linkProgram(shaderProgram);
    // 使用着色器程序
    gl.useProgram(shaderProgram);

    // 顶点位置数据
    const positions = [
        -0.5, -0.5, 0,
        0.5, -0.5, 0,
        0.5, 0.5, 0,
        -0.5, 0.5, 0
    ];
    // 纹理坐标数据
    const texCoords = [
        0, 0,
        1, 0,
        1, 1,
        0, 1
    ];
    // 索引数据
    const indices = [
        0, 1, 2,
        2, 3, 0
    ];

    // 创建顶点缓冲区
    const positionBuffer = gl.createBuffer();
    // 绑定顶点缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    // 将顶点位置数据写入缓冲区
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

    // 获取顶点属性的位置
    const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "a_position");
    // 启用顶点属性
    gl.enableVertexAttribArray(positionAttributeLocation);
    // 设置顶点属性指针
    gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);

    // 创建纹理坐标缓冲区
    const texCoordBuffer = gl.createBuffer();
    // 绑定纹理坐标缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    // 将纹理坐标数据写入缓冲区
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);

    // 获取纹理坐标属性的位置
    const texCoordAttributeLocation = gl.getAttribLocation(shaderProgram, "a_texCoord");
    // 启用纹理坐标属性
    gl.enableVertexAttribArray(texCoordAttributeLocation);
    // 设置纹理坐标属性指针
    gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 0, 0);

    // 创建索引缓冲区
    const indexBuffer = gl.createBuffer();
    // 绑定索引缓冲区
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    // 将索引数据写入缓冲区
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

    // 获取纹理统一变量的位置
    const textureUniformLocation = gl.getUniformLocation(shaderProgram, "u_texture");
    // 激活纹理单元 0
    gl.activeTexture(gl.TEXTURE0);
    // 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);
    // 设置纹理统一变量的值
    gl.uniform1i(textureUniformLocation, 0);

    // 设置视口大小
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    // 清空颜色缓冲区
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 绘制几何体
    gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}

// 调用主函数
main();
3. 控制渲染循环

渲染循环就是不停地更新和绘制场景。如果帧率太高,会浪费性能;如果场景没变化,还一直渲染也没必要。所以要合理控制渲染循环,就像你跑步,不需要一直全速跑,该慢就慢,该停就停。

javascript 复制代码
// 初始化 WebGL 上下文
function initWebGL(canvas) {
    try {
        // 尝试获取 WebGL 上下文
        return canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    } catch (e) {
        // 如果获取失败,输出错误信息
        console.error("无法获取 WebGL 上下文", e);
        return null;
    }
}

// 主函数
function main() {
    // 获取 canvas 元素
    const canvas = document.getElementById("glCanvas");
    // 初始化 WebGL 上下文
    const gl = initWebGL(canvas);

    if (!gl) {
        // 如果上下文获取失败,输出错误信息并返回
        console.error("无法初始化 WebGL");
        return;
    }

    // 顶点着色器代码
    const vertexShaderSource = `
        attribute vec3 a_position;
        void main() {
            gl_Position = vec4(a_position, 1.0);
        }
    `;
    // 片段着色器代码
    const fragmentShaderSource = `
        precision mediump float;
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `;

    // 创建顶点着色器对象
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    // 将顶点着色器代码放入着色器对象
    gl.shaderSource(vertexShader, vertexShaderSource);
    // 编译顶点着色器
    gl.compileShader(vertexShader);

    // 创建片段着色器对象
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    // 将片段着色器代码放入着色器对象
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    // 编译片段着色器
    gl.compileShader(fragmentShader);

    // 创建着色器程序对象
    const shaderProgram = gl.createProgram();
    // 将顶点着色器附加到着色器程序
    gl.attachShader(shaderProgram, vertexShader);
    // 将片段着色器附加到着色器程序
    gl.attachShader(shaderProgram, fragmentShader);
    // 链接着色器程序
    gl.linkProgram(shaderProgram);
    // 使用着色器程序
    gl.useProgram(shaderProgram);

    // 顶点位置数据
    const positions = [
        -0.5, -0.5, 0,
        0.5, -0.5, 0,
        0.5, 0.5, 0,
        -0.5, 0.5, 0
    ];
    // 索引数据
    const indices = [
        0, 1, 2,
        2, 3, 0
    ];

    // 创建顶点缓冲区
    const positionBuffer = gl.createBuffer();
    // 绑定顶点缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    // 将顶点位置数据写入缓冲区
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

    // 获取顶点属性的位置
    const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "a_position");
    // 启用顶点属性
    gl.enableVertexAttribArray(positionAttributeLocation);
    // 设置顶点属性指针
    gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);

    // 创建索引缓冲区
    const indexBuffer = gl.createBuffer();
    // 绑定
相关推荐
炫饭第一名10 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
符方昊10 小时前
React 19 对比 React 16 新特性解析
前端·react.js
不会敲代码111 小时前
前端组件化样式隔离实战:React CSS Modules、styled-components 与 Vue scoped 对比
css·vue.js·react.js
进击的尘埃11 小时前
Vue3 响应式原理:从 Proxy 到依赖收集,手撸一个迷你 reactivity
javascript
willow12 小时前
JavaScript数据类型整理1
javascript
LeeYaMaster12 小时前
20个例子掌握RxJS——第十一章实现 WebSocket 消息节流
javascript·angular.js
UIUV12 小时前
RAG技术学习笔记(含实操解析)
javascript·langchain·llm
叶智辽12 小时前
【Three.js与WebGPU】下一代3D技术到底强在哪?
webgl·three.js
阿虎儿13 小时前
React Hook 入门指南
前端·react.js
阿虎儿14 小时前
React Context 详解:从入门到性能优化
前端·vue.js·react.js