当前位置: 首页 > news >正文

Cocos Creator 2d光照

godot游戏引擎是有2d光照的,用起来感觉还是很强大的,不知道他是怎么搞的,有时间看看他们怎么实现的。

之前一直以为cocos社区里面没有2d光照的实现,偶然看到2d实现的具体逻辑,现在整理如下,

一:实现原理

这里实现的2d光源是类似聚光灯的效果,是有一个衰减过程的,具体怎么个衰减法,还得用到我们学的数学知识就是线性衰减 y = -x + b,就是用它来模拟光照的衰减效果的,在光照半径范围内衰减的时候是一个值(根据距离进行衰减,该值的意义是对光照效果的贡献值),大于光照半径范围 光照贡献值急速的变为0.

1:需要哪些参数

模拟光照的参数,需要一个光源照射的半径范围r,光源锥形的角度r1,光源的强度indensity,光源的颜色 color,光源世界坐标,用来计算物体距离光源大小,以此来计算光照效果。

properties:alphaThreshold:     { value: 0.5 }light_normal:       { value: white }light_worldpos:     { value: [255, 255, 255, 255], editor: { type: vec4 } }        light_ambientColor: { value: [127, 127, 127, 127], editor: { type: color } }light_lightColor:   { value: [255, 255, 255, 255], editor: { type: color } }        light_radius:       { value: 10.0 }light_halfRadius:   { value: 0.5 }light_brightness:   { value: 1.0 }
uniform Constant {// 环境光模拟白天和黑夜vec4  light_ambientColor;// 光源颜色vec4  light_lightColor;// 光源世界坐标vec4  light_worldpos;// 光源半径float light_radius;// 光源角度半径 决定了光源锥形区域的宽度float light_halfRadius;// 光源的亮度float light_brightness;float light_unused;};

光源的世界坐标可以通过外部脚本传入,定义一个节点挂在Light脚本来控制光源的世界坐标:


import { _decorator, Component, Node, Sprite, math, UITransform, Label, Vec2, Vec3, Vec4, Camera, view, Material, Texture2D, renderer, color, Color } from 'cc';
import { EDITOR } from 'cc/env';
const { ccclass, property, executeInEditMode } = _decorator;@ccclass('Light')
@executeInEditMode
export class Light extends Component {@property([Node])bodys_normal: Node[] = [];@property([Node])bodys: Node[] = [];@property(Material)eff: Material = null!;@property(Material)eff_normal: Material = null!;onLoad() {}start() {this.updateLight();}update() {this.updateLight();}getwpos(node2d: Node) {return node2d.worldPosition;}updateBody(target, lightPos) {// 更新uniformlet spr = target.getComponent(Sprite);// 灯光位置spr.getSharedMaterial(0).setProperty('light_worldpos', new Vec4(lightPos.x, lightPos.y, lightPos.z, 1));}updateLight() {// 光源位置let lightPos = this.getwpos(this.node)for (var idx in this.bodys_normal) {let node = this.bodys_normal[idx];if (null == node) return;this.updateBody(node, lightPos);}for (var idx in this.bodys) {let node = this.bodys[idx];if (null == node) return;this.updateBody(node, lightPos);}}
}

 2:具体的一些细节

想要让光源产生效果,需要单独的给每个图片加上一个单独的材质,这样才能控制颜色的输出,这里使用时最新版本(3.8.x)的shader的结构:

// Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
CCEffect %{techniques:- passes:- vert: light-vs:vertfrag: light-fs:fragdepthStencilState:depthTest: falsedepthWrite: falseblendState:targets:- blend: trueblendSrc: src_alphablendDst: one_minus_src_alphablendDstAlpha: one_minus_src_alpharasterizerState:cullMode: noneproperties:alphaThreshold:     { value: 0.5 }light_normal:       { value: white }light_worldpos:     { value: [255, 255, 255, 255], editor: { type: vec4 } }        light_ambientColor: { value: [127, 127, 127, 127], editor: { type: color } }light_lightColor:   { value: [255, 255, 255, 255], editor: { type: color } }        light_radius:       { value: 10.0 }light_halfRadius:   { value: 0.5 }light_brightness:   { value: 1.0 }
}%CCProgram light-vs %{precision highp float;#include <builtin/uniforms/cc-global>#if USE_LOCAL#include <builtin/uniforms/cc-local>#endif#if SAMPLE_FROM_RT#include <common/common-define>#endifin vec3 a_position;in vec2 a_texCoord;in vec4 a_color;out vec4 color;out vec2 uv0;out vec4 object_position;vec4 vert () {vec4 pos = vec4(a_position, 1);// 不适用mvp矩阵计算成世界坐标,因为如果屏幕是横屏的时候,转成世界坐标后,x轴会出现拉伸。// 这里使用的是UI的坐标系,参考light.ts获取世界坐标的代码。object_position = pos;#if USE_LOCALpos = cc_matWorld * pos;#endif#if USE_PIXEL_ALIGNMENTpos = cc_matView * pos;pos.xyz = floor(pos.xyz);pos = cc_matProj * pos;#elsepos = cc_matViewProj * pos;#endifuv0 = a_texCoord;#if SAMPLE_FROM_RTCC_HANDLE_RT_SAMPLE_FLIP(uv0);#endifcolor = a_color;return pos;}
}%CCProgram light-fs %{precision highp float;#include <builtin/internal/embedded-alpha>#include <builtin/internal/alpha-test>in vec4 color;in vec4 object_position;#if USE_TEXTUREin vec2 uv0;#pragma builtin(local)layout(set = 2, binding = 11) uniform sampler2D cc_spriteTexture;#endif// 是否使用2d法线#if USE_2D_NORMALuniform sampler2D light_normal;#endif#if USE_2D_LIGHTuniform Constant {// 环境光模拟白天和黑夜vec4  light_ambientColor;// 光源颜色vec4  light_lightColor;// 光源世界坐标vec4  light_worldpos;// 光源半径float light_radius;// 光源角度半径 决定了光源锥形区域的宽度float light_halfRadius;// 光源的亮度float light_brightness;float light_unused;};/*** 亮度计算, 按照距离远近衰减, 采取内外光圈叠加方式, 按照世界坐标计算 (0.0 ~ 1.0)* @param dist 距离 (0.0 ~ 1.0)* @param cutoff_r 外光圈半径 (> 0.0) 光源的截止半径 超过这个半径区域不再受到关照的影响* @param half_r 内光圈半径, 使用cutoffRadius的半径占比 (0.0 ~ 1.0)  光源的角度半径,决定了光源锥形区域的宽度*/float light_bright(float dist, float cutoff_r, float half_r) {// 截距float intercept = cutoff_r * half_r;// dx_1 = 1 / (2 * intercept) => y = 1 / 2x; 双曲线 近处float dx_1 = 0.5 / intercept;// dx_2曲线和dx_1曲线对称,对称中心是(cutoff_r / 2,1 / cutoff_r) 远处float dx_2 = 0.5 / (cutoff_r - intercept);float offset = 0.5 + intercept * dx_2;// 近处 慢慢衰减float falloffTermNear = clamp((1.0 - dist * dx_1), 0.0, 1.0);// 远处 远离光源的时候迅速减小到0float falloffTermFar  = clamp((offset - dist * dx_2), 0.0, 1.0);// 当dist > intercept 的时候 => 1  dist < intercept => 0float falloffSelect = step(intercept, dist);// 计算光源对某一个点的照明贡献  距离衰减因子 dist < intercept => fallofftTermNear 近距离因子  dist > intercept => 远距离因子float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;return falloffTerm;}/*** 计算灯光的颜色值* @param dist 物体距离光源的距离, 世界单位 (> 0.0)* @param radius 光源半径,世界单位 (> 0.0)*/vec3 light_diffuse (float dist, float radius) {      // 计算像素点所在光圈位置的亮度float falloffTerm = light_bright(dist, radius, light_halfRadius);// falloffTerm 为亮度值, light_lightColor 为灯光颜色return falloffTerm * vec3(light_lightColor);}/*** 计算光照颜色* @param object_position 物体坐标, 世界坐标* @param object_vertex_normal 顶点的法线向量, 归一化*/vec3 light_color(vec3 col) {// 计算光线方向, 这个方式不能直接用,打个比方纹理是正方形的,而世界坐标可能是长方形的(GL的坐标固定在-1.0到1.0之间, 而屏幕不一定是正方形)vec4 object_direction = object_position - light_worldpos;// 计算物体与灯光的距离float object_dist = length(vec3(object_direction));// 开启这个可以测试// object_dist = length(uv0 - 0.5);// 计算物体与灯光的的距离, 占用直径的百分比float object_dist_normal = object_dist / (light_radius * 2.0);// 获取灯光漫反射颜色vec3 diffuse = light_diffuse(object_dist_normal, light_radius);#if USE_2D_NORMAL// 获取法向量vec3 normal = texture(light_normal, uv0).rgb;normal = normal * 2.0 - 1.0;// 计算光照反射系数,向量点积float normalDot = max(0.0, dot(normal, -normalize(vec3(object_direction.x, object_direction.y, -60))));// 反射光 * 法向量衰减 + 环境光      return col * (diffuse * light_brightness * normalDot + vec3(light_ambientColor));#else// 反射光 * 法向量衰减 + 环境光 (没有法线的情况下需要 0.5 衰减)return col * (diffuse * light_brightness + vec3(light_ambientColor));#endif      }/*** 计算光照颜色* @param object_position 物体坐标, 世界坐标* @param object_vertex_normal 顶点的法线向量, 归一化*/vec4 light_dist() {}#endifvec4 frag () {vec4 o = vec4(1, 1, 1, 1);#if USE_TEXTUREo *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);#if IS_GRAYfloat gray  = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;o.r = o.g = o.b = gray;#endif#endifo *= color;ALPHA_TEST(o);#if USE_2D_LIGHTreturn vec4(light_color(vec3(o)), o.a);#elsereturn o;#endif}
}%

主要的核心方法:light_bright,这个方法是用来计算光照贡献值的。

其中intercept = cutoff_r * half_r 是设定了一个阙值,当dist < intercept的时候用什么样的衰减方式,当dist > intercept的时候用什么样的衰减方式,

那么 float dx_1  = 0.5 / intercept 该怎么理解呢,这个得结合falloffTermNear,falloffTermFar来理解,就好比v = s / t,总的衰减总数是1,前半部分(dist < intercept)占了0.5,那么后半部分就是1 - 0.5,我还是画一张图来理解吧,每衰减 1需要消耗的值就是 dx_1,这个dx_1可以理解为衰减的速率,也就是下面代码中出现的斜率,也就是衰减速度。那么dx_1讲清楚了,自然而然dx_2你也是理解的

这里还有一个点就是offset什么意思,offset指的就是当dist = intercept的时候怎么保证两个衰减过程衔接的非常自然呢,咱们可以列一个公式看看

1 - dist * dx_1 = offset - dist * dx_2,很容易我们解方程就能够知道 offset = 0.5 + intercept * dx_2;

最后根据dist来计算对光照的影响程度就可以了, 

/*** 亮度计算, 按照距离远近衰减, 采取内外光圈叠加方式, 按照世界坐标计算 (0.0 ~ 1.0)* @param dist 距离 (0.0 ~ 1.0)* @param cutoff_r 外光圈半径 (> 0.0) 光源的截止半径 超过这个半径区域不再受到关照的影响* @param half_r 内光圈半径, 使用cutoffRadius的半径占比 (0.0 ~ 1.0)  光源的角度半径,决定了光源锥形区域的宽度*/float light_bright(float dist, float cutoff_r, float half_r) {// 截距float intercept = cutoff_r * half_r;// dx_1 = 1 / (2 * intercept) => y = 1 / 2x; 双曲线 近处float dx_1 = 0.5 / intercept;// dx_2曲线和dx_1曲线对称,对称中心是(cutoff_r / 2,1 / cutoff_r) 远处float dx_2 = 0.5 / (cutoff_r - intercept);// 用在两种衰减过程中的阙值出的矫正保证颜色渐变的连贯  计算过程是 1 - dist * dx_1 = offset - dist * dx_2 可以反算出来 offset = 0.5 + intercept * dx_2float offset = 0.5 + intercept * dx_2;// 近处 慢慢衰减 线性衰减float falloffTermNear = clamp((1.0 - dist * dx_1), 0.0, 1.0);// 远处 远离光源的时候迅速减小到0float falloffTermFar  = clamp((offset - dist * dx_2), 0.0, 1.0);// 当dist > intercept 的时候 => 1  dist < intercept => 0float falloffSelect = step(intercept, dist);// 计算光源对某一个点的照明贡献  距离衰减因子 dist < intercept => fallofftTermNear 近距离因子  dist > intercept => 远距离因子float falloffTerm = (1.0 - falloffSelect) * falloffTermNear + falloffSelect * falloffTermFar;return falloffTerm;}

讲解到这里希望你能够理解光源产生的过程。

下面贴下原文的链接:

cocos creator 2D关照 

我只是把不容易理解的部分给讲一下,希望对你有帮助。

相关文章:

Cocos Creator 2d光照

godot游戏引擎是有2d光照的&#xff0c;用起来感觉还是很强大的&#xff0c;不知道他是怎么搞的&#xff0c;有时间看看他们怎么实现的。 之前一直以为cocos社区里面没有2d光照的实现&#xff0c;偶然看到2d实现的具体逻辑&#xff0c;现在整理如下&#xff0c; 一&#xff1…...

5款好用的AI办公软件,一键轻松制作PPT、视频,提升工作效率!

众所周知&#xff0c;AI 人工智能技术已渗透到生活的方方面面&#xff0c;无论是很多人早已用上的智能音箱、语音助手&#xff0c;还是新近诞生的各种 AI 软件工具&#xff0c;背后都离不开 AI 人工智能技术的加持。 对于各类新生的 AI 软件工具&#xff0c;人们很容易「选边站…...

【MyBatis面试题】

目录 前言 1.MyBatis执行流程。 2.Mybatis是否支持延迟加载&#xff1f; 3.延迟加载的底层原理知道吗&#xff1f; 4.Mybatis的一级、二级缓存用过吗&#xff1f; 5.Mybatis的二级缓存什么时候会清理缓存中的数据&#xff1f; 总结 前言 本文主要介绍了MyBatis面试题相…...

编程界的圣经:从Scheme到JavaScript构建你的计算思维

文章目录 适读人群目 录 《计算机程序的构造和解释》&#xff08;Structure and Interpretation of Computer Programs&#xff0c;简记为SICP&#xff09;是MIT的基础课教材&#xff0c;出版后引起计算机教育界的广泛关注&#xff0c;对推动全世界大学计算机科学技术教育的发…...

智慧城市与智慧乡村:共创城乡一体化新局面

一、引言 随着科技的不断进步和城乡发展的日益融合&#xff0c;智慧城市与智慧乡村的建设已成为推动城乡一体化发展的新引擎。智慧城市利用物联网、大数据、云计算等先进技术&#xff0c;实现城市治理、公共服务、产业发展等领域的智能化&#xff1b;而智慧乡村则借助现代科技…...

蓝桥杯——web(ECharts)

ECharts 初体验 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><script src"echarts.js">&l…...

MySQL数据库在Windows和Linux中由于大小写默认规则不同,出现大小写问题如何解决?

Windows和Linux差异&#xff1a;在Windows上&#xff0c;lower_case_table_names默认为1&#xff0c;而在Linux上&#xff0c;默认值通常为0。因此&#xff0c;在Linux上更改这个设置更常见&#xff0c;以确保与Windows环境的兼容性或实现特定的大小写敏感性需求。 操作系统的大…...

新雀优化算法NOA求解机器人栅格地图最短路径规划,可以自定义地图(提供MATLAB代码)

一、星雀优化算法 星雀优化算法(Nutcracker optimizer algorithm,NOA)由Mohamed Abdel-Basset等人于2023年提出&#xff0c;该算法模拟星雀的两种行为&#xff0c;即&#xff1a;在夏秋季节收集并储存食物&#xff0c;在春冬季节搜索食物的存储位置。CEC2005:星雀优化算法(Nut…...

重塑语言智能未来:掌握Transformer,驱动AI与NLP创新实战

Transformer模型 Transformer是自然语言理解(Natural Language Understanding&#xff0c;NLU)的游戏规则改变者&#xff0c;NLU 是自然语言处理(Natural Language Processing&#xff0c;NLP)的一个子集。NLU已成为全球数字经济中AI 的支柱之一。 Transformer 模型标志着AI 新…...

【Windows】Windows 11无法连接共享打印机

Windows 11无法连接共享打印机 1.在电脑点击winr 键然后输入gpedit.msc进行回车进入本地本地组策略编辑器2.打开本地组策略-管理模板>打印机->找到配置RPC连接设置&#xff0c;打开3.选择“已启用”&#xff0c;将下面连接协议改成“命名管道上的RPC”&#xff0c;搞定。…...

Window10数据库崩溃启动失败,MySQL8.0.30通过data文件夹恢复数据库到Docker

背景&#xff1a; 昨天关机前还在使用mysql&#xff0c;一切正常&#xff0c;但今天打开电脑&#xff0c;发现mysql启动不起来了&#xff0c;老是提示端口占用&#xff0c;但是系统也没有新安装什么软件&#xff0c;而且通过查询nat命令也没发现3306端口占用。而且修改成3307等…...

【树】-Lc101-对称二叉树(一棵树是否是另一棵树的子树的变形)

写在前面 最近想复习一下数据结构与算法相关的内容&#xff0c;找一些题来做一做。如有更好思路&#xff0c;欢迎指正。 目录 写在前面一、场景描述二、具体步骤1.环境说明2.代码 写在后面 一、场景描述 对称二叉树。给给定一个二叉树&#xff0c;检查它是否是镜像对称的。 例…...

在Jupyter Notebook中安装第三方库

pip vs. conda pip 可以在所有环境下安装python包。conda 可以在conda环境下安装所有包。 如果你已经安装了python&#xff0c;那么这个选择对你来说是非常容易的&#xff1a; 如果你是用Anaconda或者Miniconda安装的python&#xff0c;那么请使用conda命令来安装python包。如…...

「AI工程师」数据处理与分析-工作指导

工作指导书 一、工作职责 负责数据的收集、清洗、整合和处理,确保数据质量和准确性。进行数据分析和挖掘,提取有价值的信息,为业务决策提供支持。构建和维护数据处理和分析的流程和工具,提高数据处理效率。与其他团队成员合作,共同解决数据处理和分析过程中遇到的问题。二…...

Rust:Mutex 的示例代码

在Rust中&#xff0c;你可以使用std::sync::Mutex来创建一个互斥锁&#xff0c;从而保护共享资源。下面是一个使用Mutex的简单示例&#xff1a; use std::sync::Mutex; use std::thread; use std::time::Duration; fn main() { // 创建一个包含整数的Mutex let counter…...

在 Docker 环境下安装 OpenWrt

在 Docker 环境下安装 OpenWrt 是一种方便且易于管理的方式&#xff0c;它允许您在不需要物理设备的情况下运行 OpenWrt。以下是在 Docker 中安装 OpenWrt 的步骤&#xff1a; 首先&#xff0c;您需要安装 Docker。具体安装方法可以参考 Docker 官方文档。在安装完成后&#xf…...

stl的基本知识学习

1.vector&#xff1a; 2.set&#xff1a; 3.map&#xff1a; 4.栈&#xff1a; 5.队列&#xff1a; 6. unordered_map与unordered_set: 7. 位运算&#xff1a; 8.cctype&#xff1a; 导图&#xff1a;...

Python从0到100(三):Python中的变量介绍

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...

污水处理厂重金属废水深度处理CH-90树脂处理系统

项目名称 广东某工业污水处理厂重金属废水深度处理工程项目 工艺选择 科海思重金属深度处理工艺 工艺原理 离子交换吸附 项目背景 随着环保要求不断提高&#xff0c;工业废水处理已成为众多企业的必修课。然而在工业生产中&#xff0c;如何有效处理含有重金属的废水成为…...

WordPress供求插件API文档:用户登录

该文档为WordPress供求插件文档&#xff0c;详情请查看 WordPress供求插件&#xff1a;一款专注于同城生活信息发布的插件-CSDN博客文章浏览阅读67次。WordPress供求插件&#xff1a;sliver-urban-life 是一款专注于提供同城生活信息发布与查看的插件&#xff0c;该插件可以实…...

隐私安全首选:DeepSeek-R1本地推理引擎快速上手指南

隐私安全首选&#xff1a;DeepSeek-R1本地推理引擎快速上手指南 1. 为什么选择本地推理引擎 在当今数据安全日益重要的时代&#xff0c;越来越多的用户开始关注AI应用的隐私保护问题。传统的云端AI服务虽然功能强大&#xff0c;但存在数据外泄的风险&#xff0c;尤其对于处理…...

告别Arduino!用VSCode+ESP-IDF给零知ESP32开发AI语音项目,效率翻倍指南

从Arduino到专业级开发&#xff1a;VSCodeESP-IDF构建ESP32 AI语音系统实战指南 当你的ESP32项目从简单的传感器读取升级到需要整合语音识别、网络通信和图形界面的复杂系统时&#xff0c;Arduino IDE的局限性就会逐渐显现。编译速度慢、依赖管理混乱、调试工具简陋等问题会严…...

抖音批量下载工具终极指南:从零构建高效内容采集系统

抖音批量下载工具终极指南&#xff1a;从零构建高效内容采集系统 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback suppor…...

Display Driver Uninstaller (DDU) 终极指南:彻底解决显卡驱动残留问题的专业工具

Display Driver Uninstaller (DDU) 终极指南&#xff1a;彻底解决显卡驱动残留问题的专业工具 【免费下载链接】display-drivers-uninstaller Display Driver Uninstaller (DDU) a driver removal utility / cleaner utility 项目地址: https://gitcode.com/gh_mirrors/di/di…...

Qwen3.5-2B辅助Proteus单片机仿真:代码生成与调试建议

Qwen3.5-2B辅助Proteus单片机仿真&#xff1a;代码生成与调试建议 1. 嵌入式开发的新帮手 最近在调试一个基于STM32的温度监控项目时&#xff0c;遇到了一个奇怪的现象&#xff1a;Proteus仿真中ADC读数总是偏高。花了整整两天时间排查硬件连接和代码逻辑&#xff0c;最后发现…...

3步解锁外语视频:PotPlayer实时字幕翻译插件全攻略

3步解锁外语视频&#xff1a;PotPlayer实时字幕翻译插件全攻略 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu 还在为看不懂外语电影、…...

MAXIM美信 MAX1673ESA+T SOP8 电荷泵

特性MAX1673电荷泵反相器提供了一种低成本、紧凑的方式&#xff0c;可从正输入产生稳压负输出&#xff0c;输出电流高达125mA。仅需三个小电容&#xff0c;且只需两个电阻即可设置其输出电压。输入范围为2V至5.5V。在跳周期&#xff08;Skip&#xff09;稳压模式下&#xff0c;…...

SDFCOM_SWC警告深度剖析:从assign短路到时序标注失效的链路追踪

1. SDFCOM_SWC警告现象解析 最近在用VCS跑后仿时&#xff0c;遇到了一个让人头疼的SDFCOM_SWC警告。这个警告看起来简单&#xff0c;但背后隐藏的问题却相当复杂。简单来说&#xff0c;当你在RTL代码里用assign语句把信号"短路"连接时&#xff0c;PrimeTime生成的SD…...

B站视频下载器终极指南:轻松下载4K大会员高清视频

B站视频下载器终极指南&#xff1a;轻松下载4K大会员高清视频 【免费下载链接】bilibili-downloader B站视频下载&#xff0c;支持下载大会员清晰度4K&#xff0c;持续更新中 项目地址: https://gitcode.com/gh_mirrors/bil/bilibili-downloader 还在为无法离线观看B站精…...

Qwen3-4B实战:Streamlit缓存让AI应用支持多人同时聊天

Qwen3-4B实战&#xff1a;Streamlit缓存让AI应用支持多人同时聊天 1. 问题背景与挑战 当你在本地部署了一个基于Qwen3-4B的AI聊天应用&#xff0c;自己测试时一切运行良好。但当你兴奋地分享给同事或朋友使用时&#xff0c;突然发现&#xff1a; 第二个用户打开页面时&#…...