Unity中Shader的Standard材质解析(二)

news2024/9/24 13:23:36

文章目录

  • 前言
  • 一、我们对 Standard 的 PBR 的 GI 进行解析
    • 1、我们先创建一个PBR的.cginc文件,用于整理用到的函数
    • 2、然后在Standard的Shader中引用该cginc文件
  • 二、依次整理函数到该cginc文件中
    • 我们来看一下PBR中GI的镜面反射做了些什么
  • 二、最终代码
    • .cginc代码:
    • Shader代码:


前言

Unity中Shader的Standard材质解析(二),对 Standard 的 PBR 的 GI 进行解析

  • Unity中Shader的Standard材质解析(一)

一、我们对 Standard 的 PBR 的 GI 进行解析

在这里插入图片描述

1、我们先创建一个PBR的.cginc文件,用于整理用到的函数

在这里插入图片描述

2、然后在Standard的Shader中引用该cginc文件

#include “CGInclude/MyPhysicallyBasedRendering.cginc”


二、依次整理函数到该cginc文件中

  • 整理 LightingStandard_GI1(o, giInput, gi); 中的数据

在这里插入图片描述

  • Unity_GlossyEnvironmentData表示GI中的反射准备数据

在这里插入图片描述

  • 准备好反射数据后,计算得出GI中的 漫反射 和 镜面反射

在这里插入图片描述

  • 输出漫反射看看效果

在这里插入图片描述
在这里插入图片描述

  • 输出镜面反射看看效果

在这里插入图片描述

在这里插入图片描述

  • 我们可以自定义一下Cubemap,看看不同的反射效果

(这就是PBR的优点,可以根据不同的环境,直接呈现效果,不用再根据环境调节参数)

请添加图片描述

我们来看一下PBR中GI的镜面反射做了些什么

  • 这个程序块只会在,反射探针中开启Box Projection时,才会运行

在这里插入图片描述
在这里插入图片描述
这选项的作用是:使用反射探针的物体在移动时,效果不会变,只有在摄像机方向变时,效果才会变化。那么,要让物体动时,反射效果同时改变的话,就需要开启该选项。

  • 在取消了材质的Reflection后,会运行该程序块
  • 反之,运行之后的部分

在这里插入图片描述

在这里插入图片描述

  • 在开启反射效果后,对于感性粗糙度的计算。
  • 在Unity中的粗糙度,使用分级贴图来模拟粗糙度(节省性能)

在这里插入图片描述

  • 由于粗糙度与反射探针的mip变化不呈现线性正比,所以需要一个公式来改变

//r = r * (1.7 - 0.7r)
perceptualRoughness = perceptualRoughness
(1.7 - 0.7*perceptualRoughness);

在这里插入图片描述

在Blender中,粗糙度是按数值改变的:
请添加图片描述

在Unity中,粗糙度是按贴图分级来模拟的粗糙度:

请添加图片描述

在这里插入图片描述


二、最终代码

.cginc代码:

#ifndef MYPHYSICALLYBASERENDERING_INCLUDE
    #define MYPHYSICALLYBASERENDERING_INCLUDE

    half3 Unity_GlossyEnvironment1 (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn)
    {
        half perceptualRoughness = glossIn.roughness /* perceptualRoughness */ ;

        // TODO: CAUTION: remap from Morten may work only with offline convolution, see impact with runtime convolution!
        // For now disabled
        #if 0
        float m = PerceptualRoughnessToRoughness(perceptualRoughness); // m is the real roughness parameter
        const float fEps = 1.192092896e-07F;        // smallest such that 1.0+FLT_EPSILON != 1.0  (+1e-4h is NOT good here. is visibly very wrong)
        float n =  (2.0/max(fEps, m*m))-2.0;        // remap to spec power. See eq. 21 in --> https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf

        n /= 4;                                     // remap from n_dot_h formulatino to n_dot_r. See section "Pre-convolved Cube Maps vs Path Tracers" --> https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html

        perceptualRoughness = pow( 2/(n+2), 0.25);      // remap back to square root of real roughness (0.25 include both the sqrt root of the conversion and sqrt for going from roughness to perceptualRoughness)
        #else
        // MM: came up with a surprisingly close approximation to what the #if 0'ed out code above does.
        //r = r * (1.7 - 0.7*r)
        //由于粗糙度与反射探针的mip变化不呈现线性正比,所以需要一个公式来改变
        perceptualRoughness = perceptualRoughness*(1.7 - 0.7*perceptualRoughness);
        #endif

        //UNITY_SPECCUBE_LOD_STEPS = 6,表示反射探针的mip级别有 6 档。粗糙度X6得到最终得mip级别
        half mip = perceptualRoughnessToMipmapLevel(perceptualRoughness);
        half3 R = glossIn.reflUVW;
        half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, R, mip);

        return DecodeHDR(rgbm, hdr);
    }



    //GI中的镜面反射
    inline half3 UnityGI_IndirectSpecular1(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn)
    {
        half3 specular;
        //如果开启了反射探针的Box Projection
        #ifdef UNITY_SPECCUBE_BOX_PROJECTION
        // we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice for probe0 and probe1), so keep original to pass into BoxProjectedCubemapDirection
        half3 originalReflUVW = glossIn.reflUVW;
        glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
        #endif

        #ifdef _GLOSSYREFLECTIONS_OFF
        specular = unity_IndirectSpecColor.rgb;
        #else
        half3 env0 = Unity_GlossyEnvironment1 (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
        //如果开启了反射探针混合
        #ifdef UNITY_SPECCUBE_BLENDING
        const float kBlendFactor = 0.99999;
        float blendLerp = data.boxMin[0].w;
        UNITY_BRANCH
        if (blendLerp < kBlendFactor)
        {
            #ifdef UNITY_SPECCUBE_BOX_PROJECTION
            glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]);
            #endif

            half3 env1 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1,unity_SpecCube0), data.probeHDR[1], glossIn);
            specular = lerp(env1, env0, blendLerp);
        }
        else
        {
            specular = env0;
        }
        #else
        specular = env0;
        #endif
        #endif

        return specular * occlusion;
    }


    inline UnityGI UnityGlobalIllumination1 (UnityGIInput data, half occlusion, half3 normalWorld)
    {
        return UnityGI_Base(data, occlusion, normalWorld);
    }
    //GI计算
    inline UnityGI UnityGlobalIllumination1 (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
    {
        //计算得出GI中的漫反射
        UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld);
        //计算得出GI中的镜面反射
        o_gi.indirect.specular = UnityGI_IndirectSpecular1(data, occlusion, glossIn);
        return o_gi;
    }
    float SmoothnessToPerceptualRoughness1(float smoothness)
    {
        return (1 - smoothness);
    }
    Unity_GlossyEnvironmentData UnityGlossyEnvironmentSetup1(half Smoothness, half3 worldViewDir, half3 Normal, half3 fresnel0)
    {
        Unity_GlossyEnvironmentData g;
        //粗糙度
        g.roughness /* perceptualRoughness */   = SmoothnessToPerceptualRoughness1(Smoothness);
        //反射球的采样坐标
        g.reflUVW   = reflect(-worldViewDir, Normal);

        return g;
    }

    //PBR光照模型的GI计算
    inline void LightingStandard_GI1(
        SurfaceOutputStandard s,
        UnityGIInput data,
        inout UnityGI gi)
    {
        //如果是延迟渲染PASS并且开启了延迟渲染反射探针的话
        #if defined(UNITY_PASS_DEFERRED) && UNITY_ENABLE_REFLECTION_BUFFERS
        gi = UnityGlobalIllumination1(data, s.Occlusion, s.Normal);
        #else

        //Unity_GlossyEnvironmentData表示GI中的反射准备数据
        Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup1(s.Smoothness, data.worldViewDir, s.Normal,
                                                                    lerp(unity_ColorSpaceDielectricSpec.rgb, s.Albedo,
                                                                         s.Metallic));
        //进行GI计算并返回输出gi
        gi = UnityGlobalIllumination1(data, s.Occlusion, s.Normal, g);
        #endif
    }


#endif

Shader代码:

//Standard材质
Shader "MyShader/P2_2_5"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        [NoScaleOffset]_MetallicTex("Metallic(R) Smoothness(G) AO(B)",2D) = "white" {}
        [NoScaleOffset][Normal]_NormalTex("NormalTex",2D) = "bump" {}
        
        _Glossiness ("Smoothness", Range(0,1)) = 0.0
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _AO("AO",Range(0,1)) = 1.0
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
        LOD 200

        // ---- forward rendering base pass:
        Pass
        {
            Name "FORWARD"
            Tags
            {
                "LightMode" = "ForwardBase"
            }

            CGPROGRAM
            // compile directives
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            #pragma multi_compile_instancing
            #pragma multi_compile_fog
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "UnityPBSLighting.cginc"
            #include "AutoLight.cginc"
            #include "CGInclude/MyPhysicallyBasedRendering.cginc"
                
            sampler2D _MainTex;
            float4 _MainTex_ST;
            half _Glossiness;
            half _Metallic;
            fixed4 _Color;
            sampler2D _MetallicTex;
            half _AO;
            sampler2D _NormalTex;
            
            struct appdata
            {
                float4 vertex : POSITION;
                float4 tangent : TANGENT;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
                float4 texcoord1 : TEXCOORD1;
                float4 texcoord2 : TEXCOORD2;
                float4 texcoord3 : TEXCOORD3;
                fixed4 color : COLOR;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            // vertex-to-fragment interpolation data
            // no lightmaps:
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0; // _MainTex
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                #if UNITY_SHOULD_SAMPLE_SH
                    half3 sh : TEXCOORD3; // SH
                #endif
                //切线空间需要使用的矩阵
                float3 tSpace0 : TEXCOORD4;
                float3 tSpace1 : TEXCOORD5;
                float3 tSpace2 : TEXCOORD6;

                UNITY_FOG_COORDS(7)
                UNITY_SHADOW_COORDS(8)
            };

            // vertex shader
            v2f vert(appdata v)
            {
                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);

                //世界空间下的切线
                half3 worldTangent = UnityObjectToWorldDir(v.tangent);
                //切线方向
                half tangentSign = v.tangent.w * unity_WorldTransformParams.w;
                //世界空间下的副切线
                half3 worldBinormal = cross(worldNormal, worldTangent) * tangentSign;
                //切线矩阵
                o.tSpace0 = float3(worldTangent.x, worldBinormal.x, worldNormal.x);
                o.tSpace1 = float3(worldTangent.y, worldBinormal.y, worldNormal.y);
                o.tSpace2 = float3(worldTangent.z, worldBinormal.z, worldNormal.z);

                o.worldPos.xyz = worldPos;
                o.worldNormal = worldNormal;

                // SH/ambient and vertex lights

                #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                    o.sh = 0;
                    // Approximated illumination from non-important point lights
                #ifdef VERTEXLIGHT_ON
                    o.sh += Shade4PointLights (
                    unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
                    unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
                    unity_4LightAtten0, worldPos, worldNormal);
                #endif
                    o.sh = ShadeSHPerVertex (worldNormal, o.sh);
                #endif


                UNITY_TRANSFER_LIGHTING(o, v.texcoord1.xy);

                UNITY_TRANSFER_FOG(o, o.pos); // pass fog coordinates to pixel shader

                return o;
            }

            // fragment shader
            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_EXTRACT_FOG(i);
                
                float3 worldPos = i.worldPos.xyz;
                
                float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                SurfaceOutputStandard o;
                UNITY_INITIALIZE_OUTPUT(SurfaceOutputStandard, o);

                fixed4 mainTex = tex2D(_MainTex, i.uv);
                o.Albedo = mainTex.rgb * _Color;

                o.Emission = 0.0;

                fixed4 metallicTex = tex2D(_MetallicTex, i.uv);
                o.Metallic = metallicTex.r * _Metallic;
                o.Smoothness = metallicTex.g * _Glossiness;
                o.Occlusion = metallicTex.b * _AO;
                o.Alpha = 1;


                half3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
                half3 worldNormal = half3(dot(i.tSpace0,normalTex),dot(i.tSpace1,normalTex),dot(i.tSpace2,normalTex));
                o.Normal = worldNormal;


                // compute lighting & shadowing factor
                UNITY_LIGHT_ATTENUATION(atten, i, worldPos)

                // Setup lighting environment
                UnityGI gi;
                UNITY_INITIALIZE_OUTPUT(UnityGI, gi);
                gi.indirect.diffuse = 0;
                gi.indirect.specular = 0;
                gi.light.color = _LightColor0.rgb;
                gi.light.dir = _WorldSpaceLightPos0.xyz;
                // Call GI (lightmaps/SH/reflections) lighting function
                UnityGIInput giInput;
                UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
                giInput.light = gi.light;
                giInput.worldPos = worldPos;
                giInput.worldViewDir = worldViewDir;
                giInput.atten = atten;
                #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                    giInput.lightmapUV = IN.lmap;
                #else
                giInput.lightmapUV = 0.0;
                #endif
                #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL
                    giInput.ambient = i.sh;
                #else
                giInput.ambient.rgb = 0.0;
                #endif
                giInput.probeHDR[0] = unity_SpecCube0_HDR;
                giInput.probeHDR[1] = unity_SpecCube1_HDR;
                #if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
                    giInput.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending
                #endif
                #ifdef UNITY_SPECCUBE_BOX_PROJECTION
                    giInput.boxMax[0] = unity_SpecCube0_BoxMax;
                    giInput.probePosition[0] = unity_SpecCube0_ProbePosition;
                    giInput.boxMax[1] = unity_SpecCube1_BoxMax;
                    giInput.boxMin[1] = unity_SpecCube1_BoxMin;
                    giInput.probePosition[1] = unity_SpecCube1_ProbePosition;
                #endif
                
                LightingStandard_GI1(o, giInput, gi);
                
                //return fixed4(gi.indirect.specular,1);
                
                // PBS的核心计算
                fixed4 c = LightingStandard(o, worldViewDir, gi);
                
                UNITY_APPLY_FOG(_unity_fogCoord, c); // apply fog
                UNITY_OPAQUE_ALPHA(c.a); //把c的Alpha置1
                return c;
            }
            ENDCG

        }
    }

}

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

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

相关文章

Verilog基础:时序调度中的竞争(一)

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 作为一个硬件描述语言&#xff0c;Verilog HDL常常需要使用语句描述并行执行的电路&#xff0c;但其实在仿真器的底层&#xff0c;这些并行执行的语句是有先后顺序…

NeurIPS 2023 | RGIB:对抗双边图噪声的鲁棒图学习

▐ 摘要 链接预测[1,2]是图学习的一种基础任务&#xff0c;用于判断图中的两个节点是否可能相连&#xff0c;被广泛应用于药物发现、知识图谱补全和在线问答等实际场景。尽管图神经网络&#xff08;Graph Neural Network&#xff0c;GNN&#xff09;在该问题的性能上取得了显著…

JMeter 常见易错问题

1、配置错误&#xff1a; 问题&#xff1a;线程组配置错误&#xff0c;例如设置了错误的线程数或循环次数。 解决方法&#xff1a;检查线程组的配置。确保线程数&#xff08;即并发用户数量&#xff09;设置正确&#xff0c;以及循环次数符合预期。如果要模拟不同类型的用户行…

怎样自动把网页截图发到微信群里

现在很多公司都在使用企业微信了&#xff0c;不但方便公司内部交流和客户交流&#xff0c;还能组建各种小组群&#xff0c;业务群。企业微信群提供一个机器人的功能&#xff0c;方便我们把公司业务信息&#xff0c;或来自外部的信息自动发布到群里。 这里研究一下如何向微信群…

Docker的项目资源参考

Docker的项目资源包括以下内容&#xff1a; Docker官方网站&#xff1a;https://www.docker.com/ Docker Hub&#xff1a;https://hub.docker.com/ Docker文档&#xff1a;https://docs.docker.com/ Docker GitHub仓库&#xff1a;https://github.com/docker Docker官方博客…

MT6893_天玑 1200芯片规格参数介绍_datasheet规格书

天玑 1200(MT6893)是一款专为旗舰级全新5G芯片&#xff0c;它融合了先进的AI、相机和多媒体技术&#xff0c;为用户带来令人惊叹的体验。采用先进的6纳米制程设计&#xff0c;内置各种先进技术。该芯片采用旗舰级的八核CPU架构设计&#xff0c;支持16GB强大的四通道内存以及双通…

jQuery_03 dom对象和jQuery对象的互相转换

dom对象和jQuery对象 dom对象 jQuery对象 在一个文件中同时存在两种对象 dom对象: 通过js中的document对象获取的对象 或者创建的对象 jQuery对象: 通过jQuery中的函数获取的对象。 为什么使用dom或jQuery对象呢&#xff1f; 目的是 要使用dom对象的函数或者属性 以及呢 要…

DQN算法

DQN算法 教程链接 DataWhale强化学习课程JoyRL https://johnjim0816.com/joyrl-book/#/ch7/main DQN算法 DQN(Deep Q-Network) 主要创新点在于将Q-learning算法中的Q表记录动作价值函数转为引入深度神经网络来近似动作价值函数 Q ( s , a ) Q(s,a) Q(s,a),从而能够处理连续…

【Java 进阶篇】Jedis:让Java与Redis轻松对话的利器

在现代软件开发中&#xff0c;缓存系统是提高系统性能的常见手段之一&#xff0c;而Redis作为一个高性能的缓存数据库&#xff0c;被广泛应用于各类系统。如果你是Java开发者&#xff0c;那么使用Jedis库可以让你轻松地与Redis进行交互。本文将带你深入了解Jedis的快速入门&…

pat实现基于邻接矩阵表示的深度优先遍历[含非递归写法]

文章目录 1.递归2.非递归 1.递归 void DFS(Graph G, int v) {visited[v] 1;printf("%c ", G.vexs[v]);for (int i 0; i < G.vexnum; i) {if (!visited[i] && G.arcs[v][i]) DFS(G, i);} }2.非递归 #include <stack> #include <iostream> …

【间歇振荡器2片555时基仿真】2022-9-24

缘由multisim出现这个应该怎么解决吖&#xff0c;急需解决-嵌入式-CSDN问答 输出一定要有电阻分压才能前后连接控制否则一定报错。

【Web】PhpBypassTrick相关例题wp

目录 ①[NSSCTF 2022 Spring Recruit]babyphp ②[鹤城杯 2021]Middle magic ③[WUSTCTF 2020]朴实无华 ④[SWPUCTF 2022 新生赛]funny_php 明天中期考&#xff0c;先整理些小知识点冷静一下 ①[NSSCTF 2022 Spring Recruit]babyphp payload: a[]1&b1[]1&b2[]2&…

C#,《小白学程序》第二课:数组,循环与排序

1 什么是数组&#xff1f; 数组 Array 是一组数值&#xff08;数 或 值&#xff09;。 int[] a; int[,] b; int[][] c; Anything[] d; 都是数组。 2 排序 排序就是按大小、名字、拼音或你指定的信息进行比较后排队。 排序是数组最基本的功能需求。 3 文本格式 /// <summa…

【C语言】memset函数

memset是C和C编程语言中的一个函数&#xff0c;用于将指定的内存区域设置为特定的值。这个函数的原型在<string.h>&#xff08;对于C&#xff09;或者<cstring>&#xff08;对于C&#xff09;头文件中定义。 函数原型如下&#xff1a; void *memset(void *str, i…

Halcon Solution Guide I basics(4): Blob Analysis(连通性解析)

文章目录 文章专栏前言文章解析开头步骤分析简单案例进阶方案 进阶代码案例crystal&#xff0c;结晶匹配需求分析 文章专栏 Halcon开发 Halcon学习 练习项目gitee仓库 CSDN Major 博主Halcon文章推荐 前言 今天来看第三章内容&#xff0c;既然是零基础&#xff0c;而且我还有大…

数据库-MySQL之数据库必知必会10-13章

第10章 创建计算字段 拼接字段 使用Concat()函数 执行算术计算 示例&#xff1a;从 Products 表中返回 prod_id、prod_price 和 sale_price。sale_price 是一个包含促销价格的计算字段。提示&#xff1a;可以乘以 0.9&#xff0c;得到原价的 90%&#xff08;即 10%的折扣&…

C百题--7.输出乘法表

1.问题描述 输出9*9乘法表 2.解决思路 利用99乘法表行和列之间的关系&#xff0c;进行输出 注意&#xff1a;%-2d 2代表占两个字符&#xff1b;-代表左对齐 3.代码实现 #include<stdio.h> int main(){for(int i1;i<9;i){for(int j1;j<i;j){printf("%d*%d…

98、Text2Room: Extracting Textured 3D Meshes from 2D Text-to-Image Models

简介 github 利用预训练的2D文本到图像模型来合成来自不同姿势的一系列图像。为了将这些输出提升为一致的3D场景表示&#xff0c;将单目深度估计与文本条件下的绘画模型结合起来&#xff0c;提出了一个连续的对齐策略&#xff0c;迭代地融合场景帧与现有的几何形状&#xff0…

基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(一)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 为了增加流程节点配置的功能的灵活性与方便性&#xff0c;这里增加流程节点的配置。 1、相应表结构如下&a…

百度云加速免费版下线,推荐几款目前仍旧免费的CDN

近日&#xff0c;百度云加速实施了新政策&#xff0c;将不再支持免费套餐服务。现在免费的CDN也越来越少了&#xff0c;推荐几款目前仍旧免费的CDN&#xff0c;大家且用且珍惜&#xff01; 1、雨云【点此直达】 源站为雨云产品可以免费使用CDN&#xff0c;源站非雨云产品流量包…