webgl_clipping_stencil

news2024/10/6 16:22:48

ThreeJS 官方案例学习(webgl_clipping_stencil)

1.效果图

在这里插入图片描述

2.源码

<template>
	<div>
		<div id="container"></div>
	</div>
</template>
<script>
import * as THREE from 'three';
// 导入控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// 引入房间环境,创建一个室内环境
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
// 导入性能监视器
import Stats from 'three/examples/jsm/libs/stats.module.js';
// 导入gltf载入库、模型加载器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
// 引入模型解压器
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
//GUI界面
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import gsap from 'gsap';
export default {
	data() {
		return {
			container: null, //界面需要渲染的容器
			scene: null,	// 场景对象
			camera: null, //相机对象
			renderer: null, //渲染器对象
			controller: null,	// 相机控件对象
			stats: null,// 性能监听器
			model: null,//导入的模型
			clock: new THREE.Clock(),// 创建一个clock对象,用于跟踪时间
			params: {//gui参数
				animate: true,
				planeX: {
					constant: 0,//从原点到平面的有符号距离
					negated: false,//更改剪裁平面的行为,以便仅剪切其交叉点,而不是它们的并集。默认值为 false。
					displayHelper: false,//剪切平面helper隐藏显现
				},
				planeY: {
					constant: 0,
					negated: false,
					displayHelper: false,
				},
				planeZ: {
					constant: 0,
					negated: false,
					displayHelper: false,
				},
			},
			planeHelpers: null,// 剪切平面helpers
			planeObjects: [],//剪切平面对象
			planes: [//剪裁平面集合
				new THREE.Plane(new THREE.Vector3(- 1, 0, 0), 0),
				new THREE.Plane(new THREE.Vector3(0, - 1, 0), 0),
				new THREE.Plane(new THREE.Vector3(0, 0, - 1), 0),
			],
		};
	},
	mounted() {
		this.init()
		this.animate()  //如果引入了模型并存在动画,可在模型引入成功后加载动画
		window.addEventListener("resize", this.onWindowSize)
	},
	beforeUnmount() {
		console.log('beforeUnmount===============');
		// 组件销毁时置空
		this.container = null
		this.scene = null
		this.camera = null
		this.renderer = null
		this.controller = null
		this.stats = null
		this.object = null//物体实列对象
	},
	methods: {
		/**
		* @description 初始化
		 */
		init() {
			this.container = document.getElementById('container')
			this.startTime = Date.now();
			this.setScene()
			this.setCamera()
			this.setRenderer()
			this.setController()
			this.addHelper()
			// this.setPMREMGenerator()
			this.setLight()
			this.setMesh()
			this.addStatus()
		},
		/**
		 * @description 创建场景
		 */
		setScene() {
			// 创建场景对象Scene
			this.scene = new THREE.Scene()
			// 设置场景背景
			// this.scene.background = new THREE.Color(0xbfe3dd);
		},
		/**
		 * @description 创建相机
		*/
		setCamera() {
			// 第二参数就是 长度和宽度比 默认采用浏览器  返回以像素为单位的窗口的内部宽度和高度
			this.camera = new THREE.PerspectiveCamera(36, this.container.clientWidth / this.container.clientHeight, 1, 100)
			// 设置相机位置
			this.camera.position.set(2, 2, 2)
			// 设置摄像头宽高比例
			this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
			// 设置摄像头投影矩阵
			this.camera.updateProjectionMatrix();
			// 设置相机视线方向
			this.camera.lookAt(new THREE.Vector3(0, 0, 0))// 0, 0, 0 this.scene.position
			// 将相机加入场景
			this.scene.add(this.camera)
		},
		/**
		 * @description 创建渲染器
		 */
		setRenderer() {
			// 初始化渲染器
			this.renderer = new THREE.WebGLRenderer({
				antialias: true,// 设置抗锯齿
				logarithmicDepthBuffer: true,  // 是否使用对数深度缓存
			})
			// 设置渲染器宽高
			this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
			// 设置渲染器的像素比
			this.renderer.setPixelRatio(window.devicePixelRatio);
			// 允许在场景中使用阴影贴图
			this.renderer.shadowMap.enabled = true;
			// 定义渲染器是否考虑对象级剪切平面
			this.renderer.localClippingEnabled = true;
			// 是否需要对对象排序
			// !!!!!! 设置剖切面是取消设置
			// this.renderer.sortObjects = false;
			// 设置颜色及其透明度
			this.renderer.setClearColor(0x263238);
			// 将渲染器添加到页面
			this.container.appendChild(this.renderer.domElement);

		},
		/**
		 * @description 添加创建控制器
		 */
		setController() {
			this.controller = new OrbitControls(this.camera, this.renderer.domElement);
			// 控制缩放范围
			this.controller.minDistance = 2;
			this.controller.maxDistance = 20;
			//是否开启右键拖拽
			// this.controller.enablePan = false;
			// 阻尼(惯性)
			this.controller.enableDamping = true; //启用阻尼(惯性)
			this.controller.dampingFactor = 0.04; //阻尼惯性有多大
			// this.controller.autoRotate = true; //自动围绕目标旋转
			// 由于设置了控制器,因此只能改变控制器的target以改变相机的lookAt方向
			// this.controller.target.set(0, 1, 0); //控制器的焦点
		},
		/**
		* @description 添加创建模型
		*/
		setMesh() {
			// 创建物体

			// 创建一个圆环扭结
			const geometry = new THREE.TorusKnotGeometry(0.4, 0.15, 220, 60);
			this.object = new THREE.Group();
			this.scene.add(this.object);

			// 设置剪辑平面渲染
			const planeGeom = new THREE.PlaneGeometry(4, 4);
			// 存在三个剪切平面,需要设置三个剖切面(有问题)
			for (var i = 0; i < 3; i++) {
				const poGroup = new THREE.Group();
				const plane = this.planes[i];
				// 绘制剖切面
				const stencilGroup = this.createPlaneStencilGroup(geometry, plane, i + 1);

				// 平面被其他裁剪平面裁剪 - 剖切面
				const planeMat = new THREE.MeshStandardMaterial({
					color: 0xe91e63,
					metalness: 0.1,
					roughness: 0.75,
					clippingPlanes: this.planes.filter((p) => p !== plane),

					stencilWrite: true,
					stencilRef: 0,
					stencilFunc: THREE.NotEqualStencilFunc,
					stencilFail: THREE.ReplaceStencilOp,
					stencilZFail: THREE.ReplaceStencilOp,
					stencilZPass: THREE.ReplaceStencilOp,
				});
				const po = new THREE.Mesh(planeGeom, planeMat);
				//通过renderOrder设置渲染顺序,保证 物体_背面 和 物体_正面 先绘制,之后在绘制剖切面。如果剖切面存在多个在绘制一个之后需要清除模板缓冲区后,再绘制下一个
				//物体渲染之后直接执行 - 清除模板缓冲区
				po.onAfterRender = (renderer) => {
					renderer.clearStencil();
				};
				po.renderOrder = i + 1.2;

				this.object.add(stencilGroup);
				poGroup.add(po);
				this.planeObjects.push(po);
				this.scene.add(poGroup);
			}


			// 添加颜色

			const material = new THREE.MeshStandardMaterial({
				color: 0xffc107,
				metalness: 0.1,// 金属度(光反射的光泽程度,1 是最高)
				roughness: 0.75,// 粗糙度设置(0 光滑, 1 粗糙)
				side: THREE.DoubleSide,//渲染哪一面,双面
				// ***** Clipping setup (material): *****
				clippingPlanes: this.planes,//定义剪裁平面,需要WebGLRenderer.localClippingEnabled为true。
				clipShadows: true,//是否根据此材质上指定的剪裁平面剪切阴影
			})
			const clippedColorFront = new THREE.Mesh(geometry, material);
			clippedColorFront.castShadow = true;// 对象是否被渲染到阴影贴图中
			clippedColorFront.renderOrder = 16;
			this.object.add(clippedColorFront);


			// 创建地面
			const ground = new THREE.Mesh(
				new THREE.PlaneGeometry(9, 9, 1, 1),
				new THREE.ShadowMaterial({//阴影材质,此材质可以接收阴影,但在其他方面完全透明
					color: 0x000000,
					opacity: 0.25,
					side: THREE.DoubleSide,
				})
			);

			ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
			ground.position.y = -1;
			ground.receiveShadow = true;
			this.scene.add(ground);


			// ***** Clipping setup (renderer): *****
			// GUI
			const gui = new GUI()
			gui.add(this.params, "animate");

			// planeX - gui设置
			const planeX = gui.addFolder("planeX");
			planeX.add(this.params.planeX, 'displayHelper').onChange((v) => (this.planeHelpers[0].visible = v));
			planeX.add(this.params.planeX, "constant", -1, 1).step(0.01).onChange((d) => (this.planes[0].constant = d));
			planeX.add(this.params.planeX, "negated").onChange(() => {
				this.planes[0].negate();//将法向量与常量求反(乘以-1)
				this.params.planeX.constant = this.planes[0].constant;
			});
			planeX.open();//打开面板

			// planeY - gui设置
			const planeY = gui.addFolder("planeY");
			planeY.add(this.params.planeY, "displayHelper").onChange((v) => (this.planeHelpers[1].visible = v));
			planeY.add(this.params.planeY, "constant", -1, 1).onChange((d) => (this.planes[1].constant = d));
			planeY.add(this.params.planeY, "negated").onChange(() => {
				this.planes[1].negate();
				this.params.planeY.constant = this.planes[1].constant;
			});
			planeY.open();

			// planeZ - gui设置
			const planeZ = gui.addFolder("planeZ");
			planeZ.add(this.params.planeZ, "displayHelper").onChange((v) => (this.planeHelpers[2].visible = v));
			planeZ.add(this.params.planeZ, "constant", -1, 1).onChange((d) => (this.planes[2].constant = d));
			planeZ.add(this.params.planeZ, "negated").onChange(() => {
				this.planes[2].negate();
				this.params.planeZ.constant = this.planes[2].constant;
			});
			planeZ.open();

		},
		/**
		* @description 绘制剖切面(直接用即可,无需深度理解)
		*/
		createPlaneStencilGroup(geometry, plane, renderOrder) {
			// 物体_背面 和 物体_正面 都需要关闭深度检测、深度写入、颜色写入,因为他们的主要目的是使模板缓冲区中最终记录插值部分即剖切面
			const group = new THREE.Group();
			const baseMat = new THREE.MeshBasicMaterial();
			baseMat.depthWrite = false;//深度写入
			baseMat.depthTest = false;//深度检测
			baseMat.colorWrite = false;//颜色写入
			baseMat.stencilWrite = true;//是否对模板缓冲执行模板操作,如果执行写入或者与模板缓冲进行比较,这个值需要设置为true
			baseMat.stencilFunc = THREE.AlwaysStencilFunc;//模板函数。AlwaysStencilFunc - 模板比较方法

			// back faces(背面)
			const mat0 = baseMat.clone();
			mat0.side = THREE.BackSide;
			mat0.clippingPlanes = [plane];
			//模板操作,当提供的模板函数通过的时候,材质会在模板缓冲像素上执行怎样的模板操作。
			//IncrementWrapStencilOp - 将当前模板值加1,如果这个值超过了255则会设置为0
			mat0.stencilFail = THREE.IncrementWrapStencilOp;//当比较函数没有通过的时候要执行的模板操作
			mat0.stencilZFail = THREE.IncrementWrapStencilOp;//当比较函数通过了但是深度检测没有通过的时候要执行的模板操作
			mat0.stencilZPass = THREE.IncrementWrapStencilOp;//当比较函数和深度检测都通过时要执行的模板操作

			const mesh0 = new THREE.Mesh(geometry, mat0);
			mesh0.renderOrder = renderOrder;//渲染顺序是由低到高来排序的,默认值为0
			group.add(mesh0);

			// front faces(正面)
			const mat1 = baseMat.clone();
			mat1.side = THREE.FrontSide;
			mat1.clippingPlanes = [plane];
			// DecrementWrapStencilOp - 将当前模板值减1,如果这个值低于0则会设置为255
			mat1.stencilFail = THREE.DecrementWrapStencilOp;
			mat1.stencilZFail = THREE.DecrementWrapStencilOp;
			mat1.stencilZPass = THREE.DecrementWrapStencilOp;

			const mesh1 = new THREE.Mesh(geometry, mat1);
			mesh1.renderOrder = renderOrder;//渲染顺序是由低到高来排序的,默认值为0

			group.add(mesh1);

			return group;
		},

		/**
		 * @description 创建辅助坐标轴
		 */
		addHelper() {
			// 模拟相机视锥体的辅助对象
			let helper = new THREE.CameraHelper(this.camera);
			// this.scene.add(helper);
			//创建辅助坐标轴、轴辅助 (每一个轴的长度)
			let axisHelper = new THREE.AxesHelper(150);  // 红线是X轴,绿线是Y轴,蓝线是Z轴
			// this.scene.add(axisHelper)
			// 坐标格辅助对象
			let gridHelper = new THREE.GridHelper(100, 30, 0x2C2C2C, 0x888888);
			// this.scene.add(gridHelper);

			this.planeHelpers = this.planes.map(p => new THREE.PlaneHelper(p, 2, 0xffffff))
			this.planeHelpers.map(ph => {
				ph.visible = false
				this.scene.add(ph)
			})
		},
		/**
		 * @description 给场景添加环境光效果
		 */
		setPMREMGenerator() {
			// 预过滤的Mipmapped辐射环境贴图
			const pmremGenerator = new THREE.PMREMGenerator(this.renderer);
			this.scene.environment = pmremGenerator.fromScene(new RoomEnvironment(this.renderer), 0.04).texture;
		},
		/**
		 * @description 设置光源
		 */
		setLight() {
			// 环境光
			const ambientLight = new THREE.AmbientLight(0xffffff, 1.5);
			this.scene.add(ambientLight);
			// 聚光灯
			const spotLight = new THREE.SpotLight(0xffffff, 60);
			spotLight.angle = Math.PI / 5;
			spotLight.penumbra = 0.2;
			spotLight.position.set(2, 3, 3);
			spotLight.castShadow = true;
			spotLight.shadow.camera.near = 3;
			spotLight.shadow.camera.far = 10;
			spotLight.shadow.mapSize.width = 1024;
			spotLight.shadow.mapSize.height = 1024;
			// this.scene.add(spotLight);

			// 平行光
			const dirLight = new THREE.DirectionalLight(0xffffff, 3);
			dirLight.position.set(5, 10, 7.5);
			dirLight.castShadow = true;
			dirLight.shadow.camera.right = 2;
			dirLight.shadow.camera.left = - 2;
			dirLight.shadow.camera.top = 2;
			dirLight.shadow.camera.bottom = - 2;

			dirLight.shadow.mapSize.width = 1024;
			dirLight.shadow.mapSize.height = 1024;
			this.scene.add(dirLight);
		},
		/**
		 * @description 创建性能监听器
		*/
		addStatus() {
			// 创建一个性能监听器
			this.stats = new Stats();
			// 将性能监听器添加到容器中
			this.container.appendChild(this.stats.dom);
		},
		/**
		 * @description 监听屏幕的大小改变,修改渲染器的宽高,相机的比例
		*/
		// 窗口变化
		onWindowSize() {
			// 更新摄像头
			this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
			// 更新摄像机的投影矩阵
			this.camera.updateProjectionMatrix();
			//更新渲染器
			this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
			// 设置渲染器的像素比
			this.renderer.setPixelRatio(window.devicePixelRatio)
		},
		/**
		* @description 动画执行函数
		*/
		animate() {
			const delta = this.clock.getDelta();
			if (this.params.animate) {
				this.object.rotation.x += delta * 0.5;
				this.object.rotation.y += delta * 0.2;
			}
			for (let i = 0; i < this.planeObjects.length; i++) {
				const plane = this.planes[i];
				const po = this.planeObjects[i];
				plane.coplanarPoint(po.position);
				po.lookAt(
					po.position.x - plane.normal.x,
					po.position.y - plane.normal.y,
					po.position.z - plane.normal.z
				);

			}
			// 引擎自动更新渲染器
			requestAnimationFrame(this.animate);
			//update()函数内会执行camera.lookAt(x, y, z)
			this.controller.update();
			// 更新性能监听器
			this.stats.update();
			// 重新渲染场景
			this.renderer.render(this.scene, this.camera);
		},
	},
};
</script>
<style>
#container {
	position: absolute;
	width: 100%;
	height: 100%;
}
</style>


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1800352.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

成都跃享未来教育咨询有限公司,值得信赖!

在浩渺的教育咨询市场中&#xff0c;成都跃享未来教育咨询有限公司以其独特的魅力和卓越的服务质量&#xff0c;成为了行业内的璀璨明星。作为一家致力于为学生提供全方位教育咨询服务的公司&#xff0c;成都跃享未来教育咨询有限公司始终坚持安全可靠的原则&#xff0c;为广大…

docker命令 docker ps -l (latest)命令在 Docker 中用于列出最近一次创建的容器

文章目录 12345 1 docker ps -l 命令在 Docker 中用于列出最近一次创建的容器。具体来说&#xff1a; docker ps&#xff1a;这个命令用于列出当前正在运行的容器。-l 或 --latest&#xff1a;这个选项告诉 docker ps 命令只显示最近一次创建的容器&#xff0c;不论该容器当前…

SAP ERP系统主要模块简介

SAP系统通过提供一系列高度灵活的模块&#xff0c;满足企业在不同业务领域的需求。这些模块不仅功能齐全且相对独立&#xff0c;但它们之间又能紧密协作&#xff0c;共同构筑一个协同高效的工作环境。 财务会计&#xff08;FI&#xff09;模块 它涵盖了总账、应收账款、应付账…

fs.1.10 ON rockylinux8 docker镜像制作

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 rockylinux docker上编译安装fs1.10版本的流程记录。 环境 docker engine&#xff1a;Version 24.0.6 rockylinux docker&#xff1a;8 freeswitch&#xff1a;v1.10.7 手动模式 rockylinux准备 docker hub拉取r…

第三篇——大数据思维的科学基础

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 大数据时代&#xff0c;大数据思维的重要性不言而喻&#xff1b;而信息在…

搜索引擎优化服务如何寻找?

首先你要了解搜索引擎优化&#xff0c;也就是seo具体是什么&#xff0c;要做些什么&#xff0c;然后就是确定你自身业务的需求&#xff0c;是要特定的关键词排名&#xff0c;还是整体网站流量的提升&#xff0c;还是想要优化目前的网站 接下来你就可以正式的寻找真正能帮助到你…

three.js官方案例(animation / multiple)webgl_animation_multiple.html学习笔记

目录 ​编辑 1 骨架工具&#xff08;SkeletonUtils&#xff09; 1.1 clone方法 2 蒙皮网格&#xff08;SkinnedMesh&#xff09; 3 自测 4 webgl_animation_multiple.html全部脚本 1 骨架工具&#xff08;SkeletonUtils&#xff09; 用于操控 Skeleton、 SkinnedMesh、和…

springboot项目部署需要redis集群问题

本来直接将redis为单独启动模式转为配置 yml文件 spring.redis.cluster.nodes: 192.168.12.78:8001,192.168.12.78:8002,192.168.12.78:8003, java文件 package io.sirc.config;import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.ann…

【JSP】如何在IDEA上部署JSP WEB开发项目

以我的课设为例&#xff0c;教大家拿到他人的项目后&#xff0c;如何在IDEA上部署。 需要准备&#xff1a; JDK17&#xff08;或者JDK13&#xff09;IntelliJ IDEA 2023.2.6MySQL 8.0Tomcat 9.0 一&#xff0c;新建项目添加文件 1.1复制“位置”的路径 1.2找到该文件夹 1.3…

SQLite3(1):介绍安装与测试

目录 1、SQLite3介绍 2、SQLite3的优势和特性 3、SQLite3安装与测试 3.1 SQLite3安装 3.2 SQLite3测试 4、SQLite3简单使用 4.1 连接数据库文件 4.2 创建信息表 4.3 插入三个学生信息 4.4 确认信息 5、总结 1、SQLite3介绍 SQLite3是一种轻量级的关系型数据库管理系…

kafka集群内外网分流方案——筑梦之路

前言 在现代分布式系统架构中&#xff0c;Kafka作为一款高性能的消息队列系统&#xff0c;广泛应用于大数据处理、实时流处理以及微服务间的异步通信场景。特别是往往企业级应用中&#xff0c;业务网段和内网通信网段不是同一个网段&#xff0c;内网的机器想要访问业务数据只能…

计网复习资料

一、选择题&#xff08;每题2分&#xff0c;共40分&#xff09; 1. Internet 网络本质上属于&#xff08; &#xff09;网络。 A.电路交换 B.报文交换 C.分组交换 D.虚电路 2.在 OSI 参考模型中,自下而上第一个提供端到端服务的是( )。 A.数据链路层 B.传输…

【YOLOv10】使用 TensorRT C++ API 调用GPU加速部署 YOLOv10 实现 500FPS 推理速度——快到飞起!

NVIDIA TensorRT ™ 是一款用于高性能深度学习推理的 SDK&#xff0c;包含深度学习推理优化器和运行时&#xff0c;可为推理应用程序提供低延迟和高吞吐量。YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法&#xff0c;通过消除NMS、优化模型架构和引入创新模块等策…

C++进阶之二叉搜索树

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 C进阶​ ​​​​算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.二叉搜索树概念 二.实现 2.1插入 …

K8s存储对象的使用

背景和概念 容器中的文件在磁盘上是临时存放的&#xff0c;这给在容器中运行较重要的应用带来一些问题&#xff1a; 当容器崩溃或停止时&#xff0c;此时容器状态未保存&#xff0c; 因此在容器生命周期内创建或修改的所有文件都将丢失。另外 在崩溃期间&#xff0c;kubelet 会…

3D打印随形水路:模具水路的革命性技术

在快速发展的模具制造行业中&#xff0c;3D打印技术以其独特的优势正在引领一场技术革命。其中&#xff0c;3D打印随形水路技术&#xff0c;凭借其灵活性和定制化设计的能力&#xff0c;为模具带来了前所未有的变革。 模具3D打印随形水路技术&#xff0c;是一种利用3D打印技术制…

《帝国时代 III:决定版》秘籍 怎么在苹果电脑上玩《帝国时代 III:决定版》

《帝国时代 III&#xff1a;决定版》是一款让玩家沉浸于历史长河体验从大航海时代到工业革命时期的游戏。下面我们来看看《帝国时代 III&#xff1a;决定版》是什么类型的游戏&#xff0c;《帝国时代 III&#xff1a;决定版》Mac安装教程的相关内容。 一、《帝国时代 III&…

UI设计公司-蓝蓝设计-交通行业ui设计解决方案

来百度APP畅享高清图片 这是北京兰亭妙微科技有限公司&#xff08;简称蓝蓝设计&#xff09;在交通行业的一些ui设计经验&#xff0c;我们建立了UI设计分享群&#xff0c;每天会分享国内外的一些优秀设计&#xff0c;如果有兴趣的话&#xff0c;可以进入一起成长学习&#xff0…

流量录制学习

AREX Cloud | AREX (arextest.com) 流量录制学习&#xff0c;比vivo的moonbox要好用

FineReport使用小记(不断更新中…………)

FineReport使用小记 1. 单元格相关设置1.1. 单元格值样式 2. 报表块设置2.1. 给报表块加单位 1. 单元格相关设置 1.1. 单元格值样式 1. 百分比样式 选中单元格&#xff0c;单元格属性——>文本——>格式——>百分比 下面可以选择保留几位小数&#xff0c;图中为保留…