【TA-霜狼_may-《百人计划》】图形4.2 SSAO算法 屏幕空间环境光遮蔽

  • @[TOC](【TA-霜狼_may-《百人计划》】图形4.2 SSAO算法 屏幕空间环境光遮蔽
  • 4.2.1 SSAO介绍
  • 4.2.2 SSAO原理
    • 深度缓冲
    • 法线缓冲
    • 法向半球
  • 4.2.3 SSAO算法实现
    • 获取深度&法线缓冲数据
    • 重建相机空间方法
      • 重建方法
      • 从NDC空间汇总重建
    • 构建法向量正交基
    • AO采样核心
  • 4.2.4 SSAO效果改进
    • AO累加的平滑优化
      • 范围判定(模型边界)
      • 自身判定
      • AO权重
      • 模糊
  • 4.2.5 比对模型烘焙AO效果
  • 4.2.6 SSAO性能消耗
  • 作业
  • 补充
    • HBAO

4.2.1 SSAO介绍

SSAO相关术语、简要理解

  • AO:环境光遮蔽,全称:Ambient Occlusion,是一种着色和渲染技术,模拟光线达到物体的能力的粗略的全局方法,描述光线到达物体表面的能力。
  • SSAO:屏幕空间环境光遮蔽,全称为Screen Space Ambient Occlusion。通过获取像素的深度缓冲、法线缓冲来计算实现,来近似的表现物体在间接光下差生的阴影。
  • SSAO的历史:AO技术2002年由工业光魔的技术主管Hayden Landis提出;2007年,Crytek公司将SSAO技术应用在代表作孤岛危机上。

4.2.2 SSAO原理

深度缓冲


深度缓冲中的depth值用于描述当前视点下,场景中的每一个像素距离相机的一个粗略表达,用于重构像素相机空间中的坐标(Z),来近似重构该视点下的三维场景。

法线缓冲

相机空间中的法线信息,用于重构每个像素的“法线-切线-副切线”构成的坐标轴,用于计算法线半球中的采样随机向量(随机向量用于判断和描述该像素的AO强度)

法向半球

  • 黑点代表我们需要计算的样本;
  • 蓝色向量表示样本的法向量;
  • 白色、灰色为采样点(很明显,采样点的多少影响最后的渲染效果),其中灰色表示被挡采样点(深度大于周围),据此判断最终AO强度。

右图表示法向球形采样,可以看到如果利用球形去计算,平整的墙面也至少有一半是灰点(即深度大于周围),导致最终的结果偏暗。

法向半球的作用就是提供采样点;

4.2.3 SSAO算法实现

获取深度&法线缓冲数据

C#部分:

private void Start(){cam = this.GetComponent<Camera>();cam.depthTextureMode = cam.depthTextureMode | DepthTextureMode.DepthNormals;

Shader部分:

// 获取深度法线图
sampler2D _CameraDepthNormalsTexture;
// 采样获得深度值和法线值
float3 viewNormal;
float linear01Depth;
float4 depthNormal = tex2D(_CameraDepthNormalsTexture, i.uv);
DecodeDepthNormal(depthNormal, linear01Depth, viewNormal);

此处相机渲染路径设置为:Forward, 如果设置为Deferred渲染路径,则由对应的g-buffer生成,在shader中通过全局变量进行访问;(延迟渲染中本身需要上述信息)

最后在OnRenderImage()中进行后期处理,进而实现SSAO;

重建相机空间方法

重建方法

“从NDC空间中重建”方法得到样本在相机空间中的向量,乘以深度值得到样本的坐标。

从NDC空间汇总重建

  1. 计算样本屏幕坐标(利用Unity内置函数)
float4 screenPos = ComputeScreenPos(o.vertex);
  1. 转换到NDC空间(归一化的设备坐标空间)中
float4 ndcPos = (screenPos / screenPos.w) * 2 - 1;
  1. 计算相机空间中远平面方向(内置变量_ProjectionParams.z存放相机远平面值far)
float3 clipVec = float3(ndcPos.x, ndcPos.y, 1.0) * _ProjectionParams.z;
  1. 矩阵变换至相机空间中的样本相对相机的方向
o.viewVec = mul(unity_CameraInvProjection, clipVec.xyzz).xyz;
  1. 重建相机空间中的样本坐标(在像素着色器中)
float3 viewPos = linear01Depth * i.viewVec;

在相机空间中,通过样本相对相机的方向以及深度,来拟合重构坐标。

构建法向量正交基

  1. 设置法向量
viewNormal = normalize(viewNormal) * float3(1, 1, -1);
  1. 生成随机向量
float3 randvec = normalize(float3(1, 1, 1));
  1. 求出切向量,再利用函数cross叉积求副切线向量
// 获得切向量
float3 tangent = normalize(randvec - viewNormal * dot(ranvec, viewNormal));
// 获得副切向量
float3 bitangent = cross(viewNormal, tangent);
// 得到正交基
float3x3 TBN = float3x3(tangent, bitangent, viewNormal);

AO采样核心

  1. 传入给定的随机采样向量,并通过法向量正交基转化至法线半球中的向量;
// 随机向量,转换到切线空间中
float3 randomVec = mul(_SampleKernekArray[i].xyz, TBN);
  1. 获取随机坐标点;
// 计算随机法线半球后的向量
float3 randomPos = viewPos + randomVec * _SampleKeneralRadius;
  1. 转换至屏幕空间坐标;
// 转换到屏幕坐标
float3 rclipPos = mul((float3x3)unity_CameraProjection, randomPos);
float2 rscreenPos = (rclipPos.xy / rclipPos.z) * 0.5 + 0.5;
  1. 计算随机向量转化至屏幕空间后对应的深度值,并判断累加AO
float randomDepth;
float3 randomNormal;
float4 rcdn = tex2D(_CameraDepthNormalsTexture, rscreenPos);
DecodeDepthNormal(rcdn, randomDepth, randomNormal);//判断是否对ao值进行累加
ao += (randomDepth >= linear01Depth) ? 1.0 : 0.0;

4.2.4 SSAO效果改进

  1. 为了不使得求得的法向半球的正交基完全一致,我们引入随机向量,从而创造不同的切向量。
  2. 利用UV至采样一张Noise贴图,或者随机向量。
// 铺平纹理
float2 noiseScale = _ScreenParams.xy /4.0;
float2 noiseUV = i.uv * noiseScale;
// randVec法线半球的随机向量
float3 randVec = tex2D(_NoiseTex, noiseUV).xyz;

AO累加的平滑优化

范围判定(模型边界)

样本采样,可能会采集到深度差非常大的随机点,会导致边界出现AO,如下图:

加入样本深度和随机点深度比较的范围判定,当超出阈值的时候不会进行ao遮蔽

float range =abs(randomDepth - linearDepth) > _RangeStrength ? 0.0 : 1.0;

自身判定

如果随机点深度值和自身一样或者非常接近(可能会导致虽然在同一个平面,也会出现AO),如图所示:

在判断深度值大小的时候,增加一个变量,来改善该问题:

float selfCheck = randomDepth + _DepthBiasValue < linear01Depth ? 1.0 : 0.0;

AO权重

AO深度判断,非0即1,比较生硬,为其增加权重,达到平滑的效果,可用权重例如:法线半球中随机采样点距离样本点的距离

float weight = smoothStep(0.2, 0, length(randomVec.xy));

模糊

4.2.5 比对模型烘焙AO效果

  1. 三维建模软件烘焙AO方式
  • 优点:

    1. 针对单一物体可控性强
    2. 弥补场景烘焙细节
    3. 不影响其(Unity场景中)静态或者动态
  • 缺点:

    1. 需要对模型进行UV处理,再进行烘焙到纹理;
    2. 不利于整个场景的整合;
    3. 增加AO纹理贴图,不利于资源优化;
    4. 只有物体本身具有AO信息,获取物体之间的AO信息工作量较大;
  1. 游戏引擎烘焙AO方式
  • 优点:

    1. 操作简单,整体场景的烘焙,包含AO的选择;
    2. 不受物体本身的UW影响;
    3. 可生成场景中物体与物体之间的AO信息;
  • 缺点:

    1. 缺少单一物体的细节
    2. 受物体是否静态影响,动态物体无法进行烘焙
  1. SSAO方式:
  • 优点:

    1. 不依赖场景复杂度;
    2. 实时计算;
    3. 可控性强;
  • 缺点:
    1. 性能损耗更多,代价较高;
    2. AO质量稍差一些;

4.2.6 SSAO性能消耗

  1. 由于法线半球中的点需要通过循环的方式进行判断,而GPU的并行运算并不能很好地支持For循环,所以性能大打折扣
  2. 采样数过低效果不好,采样数高则计算量巨大:以64为例,1334 x 750 的分辨率每个像素循环64次,则 共计 1334 * 750 * 64次AO值运算;
  3. 并且每次还要再采样贴图,导致更多的性能开销;
  4. 滤波采样将进行更多次的采样贴图,导致更多的性能开销;

作业


作业照着教程打了一遍。

补充

弄到最后碰到一个非常非常坑的问题,教程的文件中,c#中有个变量名存在拼写错误,但是shader中已经保持一致了,其实无伤大雅。但是我想将它改过来,一改问题就出来了。
当csharp脚本挂在相机上时,若我们直接对变量名称进行修改,系统缓存中对应的变量名称可能不会修改,即下图红圈中所示的这个变量名不会修改。该名称是通过对csharp文件中的变量名称按照大小写直接进行划分得到的。
最终结果就是虽然文件中的名称都改了,但是系统缓存中的名称没有改过来,导致在csharp往shader中传参数的过程中出现丢失,导致没有结果。
解决办法就是先把csharp文件拿掉,重新再贴一次,但是这样需要重新修改对应数值。
或者关闭unity再重新开一次即可更新。
坑死我了,真是救命!

c#部分:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MySSAO : MonoBehaviour
{private Material ssaoMaterial;private Camera cam;[Range(0.0f, 1.0f)]public float aoStrength = 0.0f;[Range(4, 64)]public int SampleKernelCount = 64;private List<Vector4> sampleKernelList = new List<Vector4>();[Range(0.0001f, 10f)]public float sampleKernelRadius = 0.01f;[Range(0.0001f, 1f)]public float rangeStrength = 0.001f;public float depthBiasValue;// 噪声贴图public Texture Noise;[Range(0, 2)]public int DownSample = 0;[Range(1, 4)]public int BlurRadius = 2;[Range(0, 0.2f)]public float bilaterFilterStrength  = 0.2f;public bool OnlyShowAO = false;public enum SSAOPassName{GenerateAO = 0,BilateralFilter = 1,Composite = 2,}private void Awake() {var shader = Shader.Find("Custom/100 learning/MySSAO");ssaoMaterial = new Material(shader);}// Start is called before the first frame updateprivate void Start(){cam = this.GetComponent<Camera>();cam.depthTextureMode = cam.depthTextureMode | DepthTextureMode.DepthNormals;}private void OnRenderImage(RenderTexture source, RenderTexture destination){GenerateAOSampleKernel();int rtW = source.width >> DownSample;int rtH = source.height >> DownSample;// AORenderTexture aoRT = RenderTexture.GetTemporary(rtW, rtH, 0);ssaoMaterial.SetVectorArray("_SampleKernelArray", sampleKernelList.ToArray());ssaoMaterial.SetFloat("_RangeStrength", rangeStrength);ssaoMaterial.SetFloat("_AOStrength", aoStrength);ssaoMaterial.SetFloat("_SampleKernelCount", sampleKernelList.Count);ssaoMaterial.SetFloat("_SampleKernelRadius", sampleKernelRadius);ssaoMaterial.SetFloat("_DepthBiasValue", depthBiasValue);ssaoMaterial.SetTexture("_NoiseTex", Noise);Graphics.Blit(source, aoRT, ssaoMaterial, (int)SSAOPassName.GenerateAO);// BlurRenderTexture blurRT = RenderTexture.GetTemporary(rtW, rtH, 0);ssaoMaterial.SetFloat("_BilaterFilterFactor", 1.0f - bilaterFilterStrength);ssaoMaterial.SetVector("_BlurRadius", new Vector4(BlurRadius, 0, 0, 0));Graphics.Blit(aoRT, blurRT, ssaoMaterial, (int)SSAOPassName.BilateralFilter);if(OnlyShowAO){Graphics.Blit(blurRT, destination, ssaoMaterial, (int)SSAOPassName.BilateralFilter);}else{Graphics.Blit(blurRT, aoRT, ssaoMaterial, (int)SSAOPassName.BilateralFilter);ssaoMaterial.SetTexture("_AOTex", aoRT);Graphics.Blit(source, destination, ssaoMaterial, (int)SSAOPassName.Composite);}RenderTexture.ReleaseTemporary(aoRT);RenderTexture.ReleaseTemporary(blurRT);}private void GenerateAOSampleKernel(){if(SampleKernelCount == sampleKernelList.Count){return;}sampleKernelList.Clear();for(int i = 0; i < SampleKernelCount; i++){var vec = new Vector4(Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f), Random.Range(0, 1.0f), 1.0f);vec.Normalize();var scale = (float) i / SampleKernelCount;// 使分布符合二次方程的曲线scale = Mathf.Lerp(0.01f, 1.0f, scale * scale);vec *= scale;sampleKernelList.Add(vec);}}
}

shader部分:

Shader "Custom/100 learning/MySSAO"
{Properties{[HideInspector]_MainTex("Texture", 2D) = "white" {}}CGINCLUDE#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;float3 viewVec : TEXCOORD1;float3 veiwRay : TEXCOORD2;};#define MAX_SAMPLE_KERNEL_COUNT 64sampler2D _MainTex;// 深度法线图sampler2D _CameraDepthNormalsTexture;// AOsampler2D _NoiseTex;float4 _SampleKernelArray[MAX_SAMPLE_KERNEL_COUNT];float _SampleKernelCount;float _SampleKernelRadius;float _DepthBiasValue;float _RangeStrength;float _AOStrength;v2f vertAo(appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = v.uv;//计算相机空间中的像素方向(相机到像素的方向)//https://zhuanlan.zhihu.com/p/92315967//屏幕纹理坐标float4 screenPos = ComputeScreenPos(o.vertex);// NDC positionfloat4 ndcPos = (screenPos / screenPos.w) * 2 - 1;// 计算至远屏幕方向float3 clipVec = float3(ndcPos.x, ndcPos.y, 1.0) * _ProjectionParams.z;// 利用逆投影矩阵将裁剪空间中的坐标转换到观察空间中o.viewVec = mul(unity_CameraInvProjection, clipVec.xyzz).xyz;return o;}// AO计算fixed4 fragAo(v2f i) : SV_TARGET{// 采样屏幕纹理fixed4 col = tex2D(_MainTex, i.uv);// 采样获得深度值和法线值float3 viewNormal;float linear01Depth;float4 depthnormal = tex2D(_CameraDepthNormalsTexture,i.uv);DecodeDepthNormal(depthnormal,linear01Depth,viewNormal);// 获取像素相机屏幕坐标位置float3 viewPos = linear01Depth * i.viewVec;// 获取观察空间屏幕法线,法线z方向相对于相机为负(所以需要乘以-1)并进行归一化viewNormal = normalize(viewNormal) * float3(1, 1, -1);// 铺平纹理float2 noiseScale = _ScreenParams.xy / 4.0;float2 noiseUV = i.uv * noiseScale;// randvec法线半球的随机向量float3 randvec = tex2D(_NoiseTex, noiseUV).xyz;// Gramm-Schimidt处理创建正交基// 法线&切线&副切线构成的坐标空间float3 tangent = normalize(randvec - viewNormal * dot(randvec, viewNormal));float3 bitangent = cross(viewNormal, tangent);float3x3 TBN = float3x3(tangent, bitangent, viewNormal);// 采样核心float ao = 0;// 每个像素点上的采样次数int sampleCount = _SampleKernelCount;for(int i = 0; i < sampleCount; i++){// 随机向量,转换至切线空间中float3 randomVec = mul(_SampleKernelArray[i].xyz, TBN);// ao权重float weight = smoothstep(0, 0.2, length(randomVec.xy));// 计算随机法线半球后的向量float3 randomPos = viewPos + randomVec * _SampleKernelRadius;// 转换到屏幕坐标float3 rclipPos = mul((float3x3)unity_CameraProjection, randomPos);float2 rscreenPos = (rclipPos.xy / rclipPos.z) * 0.5 + 0.5;float randomDepth;float3 randomNormal;float4 rcdn = tex2D(_CameraDepthNormalsTexture, rscreenPos);DecodeDepthNormal(rcdn, randomDepth, randomNormal);// 判断累加ao值float range = abs(randomDepth - linear01Depth) > _RangeStrength ? 0.0 : 1.0;float selfCheck = randomDepth + _DepthBiasValue < linear01Depth ? 1.0 : 0.0;// 采样点的深度值和样本深度比对前后关系ao += range * selfCheck * weight;}ao = ao/sampleCount;ao = max(0.0, 1 - ao * _AOStrength);return float4(ao, ao, ao, 1);}//Blurfloat _BilaterFilterFactor;float2 _MainTex_TexelSize;float2 _BlurRadius;///基于法线的双边滤波(Bilateral Filter)//https://blog.csdn.net/puppet_master/article/details/83066572float3 GetNormal(float2 uv){float4 cdn = tex2D(_CameraDepthNormalsTexture, uv);  return DecodeViewNormalStereo(cdn);}half CompareNormal(float3 nor1,float3 nor2){return smoothstep(_BilaterFilterFactor,1.0,dot(nor1,nor2));}fixed4 frag_Blur (v2f i) : SV_Target{//_MainTex_TexelSize -> https://forum.unity.com/threads/_maintex_texelsize-whats-the-meaning.110278/float2 delta = _MainTex_TexelSize.xy * _BlurRadius.xy;float2 uv = i.uv;float2 uv0a = i.uv - delta;float2 uv0b = i.uv + delta;  float2 uv1a = i.uv - 2.0 * delta;float2 uv1b = i.uv + 2.0 * delta;float2 uv2a = i.uv - 3.0 * delta;float2 uv2b = i.uv + 3.0 * delta;float3 normal = GetNormal(uv);float3 normal0a = GetNormal(uv0a);float3 normal0b = GetNormal(uv0b);float3 normal1a = GetNormal(uv1a);float3 normal1b = GetNormal(uv1b);float3 normal2a = GetNormal(uv2a);float3 normal2b = GetNormal(uv2b);fixed4 col = tex2D(_MainTex, uv);fixed4 col0a = tex2D(_MainTex, uv0a);fixed4 col0b = tex2D(_MainTex, uv0b);fixed4 col1a = tex2D(_MainTex, uv1a);fixed4 col1b = tex2D(_MainTex, uv1b);fixed4 col2a = tex2D(_MainTex, uv2a);fixed4 col2b = tex2D(_MainTex, uv2b);half w = 0.37004405286;half w0a = CompareNormal(normal, normal0a) * 0.31718061674;half w0b = CompareNormal(normal, normal0b) * 0.31718061674;half w1a = CompareNormal(normal, normal1a) * 0.19823788546;half w1b = CompareNormal(normal, normal1b) * 0.19823788546;half w2a = CompareNormal(normal, normal2a) * 0.11453744493;half w2b = CompareNormal(normal, normal2b) * 0.11453744493;half3 result;result = w * col.rgb;result += w0a * col0a.rgb;result += w0b * col0b.rgb;result += w1a * col1a.rgb;result += w1b * col1b.rgb;result += w2a * col2a.rgb;result += w2b * col2b.rgb;result /= w + w0a + w0b + w1a + w1b + w2a + w2b;return fixed4(result, 1.0);}// 应用AO贴图sampler2D _AOTex;fixed4 frag_Composite(v2f i) : SV_Target{fixed4 col = tex2D(_MainTex, i.uv);fixed4 ao = tex2D(_AOTex, i.uv);col.rgb *= ao.r;return col;}ENDCGSubShader{Cull OffZWrite OffZTest Always// Pass 0: Generate AOPass{CGPROGRAM#pragma vertex vertAo#pragma fragment fragAoENDCG}// Pass 1: Bilateral Filter BlurPass{CGPROGRAM#pragma vertex vertAo#pragma fragment frag_BlurENDCG}// Pass 2: Composite AOPass{CGPROGRAM#pragma vertex vertAo#pragma fragment frag_CompositeENDCG}   }
}

其实法向半球的那个比较部分还不是特别理解,再去看看games202。

HBAO

先挖个坑
教程:大佬的笔记,看看能不能照着实现一下HBAO

【TA-霜狼_may-《百人计划》】图形4.2 SSAO算法 屏幕空间环境光遮蔽相关推荐

  1. 【技美百人计划】图形 4.2 SSAO算法 屏幕空间环境光遮蔽(&HBAO)

    笔记 SSAO介绍 AO 环境光遮蔽,AmbientOcclusion.一种模拟光线到达物体的能力和粗略的全局方法. SSAO 屏幕环境光遮蔽,Screen Space Ambient Occlusi ...

  2. 图形 4.2 SSAO算法 屏幕空间环境光遮蔽

    链接: SSAO算法 屏幕空间环境光遮蔽思维导图. SSAO算法 屏幕空间环境光遮蔽 SSAO介绍 什么是AO SSAO原理详解 SSAO介绍 SSAO原理 计算近似AO SSAO算法实现 比较与分析 ...

  3. 百人计划 图形2.1 色彩空间

    色彩发送器 色彩认知:光源是出生点,光源发出光线,光线通过直射反射折射等路径最终进入人眼.在接收到光线后,人眼产生了一系列化学反应.由此把产生的信号传入大脑,大脑对颜色产生了认知感知. 光的要素: 光 ...

  4. 百人计划 图形1.4 PC手机图形API介绍

    前言 电脑工作原理:电脑是有各种不同的硬件组成,由驱动软件驱使硬件进行工作.所有的如软件工程师都会直接或间接使用到驱动. 定义:是一个针对GPU的图形库,用于渲染2D.3D矢量图形的跨语言.跨平台的应 ...

  5. 百人计划 图形 2.5 BUMP图改进

    基础感念 凹凸贴图技术是对物体表面贴图进行变化然后再进行光照计算的一种技术.例如给法线分量添加噪音,或者在一个保存扰动值的纹理图中进行查找,这是一种提升物体真实感的有效办法,但却不需要额外的提升物体的 ...

  6. 百人计划 图形2.2 模型与材质基础

    渲染流水线大致过程 1.顶点数据输入到顶点着色器中进行相关的顶点计算,然后进行图元装配,通过点与点之间的关系将点进行连接.2.再到几何着色器(可选着色器)进行图元的增加,再到光栅化通过遍历像素点将一个 ...

  7. 【TA-霜狼_may-《百人计划》】图形3.4 延迟渲染管线介绍

    [TA-霜狼_may-<百人计划>]图形3.4 延迟渲染管线介绍 @[TOC]([TA-霜狼_may-<百人计划>]图形3.4 延迟渲染管线介绍 3.4.1 渲染路径 3.4. ...

  8. 【TA-霜狼_may-《百人计划》】图形3.7.2 command buffer简

    [TA-霜狼_may-<百人计划>]图形3.72command buffer 及urp概述 @[TOC]([TA-霜狼_may-<百人计划>]图形3.72command buf ...

  9. 【TA-霜狼_may-《百人计划》】图形4.5 DoF景深基础

    [TA-霜狼_may-<百人计划>]图形4.5 Dof景深基础 @[TOC]([TA-霜狼_may-<百人计划>]图形4.5 Dof景深基础 4.5.1 景深 离散圈 4.5. ...

最新文章

  1. 深度学习点云语义分割:CVPR2019论文阅读
  2. windows 打开.ipynb文件
  3. python第三天課程:int, bool, str
  4. VTK修炼之道62:体绘制_固定点光线投影体绘制与GPU加速光线投影体绘制
  5. 这可能是最生动的加密相关科普文章
  6. 生成随机数 java
  7. aspx 页面,master页面与ascx用户控件传值的问题
  8. ASTER文字识别详解
  9. C++入门系列博客二 C++ 控制流
  10. 小美赛之matlab笔记
  11. java并行计算π_3月14日圆周率日—使用并行计算求圆周率π
  12. python持久层框架_python网络爬虫——scrapy框架持久化存储
  13. 互联网数据响应时间计算公式
  14. 解决MySQL登录1045错误
  15. 问佛----如果浮躁了,静下来看看
  16. linux下twm切换gn,Linux初学(CnetOS7 Linux)之切换命令模式和图形模式的方法
  17. 【数论】欧几里得算法
  18. 欧比特“珠海一号”遥感微纳卫星星座02组卫星发射成功!
  19. 鼎捷t100架构_鼎捷T100 管理软件
  20. 【Niagara Vykon N4 】物联网学习 03照明控制及照明时间表

热门文章

  1. 向任意平面的投影矩阵的推导
  2. 新版毒蘑菇搜索,简单方便,居家旅行必备
  3. sql中的execute与sp_executesql
  4. 适合小团队协作、任务管理、计划和进度跟踪的项目任务管理工具有哪些?
  5. 2019-01-01T00:00:00.000Z 格林尼治时间
  6. uni-app开发微信小程序,关于获取手机号事件绑定的坑
  7. 安装Vista系统 电脑需满足的十大要素
  8. 华秋DFM软件迭代升级,让用户拥有更好的体验
  9. jquery 验证Email邮箱格式的几个例子
  10. 【SpringBoot】引入mybatis及连接Mysql数据库