Unity 基于法线和深度实现完美描边,可独立控制物体描边

news2024/11/16 5:25:32

在这里插入图片描述

目录

  • 前言
  • 自定义PostProcess
  • OutlineShader关键代码说明
    • 1 使用深度绘制描边
      • 1.1 获得斜四方形UV坐标:
      • 1.2 采样四方向深度
    • 2 使用法线绘制描边
    • 3 解决倾斜表面白块问题
      • 3.1 计算视方向
      • 3.2 使用视方向修正阈值
    • 4 单独控制物体是否显示描边
  • OutlineShader完整代码

前言

最近项目需要快速出一版卡通渲染风格进行吸量测试。但是原来的模型非常不适合使用back face 的描边方案(很难看),不得已寻求其他的描边方案,于是有了现在这篇基于法线和深度的后处理描边。

优点:

  • 描边宽度一致。
  • 重叠部分也能有描边。
  • 不会出现断裂
    缺点:
  • 后处理时有一定消耗(全屏采样8次)

本文是基于buildin 渲染管线,非URP。(老项目,没办法)
本文会使用自定义post-processing,目的是可以和其他的post-processing效果结合,方便使用

不熟悉post-processing 的同学可以看下面这个文章:
PostProcessing的使用

自定义PostProcess

using System;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;

[Serializable]
[PostProcess(typeof(PostProcessOutlineRenderer), PostProcessEvent.BeforeStack, "Post Process Outline")]
public sealed class PostProcessOutline : PostProcessEffectSettings
{
    //声明变量
    public IntParameter scale = new IntParameter { value = 1 };
    public FloatParameter  depthThreshold = new FloatParameter  { value = 1 };
    [Range(0, 1)]
    public FloatParameter normalThreshold = new FloatParameter { value = 0.4f };
    [Range(0, 1)]
    public FloatParameter depthNormalThreshold = new FloatParameter { value = 0.5f };
    public FloatParameter depthNormalThresholdScale = new FloatParameter { value = 7 };
    public ColorParameter color = new ColorParameter { value = Color.white };
}

public sealed class PostProcessOutlineRenderer : PostProcessEffectRenderer<PostProcessOutline>
{
    public override void Render(PostProcessRenderContext context)
    {
    //将面板变量对Outline shader赋值
        var sheet = context.propertySheets.Get(Shader.Find("Hidden/Outline Post Process"));
        sheet.properties.SetFloat("_Scale", settings.scale);
        sheet.properties.SetFloat("_DepthThreshold", settings.depthThreshold);
        sheet.properties.SetFloat("_NormalThreshold", settings.normalThreshold);
        Matrix4x4 clipToView = GL.GetGPUProjectionMatrix(context.camera.projectionMatrix, true).inverse;
        sheet.properties.SetMatrix("_ClipToView", clipToView);
        sheet.properties.SetFloat("_DepthNormalThreshold", settings.depthNormalThreshold);
        sheet.properties.SetFloat("_DepthNormalThresholdScale", settings.depthNormalThresholdScale);
        sheet.properties.SetColor("_Color", settings.color);
        context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
    }
}

添加这个脚本后,我们就可以在PostProcess面板上进行输入参数控制
在这里插入图片描述
使用properties.SetXXX 将数据传入后处理Shader中

sheet.properties.SetFloat("_Scale", settings.scale);

后面Shader所使用的外部数据基本都由这里输入。
完整的Shader代码会放在最后。

OutlineShader关键代码说明

1 使用深度绘制描边

1.1 获得斜四方形UV坐标:

float halfScaleFloor = floor(_Scale * 0.5);
float halfScaleCeil = ceil(_Scale * 0.5);

float2 bottomLeftUV = i.texcoord - float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y) * halfScaleFloor;
float2 topRightUV = i.texcoord + float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y) * halfScaleCeil;  
float2 bottomRightUV = i.texcoord + float2(_MainTex_TexelSize.x * halfScaleCeil, -_MainTex_TexelSize.y * halfScaleFloor);
float2 topLeftUV = i.texcoord + float2(-_MainTex_TexelSize.x * halfScaleFloor, _MainTex_TexelSize.y * halfScaleCeil);

i.texcoord 是屏幕UV
_Scale 是我们用来调整描边粗细的参数。通过floor 和ceil, 使得halfScaleFloor 和 halfScaleCeil 在调整_Scale时,以整数改变。通过这种方式,我们就可以让边缘检测像素以原UV位置向4个斜方向每次增加1像素。(因为深度和法线图是用point filtering采样的,不存在插值,所以我们使用整数增加)
在这里插入图片描述
在这里插入图片描述

1.2 采样四方向深度

float depth0 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, bottomLeftUV).r;
float depth1 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, topRightUV).r;
float depth2 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, bottomRightUV).r;
float depth3 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, topLeftUV).r;

如果我们查看depth0的结果,可以看到下图
在这里插入图片描述
离摄像机越近,颜色越亮,越远则越暗。
这时我们就可以用两两深度图相减,这样两图深度相近的地方颜色值就非常小。深度差别大的地方则相反。

float depthFiniteDifference0 = depth1 - depth0;
float depthFiniteDifference1 = depth3 - depth2;

return abs(depthFiniteDifference0) * 100;//为了方便查看结果扩大100倍

得到下图:
在这里插入图片描述
现在将两张深度图的平方相加再开方(The Roberts Cross 边缘检测方法)

float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2)) * 100;
return edgeDepth;

获得下图:
在这里插入图片描述
这个时候,一些外围边缘已经清晰可见。但是我们还是能在物体表面看到一些灰色区域。所以我们需要把较低的深度值过滤掉。
使用阈值_DepthThreshold

edgeDepth = edgeDepth > _DepthThreshold ? 1 : 0;
return edgeDepth;	

在_DepthThreshold 值设定为0.2时,得到下图
在这里插入图片描述
这样,我们就解决了灰色区域。但是我们也注意到前方有一个Cube的顶部全部填充的白色,后方的两个Cube重叠区域没有描边,梯子和前方方块有些边缘没有绘制。

我们先解决后方Cube的问题。
因为深度值是非线性的,越到后面深度相差就越小。那么我们就要根据深度值改变阈值大小。

float depthThreshold = _DepthThreshold * depth0;
edgeDepth = edgeDepth > depthThreshold ? 1 : 0;

再将_DepthThreshold 改到1.5 我们可以看到
在这里插入图片描述
这样,后面重叠的Cube也能看到边缘了。
接下来我们要解决一些边缘缺失的问题。

2 使用法线绘制描边

为了获得所有物体法线数据,我们需要一个摄像机,来绘制法线图。然后将法线图保存到一个Shader全局变量中:_CameraNormalsTexture

为主摄像机添加脚本:

using UnityEngine;

public class RenderReplacementShaderToTexture : MonoBehaviour
{
    [SerializeField]
    Shader replacementShader;

    [SerializeField]
    RenderTextureFormat renderTextureFormat = RenderTextureFormat.ARGB32;

    [SerializeField]
    FilterMode filterMode = FilterMode.Point;

    [SerializeField]
    int renderTextureDepth = 24;

    [SerializeField]
    CameraClearFlags cameraClearFlags = CameraClearFlags.Color;

    [SerializeField]
    Color background = Color.black;

    [SerializeField]
    string targetTexture = "_RenderTexture";

    private RenderTexture renderTexture;
    private new Camera camera;

    private void Start()
    {
        foreach (Transform t in transform)
        {
            DestroyImmediate(t.gameObject);
        }

        Camera thisCamera = GetComponent<Camera>();

        // Create a render texture matching the main camera's current dimensions.
        renderTexture = new RenderTexture(thisCamera.pixelWidth, thisCamera.pixelHeight, renderTextureDepth, renderTextureFormat);
        renderTexture.filterMode = filterMode;
        // Surface the render texture as a global variable, available to all shaders.
        Shader.SetGlobalTexture(targetTexture, renderTexture);

        // Setup a copy of the camera to render the scene using the normals shader.
        GameObject copy = new GameObject("Camera" + targetTexture);
        camera = copy.AddComponent<Camera>();
        camera.CopyFrom(thisCamera);
        camera.transform.SetParent(transform);
        camera.targetTexture = renderTexture;
        camera.SetReplacementShader(replacementShader, "RenderType");
        camera.depth = thisCamera.depth - 1;
        camera.clearFlags = cameraClearFlags;
        camera.backgroundColor = background;
    }
}

通过SetReplacementShader,将场景中物体替换为绘制法线的Shader。得到法线图。
绘制法线Shader:

Shader "Hidden/Normals Texture"
{
    Properties
    {
    }
    SubShader
    {
        Tags 
		{ 
			"RenderType" = "Opaque" 
		}

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
				float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
				float3 viewNormal : NORMAL;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.viewNormal = COMPUTE_VIEW_NORMAL;
				//o.viewNormal = mul((float3x3)UNITY_MATRIX_M, v.normal);
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                return float4(normalize(i.viewNormal) * 0.5 + 0.5, 0);
            }
            ENDCG
        }
    }
}

点击Play按钮,我们能在生成的法线摄像机中看到下图:
在这里插入图片描述
这时,我们就可以在Outline Shader中使用法线数据了。
同样,我们进行斜四方向采样。

float3 normal0 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, bottomLeftUV).rgb;
float3 normal1 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, topRightUV).rgb;
float3 normal2 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, bottomRightUV).rgb;
float3 normal3 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, topLeftUV).rgb;

然后继续使用The Roberts Cross 方法计算边缘:

float3 normalFiniteDifference0 = normal1 - normal0;
float3 normalFiniteDifference1 = normal3 - normal2;

float edgeNormal = sqrt(dot(normalFiniteDifference0, normalFiniteDifference0) + dot(normalFiniteDifference1, normalFiniteDifference1));
edgeNormal = edgeNormal > _NormalThreshold ? 1 : 0;

return edgeNormal;

由于 normalFiniteDifference0 是个float3 向量,所以使用点乘Dot来代替平方。得到下图:
在这里插入图片描述
可以看到,通过法线比较获得的边缘,不会有一块白的区域。
并且我们得到了一些深度检测没有获得的边缘。
现将两个结果合并:

float edge = max(edgeDepth, edgeNormal);
return edge;

得到下图:
在这里插入图片描述

3 解决倾斜表面白块问题

因为深度检测的原因,倾斜表面像素间会有很大的深度差。所以容易产生白块。为了解决这个问题,我们还需要知道摄像机到表面的方向(view direction 视方向)

3.1 计算视方向

由于我们采样的法线图是在视空间(view space),那么我们也需要在视空间的视方向。为了得到它,我们需要摄像机的 裁减到视空间(clip to view) 或者 逆投影(inverse projection) 矩阵。
但是这个矩阵在默认的屏幕shader中是不能获得的,所以我们通过C#将矩阵传递进来。

在custom postprocess中的代码:

Matrix4x4 clipToView = GL.GetGPUProjectionMatrix(context.camera.projectionMatrix, true).inverse;
sheet.properties.SetMatrix("_ClipToView", clipToView);

于是,我们就可以在vert着色器中计算视空间中的视方向了。

o.vertex = float4(v.vertex.xy, 0.0, 1.0);
o.viewSpaceDir = mul(_ClipToView, o.vertex).xyz;

3.2 使用视方向修正阈值

得到视方向后,我们就可以得到NdotV(也成为菲列尔),表示法线和视方向的重合度。

//法线在0...1范围, 视方向在 -1...1范围,需要统一范围
float3 viewNormal = normal0 * 2 - 1;
float NdotV = 1 - dot(viewNormal, -i.viewSpaceDir);

return NdotV;

得到下图:
在这里插入图片描述
我们引入阈值_DepthNormalThreshold 来控制NdotV的影响范围
同时引入_DepthNormalThresholdScale控制调节范围在0 - 1之间

float normalThreshold01 = saturate((NdotV - _DepthNormalThreshold) / (1 - _DepthNormalThreshold));
float normalThreshold = normalThreshold01 * _DepthNormalThresholdScale + 1;

然后将新的法线阈值和深度阈值结合:

float depthThreshold = _DepthThreshold * depth0 * normalThreshold;

得到一个较完美的描边
在这里插入图片描述
最后合并图像原有颜色:

float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
float4 edgeColor = float4(_Color.rgb, _Color.a * edge);

return alphaBlend(edgeColor, color);

在这里插入图片描述
得到最终效果图。

4 单独控制物体是否显示描边

因为是后处理,通常情况下,看到的物体都会被描边。但是我们有时候又需要排除一些不需要描边的物体。这时我们可以通过控制材质球的shaderPassEnable来实现。

public class ShaderPassController : MonoBehaviour
{
    public bool value;
    public string passName = "Always";
    // Start is called before the first frame update
    void Start()
    {
        var mat = GetComponent<Renderer>().material;
        mat.SetShaderPassEnabled(passName, value);
    }
}

用于绘制法线的Shader 没有声明Tag。此时,Unity会默认设置Tag{“LightModel”=“Always”}
此时,我们可以通过SetShaderPassEnabled 关闭Shader中Tag为"Always"的Pass. 关闭后,此物体的Shader对于法线摄像机等于没有任何Pass,所以不会被绘制。当我们给茶壶添加ShaderPassController脚本后,法线摄像机如下图:
在这里插入图片描述
如果物体原本的shader使用的是“Always”Tag,为了防止被关闭Pass,可以添加tags {“LightModel”=“ForwardBase”} 来规避
此时,我们只需判断没有法线数据的点就是没有描边就可以了。

if (normal3.r == 1 && normal3.g == 1 && normal3.b == 1
	&& normal0.r == 1 && normal0.g == 1 && normal0.b == 1
	&& normal1.r == 1 && normal1.g == 1 && normal1.b == 1
	&& normal2.r == 1 && normal2.g == 1 && normal2.b == 1)
	{
		edge = 0;
	}

得到下图:
在这里插入图片描述

OutlineShader完整代码

Shader "Hidden/Outline Post Process"
{
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
			// Custom post processing effects are written in HLSL blocks,
			// with lots of macros to aid with platform differences.
			// https://github.com/Unity-Technologies/PostProcessing/wiki/Writing-Custom-Effects#shader
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag

			#include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"

			TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
			// _CameraNormalsTexture contains the view space normals transformed
			// to be in the 0...1 range.
			TEXTURE2D_SAMPLER2D(_CameraNormalsTexture, sampler_CameraNormalsTexture);
			TEXTURE2D_SAMPLER2D(_CameraDepthTexture, sampler_CameraDepthTexture);
        
			// Data pertaining to _MainTex's dimensions.
			// https://docs.unity3d.com/Manual/SL-PropertiesInPrograms.html
			float4 _MainTex_TexelSize;
            float _Scale;
            float _DepthThreshold;
            float _NormalThreshold;
            float4x4 _ClipToView;
            float _DepthNormalThreshold;
			float _DepthNormalThresholdScale;
            float4 _Color;

			// Combines the top and bottom colors using normal blending.
			// https://en.wikipedia.org/wiki/Blend_modes#Normal_blend_mode
			// This performs the same operation as Blend SrcAlpha OneMinusSrcAlpha.
			float4 alphaBlend(float4 top, float4 bottom)
			{
				float3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
				float alpha = top.a + bottom.a * (1 - top.a);

				return float4(color, alpha);
			}
            struct Varyings
			{
				float4 vertex : SV_POSITION;
				float2 texcoord : TEXCOORD0;
				float2 texcoordStereo : TEXCOORD1;
				float3 viewSpaceDir : TEXCOORD2;
				#if STEREO_INSTANCING_ENABLED
					uint stereoTargetEyeIndex : SV_RenderTargetArrayIndex;
				#endif
			};

            Varyings Vert(AttributesDefault v)
			{
				Varyings o;
				o.vertex = float4(v.vertex.xy, 0.0, 1.0);
            	o.viewSpaceDir = mul(_ClipToView, o.vertex).xyz;
				o.texcoord = TransformTriangleVertexToUV(v.vertex.xy);

			#if UNITY_UV_STARTS_AT_TOP
				o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0);
			#endif

				o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0);

				return o;
			}
            

			float4 Frag(Varyings i) : SV_Target
			{
				
				float halfScaleFloor = floor(_Scale * 0.5);
				float halfScaleCeil = ceil(_Scale * 0.5);

				float2 bottomLeftUV = i.texcoord - float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y) * halfScaleFloor;
				float2 topRightUV = i.texcoord + float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y) * halfScaleCeil;  
				float2 bottomRightUV = i.texcoord + float2(_MainTex_TexelSize.x * halfScaleCeil, -_MainTex_TexelSize.y * halfScaleFloor);
				float2 topLeftUV = i.texcoord + float2(-_MainTex_TexelSize.x * halfScaleFloor, _MainTex_TexelSize.y * halfScaleCeil);

				float depth0 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, bottomLeftUV).r;
				float depth1 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, topRightUV).r;
				float depth2 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, bottomRightUV).r;
				float depth3 = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, topLeftUV).r;

				float3 normal0 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, bottomLeftUV).rgb;
				float3 normal1 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, topRightUV).rgb;
				float3 normal2 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, bottomRightUV).rgb;
				float3 normal3 = SAMPLE_TEXTURE2D(_CameraNormalsTexture, sampler_CameraNormalsTexture, topLeftUV).rgb;
	
				float3 normalFiniteDifference0 = normal1 - normal0;
				float3 normalFiniteDifference1 = normal3 - normal2;

				float edgeNormal = sqrt(dot(normalFiniteDifference0, normalFiniteDifference0) + dot(normalFiniteDifference1, normalFiniteDifference1));
				edgeNormal = edgeNormal > _NormalThreshold ? 1 : 0;
				
				float depthFiniteDifference0 = depth1 - depth0;
				float depthFiniteDifference1 = depth3 - depth2;
				float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2)) * 100;
				
				float3 viewNormal = normal0 * 2 - 1;
				float NdotV = 1 - dot(viewNormal, -i.viewSpaceDir);
				float normalThreshold01 = saturate((NdotV - _DepthNormalThreshold) / (1 - _DepthNormalThreshold));
				float normalThreshold = normalThreshold01 * _DepthNormalThresholdScale + 1;

				float depthThreshold = _DepthThreshold * depth0 * normalThreshold;
				edgeDepth = edgeDepth > depthThreshold ? 1 : 0;

				float edge = max(edgeDepth, edgeNormal);

				if (normal3.r == 1 && normal3.g == 1 && normal3.b == 1
					&& normal0.r == 1 && normal0.g == 1 && normal0.b == 1
					&& normal1.r == 1 && normal1.g == 1 && normal1.b == 1
					&& normal2.r == 1 && normal2.g == 1 && normal2.b == 1)
				{
					edge = 0;
				}
				
				float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
				float4 edgeColor = float4(_Color.rgb, _Color.a * edge);

				return alphaBlend(edgeColor, color);
			}
			ENDHLSL
		}
    }
}

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

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

相关文章

github上传代码(亲测实用)

又被github上传代码折腾了我3个小时&#xff0c;各种问题都遇到过&#xff0c;最后写篇博客记录一下&#xff0c;方便后续上传。 github创建项目完成后&#xff0c;就会出现上传指令&#xff0c;如下图所示&#xff1a; 现在只需要按着命令的提示一步步执行&#xff1b; 1.点…

一文读懂HTTPS

大家第一次接触 HTTPS 协议的时候是不是和我一样&#xff0c;非常困惑。 这玩意概念又多又繁琐。尤其是里面的公钥私钥啥的。 当时就特别想知道&#xff0c;为什么用公钥加密却不能用公钥解密&#xff1f; 看完这篇文章你会弄明白&#xff0c;同时还会解锁很多HTTPS里的细节…

ansible的安装

自定义环境 1.操作环境 角色主机名IP地址组名 控制主机 server.example.com 192.168.90.134 server 受控主机 node1.example.com 192.168.90.135 node1 受控主机 node2.example.com 192.168.90.133 node2 需要保准三台主机能够互相通信。设置同一种网络模式&#xff0…

Tic-Tac-Toe可能棋局搜索的实现(python)

目录 1. 前言 2. 算法流程 3. 代码实现 3.1 终局及胜负判定方法 3.2 搜索邻节点 3.3 打印棋盘状态 3.4 代码 4. 小结 1. 前言 Tic-Tac-Toe中文常译作井字棋&#xff0c;即在3 x 3的棋盘上&#xff0c;双方轮流落子&#xff0c;先将3枚棋子连成一线的一方获得胜利。Tic-…

✿✿✿JavaScript --- jQuery框架一

目 录 1.jQuery的介绍和在线学习网址以及下载网址 2.jQuery的功能和优势 3.引用jQuery库和第一个案例 4.jQuery代码格式和注释 5.jQuery如何达到获取原生的DOM对象 6.jQuery选择器&#xff08;CSS对比版&#xff09; (1)常见选择器 (2)高级选择器以及方法 (3)属性选择…

单片机基础知识

目录 一、单片机基本认知 二、IO口输入和输出 三、点亮一个LED 1、编程实现LED闪烁 2、按下按键点亮灯 3、按键的消抖 4、记录状态位来控制LED 一、单片机基本认知 单片机和PC电脑相比的话&#xff0c;相当于电脑的主板 单片机是一种集成电路芯片。单片机又称单片微控…

S32DS_Optimization优化选项

S32DS_Optimization优化选项 S32DS3.4的选项, 右击工程后出现的选项 char is signed 让char类型为有符号, 类似signed char bitfield is unsigned 当声明不使用signed/unsigned时, 控制位字段是否无符号; 默认signed(因为基本整形(int等)也是signed) Function sections 默认情…

C语言—动态内存管理和柔性数组

目录 1. C/C的内存开辟 2. 为什么存在动态内存分配 3. 动态内存函数介绍 3.1 malloc 和 free 3.2 calloc() 3.3 realloc() 4. 常见的动态内存错误 4.1 对NULL指针的解引用操作 4.2 对动态开辟的空间越界访问 4.3 对非动态内存开辟的空间进行free&#xff08;&…

外贸谈判前需要注意的4p

01Past了解客户的过去 当一个客户找到我们的时候&#xff0c;作为业务&#xff0c;我们需要第一时间回复客户所问到的问题。让客户感受到&#xff0c;他需要的产品我们公司可以提供。于此同时&#xff0c;在客户沟通意愿度较高的时候&#xff0c;我们不妨多跟客户沟通一下几个问…

Java学习笔记 --- MySQL-索引和事务

一、索引 索引的原理 1、没有索引会全表扫描&#xff0c;从而查找速度会很慢 2、使用索引会形成一个索引的数据结构&#xff0c;比如二叉树 3、索引的代价 磁盘占用 对 dml&#xff08;update、delete、insert&#xff09;语句的效率影响 索引的类型 1、主键索引&#xff…

2022年圣诞节 | 用代码实现简单圣诞树

2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 一、前言 本文我们用 Python 来画一棵带背景音乐效果的雪夜圣诞树以及使用 HTMLCSSJS 在页面渲染出动态圣诞树&#xff0c;所涉及到的源码均来自GitHub开源站点。 二、效果展示 Python HTMLCSSJS 三、编码实现 …

(六)汇编语言——包含多个段的程序

目录 使用数据 使用栈 代码 总结 使用数据 首先&#xff0c;我们来看一个问题&#xff0c;就是编程计算0123H&#xff0c;0456H&#xff0c;0789H&#xff0c;0abcH&#xff0c;0defH&#xff0c;0fedH&#xff0c;0cbaH&#xff0c;0987H的和&#xff0c;结果存在ax寄存器…

《剑指offer》每日三题

这里使用的是题库&#xff1a; https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 07. 重建二叉树剑指 Offer 14- I. 剪绳子剑指 Offer 14- II. 剪绳子 II剑指 Offer 07. 重建二叉树 递归思想&#xff1a; 代码 class Solution {int pPre0;//用于遍历preorde…

深入理解HashMap

HashMap集合 1. HashMap集合简介 HashMap基于哈希表的Map接口实现,是以key-value存储形式存在,即主要用来存放键值对。hashMap的实现不是同步的&#xff0c;这就意味着它不是线程安全的。它的key、value都可以为null。此外&#xff0c;HashMap中的映射不是有序的。 JDK1.8之前…

短视频上热门技巧总结,这样做你也可以快速上热门。

最近开始做短视频&#xff0c;找了很多短视频运营创作技巧&#xff0c;但能上热门的只有那么几个&#xff0c;经过近一周的分析&#xff0c;结合了我赢上短视频运营创作技巧&#xff0c;得到了以下几个经典技巧合集&#xff1a;学会一个就值了。 首先说一下&#xff1a;什么样的…

团簇生长过程-Ovito渲染

文章目录一、选择出团簇原子和非团簇原子1. 选择团簇原子2. 删除非团簇原子二、选择出团簇原子和非团簇原子1. 团簇分析2. 团簇具体信息三、渲染团簇1、 对团簇进行选择2、 获得团簇渲染后的结果四、渲染结果五、 案例dump下载博文《根据近邻列表法识别团簇—冷凝成核 MatlabOv…

java06-面向对象1

一&#xff1a;面向对象学习内容&#xff1a; 1.java 类及成员&#xff1a;属性、方法、构造器、代码块、内部类 2.面向对象三大特征&#xff1a;封装、继承、多态 3.其他关键字&#xff1a;this、super static、final、abstact、interface 、package、import 二&#xff…

iPhone/iPad上值得推荐的5个免费PDF转Word

PDF 文件是在不同平台上传输数据的最便捷方式&#xff0c;可确保保持高端信息质量。处理将不同文件格式转换为 PDF 的任务通常很麻烦&#xff0c;尤其是在 iOS 设备上。为了解决这个问题&#xff0c;这里讨论了您可以轻松依赖的前 5 个iPhone PDF 转换器工具。 适用于 iPhone 和…

多传感器融合定位六-惯性导航原理及误差分析

多传感器融合定位六-惯性导航原理及误差分析1. 惯性技术简介1.1 惯性技术发展历史1.2 惯性器件1.2.1 机械陀螺(几乎没人用了)1.2.2 激光陀螺1.2.3 光纤陀螺1.2.4 MEMS陀螺(常用)1.2.5 加速度计2. 惯性器件误差分析2.1 信号误差组成2.2 Allan方差分析3. 惯性器件内参标定3.1 惯性…

十六、状态管理——Vuex(1)

本章概要 简单的状态管理安装 Vuex基本用法 Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。他采用集中式存储来管理应用程序中所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex 也被集成到了 Vue 官方调试工具 vue-devtools 中…