Chapter9 更复杂的光照——Shader入门精要学习笔记

news2024/10/7 2:30:09

Chapter9 更复杂的光照

  • 一、Unity的渲染路径
    • 1.渲染路径的概念
    • 2.渲染路径的类型
      • ①前向渲染路径
        • a. 前向渲染路径的原理
        • b. Unity中的前向渲染
        • c. 两种Pass
      • ②延迟渲染路径
        • a. 延迟渲染路径的原理
        • b. Unity中的延迟渲染
        • c. 两种Pass
      • ③顶点照明渲染路径
  • 二、Unity的光源类型
    • 1.光源类型
      • ①平行光
      • ②点光源
      • ③聚光灯
    • 2.前向渲染中处理不同的光源
      • ①实践
        • Base Pass
        • Additional Pass
  • 三、Unity光照衰减
    • 1.用于光照衰减的纹理
    • 2.数学公式计算
  • 四、Unity的阴影
    • 1.阴影如何实现
      • ①Shadow Map的生成
      • ②屏幕空间的阴影映射技术
      • ③阴影映射
    • 4.Unity Shader 使用内置宏和函数 来统一管理光照衰减和阴影
      • ①光照衰减和阴影的影响
      • ②内置宏 UNITY_LIGHT_ATTENUATION
    • 5.透明物体阴影的办法
      • ①透明物体阴影问题
      • ②透明度测试 物体的阴影处理
      • ③透明度混合 物体的阴影处理
  • 五、标准的Unity Shader

一、Unity的渲染路径

1.渲染路径的概念

渲染路径是 Unity 处理光照信息的方式,它决定了光照是如何被应用到 Unity Shader 中的。简单来说,渲染路径就像一个“沟通桥梁”,它告诉 Unity 底层渲染引擎,开发者想要以哪种方式来处理光照,以及需要哪些光照信息

2.渲染路径的类型

大多数情况下一个项目只使用一个渲染路径,在Player Setting中进行Rendering Path设置。也可以在每个摄像机中设置该摄像机的渲染路径。完成设置后,就可以在每个Pass中 使用 LightMode 标签 来指定Pass使用的渲染路径。
在这里插入图片描述

①前向渲染路径

a. 前向渲染路径的原理
  • 它将每个光源的光照计算独立进行,并逐个应用到物体上
  • 每进行一次完整的前向渲染,需要渲染该对象的渲染图元,计算两个缓冲区信息:颜色缓冲区(更新颜色缓存区中的颜色值)和 深度缓冲区(决定一个片元是否可见)
  • 对每个逐像素光源都要进行一次这样的Pass渲染,如果有多个逐像素光源,就要进行多次
b. Unity中的前向渲染
  • 渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式
  • 前向渲染中有三种照亮物体的方式:逐顶点处理、逐像素处理、球谐函数(SH)处理
  • 决定光源用哪种处理模式取决于 类型 和 渲染模式
    • 类型:指光源是平行光还是什么
    • 渲染模式:指该光源是否重要,重要就是逐像素
      在这里插入图片描述
c. 两种Pass
  • 前向渲染路径通常包含两个 Pass
  • Base Pass: 计算环境光、最重要的平行光(1个)、逐顶点/SH 光源和 Lightmaps
    • 只会执行一次
  • Additional Pass: 计算额外的逐像素光源,每个光源对应一个 Pass(不支持阴影)
    • 每个逐像素光源会被调用一次
    • 还开启和设置了渲染模式,每个Additional Pass可以与上一次的光照结果在帧缓存中进行叠加

②延迟渲染路径

a. 延迟渲染路径的原理
  • 除了前向渲染用到的颜色缓冲和深度缓冲,延迟渲染还会用到G缓冲(G-buffer),存储了我们所关心的表面的其他信息(法线、位置、用于光照计算的材质属性)
b. Unity中的延迟渲染
  • 若光源数目多,前向渲染会造成性能瓶颈,就适合延迟渲染;延迟渲染中每个光源都可以按逐像素的方式处理
  • 缺点:
    • 不支持真正的抗锯齿(anti-aliasing)
    • 不能处理半透明物体
    • 对显卡有要求
c. 两种Pass
  • 第一个Pass:不进行任何光照计算,仅仅计算哪些片元是可见的(深度缓冲);如果该片元可见,就把相关信息存储到G缓冲区中
    • 漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度信息
  • 第二个Pass:利用G缓冲区中的信息进行真正的光照计算
  • 不依赖与场景复杂度,而是与屏幕空间的大小有关(缓冲区可以理解为2D图像,空间即为图像空间)

③顶点照明渲染路径

  • 一种简单的渲染方式,它只使用逐顶点光照,不支持阴影、法线映射等高级光照效果

二、Unity的光源类型

  • Unity中有4中类型:平行光、点光源、聚光灯和面光源(面光源仅在烘焙时用到)

1.光源类型

①平行光

没有具体位置,也没有衰减,所有点的方向都是一样的

②点光源

  • 照亮的范围可以由面板中Range来调,有光照衰减(衰减可以由函数定义)
  • 需要用 点光源的位置 - 某点位置 来得到该点的方向

③聚光灯

  • 是一块锥形区域,半径由Range来调,角度由Spot Angle来调,有光照衰减(衰减可以由函数定义)
  • 需要用 聚光灯的位置 - 某点位置 来得到该点的方向

2.前向渲染中处理不同的光源

  • 如何在Shader中访问5个属性:位置、方向、颜色、强度、衰减

①实践

// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
	Properties {
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		
		Pass {
			// Pass for ambient light & first pixel light (directional light)
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			// Apparently need to add this declaration 
			#pragma multi_compile_fwdbase	
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
			 	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));

			 	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
			 	fixed3 halfDir = normalize(worldLightDir + viewDir);
			 	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				fixed atten = 1.0;
				
				return fixed4(ambient + (diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	
		Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
				#endif
				
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				
				#ifdef USING_DIRECTIONAL_LIGHT
					fixed atten = 1.0;
				#else
					#if defined (POINT)
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT)
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif
				#endif

				return fixed4((diffuse + specular) * atten, 1.0);
			}
			
			ENDCG
		}
	}
	FallBack "Specular"
}

Base Pass
  • #pragma multi_compile_fwdbase 保证我们在shader中使用 光照衰减 等光照变量时可以被正确使用
#pragma multi_compile_fwdbase	
  • 在Base Pass中计算环境光照后,在Additional Pass中就不会再计算(物体自发光也是)
Additional Pass
  • 同样使用#pragma multi_compile_fwdadd 指令
  • 开启了Blend 命令,设置了混合模式,希望Additional Pass计算得到的光照结果可以在帧缓存中与之前的光照结果进行叠加
Pass {
			// Pass for other pixel lights
			Tags { "LightMode"="ForwardAdd" }
			
			Blend One One
		
			CGPROGRAM
			
			// Apparently need to add this declaration
			#pragma multi_compile_fwdadd
  • Additional Pass处理的光源类型可能是平行光、点光源或是聚光灯,在计算位置、方向、颜色、强度、衰减时,颜色和强度仍然可以使用 _LightColor0 来得到,由于位置、方向和衰减属性就需要根据光源类型分别计算
  • 方向
    如果是平行光,可以直接通过 _WorldSpaceLightPos0.xyz 来得到;如果是其他光源,_WorldSpaceLightPos0.xyz 表示的是光源在世界坐标中的位置,光源方向需要用这个位置减去世界空间下顶点的位置
			#ifdef USING_DIRECTIONAL_LIGHT
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				#else
					fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
				#endif
  • 衰减
			#ifdef USING_DIRECTIONAL_LIGHT
					fixed atten = 1.0;
				#else
					#if defined (POINT)
				        float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
				        fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #elif defined (SPOT)
				        float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
				        fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
				    #else
				        fixed atten = 1.0;
				    #endif
				#endif

三、Unity光照衰减

1.用于光照衰减的纹理

  • 在Unity内部使用一张名为 _LightTexture0 的纹理来计算光源衰减(通常只关心对角线上的纹理颜色值),比如(0,0)点表示与光源重合的点的衰减值,(1,1)点表示了在光源中距离最远的点的衰减
  • 为了对_LightTexture0纹理采样得到给定点到该光源的衰减值,先知道点在光源空间中的位置 ,_LightMatrix0 为把顶点从世界坐标变换到光源空间的矩阵,与世界空间中的顶点坐标相乘即可
  • 再使用坐标的模的平方对衰减纹理进行采样
float3 lightCoord = mul(_LightMatrix0, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

2.数学公式计算

float3 distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
fixed atten = 1.0 / distance;

四、Unity的阴影

1.阴影如何实现

  • 让一个物体向其他物体投影
  • 在实时渲染中,经常使用Shadow Map,先把摄像机放在光源位置上,光源的阴影区域就是摄像机看不到的区域

①Shadow Map的生成

  • 摄像机位置:将摄像机放置在与光源重合的位置
  • ShadowCaster Pass:使用 LightMode 标签为 ShadowCaster 的 Pass 专门更新光源的阴影映射纹理。这个 Pass 渲染的目标不是帧缓存,而是阴影映射纹理(或深度纹理)

②屏幕空间的阴影映射技术

  • 根据光源的阴影映射纹理和摄像机的深度纹理得到屏幕空间的阴影图
  • 步骤
    • 通过调用 LightMode 标签为 ShadowCaster 的 Pass 来得到 光源的阴影映射纹理相机的深度纹理
    • 根据 光源的阴影映射纹理相机的深度纹理 得到屏幕空间的阴影图
    • 若摄像机深度图中记录的表面深度 > 转换到阴影映射纹理中的深度值 → \rightarrow 物体表面虽然可见,但出于该光源的阴影中

③阴影映射

  • 让物体 接收 来自其他物体的投影
    • 在Shader中队阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果与光照结果相乘得到阴影
  • 让物体向其他 投射阴影
    • 把该物体加到光源的阴影映射纹理计算中(通过为该物体执行 LightMode 标签为 ShadowCaster 的 Pass 来实现的)

4.Unity Shader 使用内置宏和函数 来统一管理光照衰减和阴影

①光照衰减和阴影的影响

  • 两个对物体最终的渲染效果本质上是相同的,都是通过将衰减因子和阴影值与光照结果相乘得到最终结果
  • 可以使用一个方法来同时计算这两个信息

②内置宏 UNITY_LIGHT_ATTENUATION

  • 包含进需要的头文件 #include “AutoLight.cginc”
  • 在v2f结构体中使用内置宏 SHADOW_COORDS 声明阴影坐标
struct v2f{
	float4 pos : SV_POSITION;
	float3 worldNormal : TEXCOORD0;
	float3 worldPos : TEXCOORD1;
	SHADOW_COORDS(2);
};
  • 在顶点着色器中使用内置宏 TRANSFER_SHADOW 计算并向片元着色器传递阴影坐标
v2f vert(a2v v){
	v2f o;
	...
	TRANSFER_SHADOW(o);
	return o;
}
  • 在片元着色器中使用内置宏 UNITY_LIGHT_ATTENUATION 来计算光照衰减和阴影,有三个参数
    • 第一个:用于存储光照衰减和阴影值相乘后的结果
    • 第二个:结构体 v2f ,用于传递内置宏计算阴影值
    • 第三个:世界空间的坐标,计算光源空间下的坐标,再对光照衰减纹理采样来得到光照衰减
fixed4 frag(v2f i) : SV_Target {
	...
	UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
	return fixed4(ambient + (diffuse + specular) * atten, 1.0);
} 
  • Base Pass 和 Additional Pass 代码得到统一,不需要在Base Pass里单独处理阴影,也不需要在Additional Pass中判断光源类型来处理光照衰减

5.透明物体阴影的办法

①透明物体阴影问题

  • 透明物体的实现通常会使用透明度测试或透明度混合,需要小心设置这些物体的 Fallback
  • 使用 VertexLit、Diffuse、Specular 等作为回调,往往无法得到正确的阴影(不透明物体可以,VertexLit中有ShadowCaster Pass),因为这些 Shader 中的 ShadowCaster Pass 没有进行透明度测试的计算。

②透明度测试 物体的阴影处理

  • 需要提供一个有透明度测试功能的 ShadowCaster Pass
  • Fallback “VertexLit” 改为 “Transparent/Cutout/VertexLit”
  • Cude 的 Mesh Renderer 的Cast Shadows 设置为 Two Sided
    在这里插入图片描述

③透明度混合 物体的阴影处理

  • 因为透明度混合需要关闭深度写入,这会带来阴影生成的问题,所以所有内置的透明度混合的 Unity Shader 都没有包含阴影投射的 Pass,因此,这些半透明物体不会向其他物体投射阴影,也不会接收来自其他物体的阴影
  • 可以使用一些 dirty trick 来强制为半透明物体生成阴影
    • 把它们的 Fallback 设置为 VertexLit、Diffuse 等不透明物体使用的 Unity Shader
    • 通过物体的 Mesh Renderer 组件上的 Cast Shadows 和 Receive Shadows 选项来控制是否需要向其他物体投射或接收阴影
      在这里插入图片描述

五、标准的Unity Shader

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

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

相关文章

【简单讲解神经网络训练中batch的作用】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…

IP地址与电商企业

网购作为我们现代生活不可或缺的部分,现如今电商企业蓬勃发展。 IP地址是网络世界中每一台设备的独特标识符,就像现实世界中每家每户的门牌号。对于电商企业而言,它在很多方面方面发挥着作用。 IP地址能够帮助电商企业精准地确定用户所在的地…

2024广州智能音箱展|广州蓝牙耳机展

2024广州智能音箱展|广州蓝牙耳机展 时间:2024年11月29日-12月1日 地点:广州琶洲保利世贸博览馆 【展会简介】 中国是全球最大的音频产品制造基地和消费市场,随着国内外互联网巨头纷纷瞄准音频行业并投入巨资布局AI产品矩阵,音…

思考如何学习一门编程语言?

一、什么是编程语言 编程语言是一种用于编写计算机程序的人工语言。通过编程语言,程序员可以向计算机发出指令,控制计算机执行各种任务和操作。编程语言由一组语法规则和语义规则组成,这些规则定义了如何编写代码以及代码的含义。 编程语言…

C++ ariac2 Windows库编译

cd "F:\\aria2" gmp-6.1.2.tar.lz expat-2.2.0.tar.bz2 sqlite-autoconf-3160200.tar.gz zlib-1.2.11.tar.gz c-ares-1.12.0.tar.gz libssh2-1.8.0.tar.gz --enable-libaria2 --enable-static libgnutls-dev(对于HTTPS,BitTorrent&#xff0…

探囊取物之多形式注册页面(基于BootStrap4)

基于BootStrap4的注册页面,支持手机验证码注册、账号密码注册 低配置云服务器,首次加载速度较慢,请耐心等候;演练页面可点击查看源码 预览页面:http://www.daelui.com/#/tigerlair/saas/preview/ly4gax38ub9j 演练页…

墨烯的Java技术栈-数据结构与算法基础-010

(前言 这是在之前容器很多的不知名的名词 想着与其一个个解释不如直接重温一遍数据结构) 一.概念 程序 数据结构 算法 程序的本质 数据结构经常可以说为计算机内功(心法) 而编程能力就是招式(法术) 考研 必考专业课 一共四门专业课 共150分 找工作更不用说:面试主要考…

AcWing 1256:扩展二叉树

【题目来源】https://www.acwing.com/problem/content/1258/【题目描述】 由于先序、中序和后序序列中的任一个都不能唯一确定一棵二叉树,所以对二叉树做如下处理,将二叉树的空结点用 补齐,如图所示。 我们把这样处理后的二叉树称为原二叉树…

Linux系统之 — 线程

Linux系统之 — 线程 线程介绍线程使用死锁(Deadlock)竞态条件(Race Condition) 线程使用示例服务器端代码示例服务器端示例拆解1. 引入头文件和宏定义2. 定义全局变量3. 定义线程函数4. 主函数5. 错误处理和资源释放 客户端代码示…

谷歌重磅:告别RAG,长上下文的大语言模型无需检索增强

当今人工智能领域正在经历一场静默的革命。随着大语言模型(LLM)的快速发展,它们不仅能够处理更长的上下文,还展现出惊人的推理和检索能力。 难道我们要告别基于LLM的检索增强生成(RAG)了吗? 结果还真是这样,最近谷歌发布专门用于…

k8s公网集群安装(1.23.0)

网上搜到的公网搭建k8s都不太一致, 要么说的太复杂, 要么镜像无法下载, 所以写了一个简洁版,小白也能一次搭建成功 使用的都是centos7,k8s版本为1.23.0 使用二台机器搭建的, 三台也是一样的思路1.所有节点分别设置对应主机名 hostnamectl set-hostname master hostnamectl set…

一文搞懂 java 线程池:ThreadPoolExecutor 和 FixedThreadPool 原理

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…

数据结构-排序算法篇

前言 在我们的生活中有很多东西都是有大小的,那么该如何去排序?假设有10个数字要你去排序,眼睛一扫就看出来了,那100、1000、10000要怎么去排?下面就为大家介绍各种排序的算法。 内容 1.冒泡排序 2.选择排序 3.插入…

某Dota/IM对战平台玩家助手、查看战绩下、胜率等

功能说明 WAR3游戏启动后,可以自动获取游戏双方的玩家列表,然后查询显示玩家的战绩及个人信息。附带查看玩家的战绩详情、最近游戏,查看对手及友方的战绩详情,据此推算出是否开黑、是否小号等信息 使用方法及运行效果 启动 查…

武汉星起航:贴心服务引领,跨境电商成功启航

在当今全球互联互通日益加强的背景下,跨境电商已经跃升为驱动国际贸易繁荣的重要引擎。作为全球电商领域的翘楚,亚马逊坚守公平、公正、透明的商业准则,为全球卖家搭建了一个值得信赖的交易平台。在这个平台上,众多卖家通过提升产…

如何指定Microsoft Print To PDF的输出路径

在上一篇文章中,介绍了三种将文件转换为PDF的方式。默认情况下,在Microsoft Print To PDF的首选项里,是看不到输出路径的设置的。 需要一点小小的手段。 运行输入 control 打开控制面板,选择硬件和声音下的查看设备和打印机 找到…

Django 多对多关系

多对多关系作用 Django 中,多对多关系模型的作用主要是为了表示两个模型之间的多对多关系。具体来说,多对多关系允许一个模型的实例与另一个模型的多个实例相关联,反之亦然。这在很多实际应用场景中非常有用,比如: 博…

Ceyear®VSA 信号分析软件

CeyearVSA 信号分析软件 CeyearVSA 矢量信号分析软件 CeyearVSA 矢量信号分析软件将信号分析体验和测试应用于桌面,帮助排查问题并优化设计。 CeyearVSA 矢量信号分析软件结合仪表支持在线解调分析,也可支持信号导入离线分析;软件具有多种…

搜狐新闻HarmonyOS版本 push 推送开发

背景 搜狐新闻作为HarmonyOS的合作伙伴,于2023年12月成功上架鸿蒙单框架应用市场,成为首批鸿蒙应用矩阵的一员。 新闻类推送作为应用的重要组成部分,在二期规划中,我们将推送功能列为核心功能模块。本文将推送集成过程中的步骤和…

oracle体系结构详解(实例+数据文件)

提示:主要总结oracle数据库:物理结构,逻辑结构,内存结构以及oracle进程 文章目录 Oracle服务器由(实例和数据库文件组成)1、实例2、数据文件1.oracle物理体系结构2.oracle数据库逻辑结构3oracle数据库内存结…