bresenham算法画圆mfc实现_在Unity中实现日食的效果(一)

v2-7e8adf4a8b994a48ad68588e8b709d12_1440w.jpg?source=172ae18b

前言:偶然间在Sandbox里发现一个很炫酷的效果,所以尝试在unity里实现了一下!另外非常推荐Sandbox和ShaderToy这两个网站,对于图形学算法的学习研究很有帮助,可以实时编译实时看到效果。由于这两个网站上面的效果都是以屏幕UV来绘制的,所以最初在Unity里的后处理阶段进行模拟比较好,以下最终效果!

v2-0a258d214a704da06f40d3fb74e250ec.jpg
Unity里实现的效果https://www.zhihu.com/video/1128015282010320896

核心思路:这个算法的思路非常巧妙也很简单,就是画圆,一个在屏幕中心画表示太阳的位置,一个在鼠标位置画,用来模拟月球!然后判断两者的距离,小于半径则被遮挡,绘制成黑色!

具体实现过程分析:

  1. 圆的绘制:我们知道在一个平面内,一动点以一定点为中心,以一定长度为距离旋转一周所形成的封闭曲线叫做。正好,我们可以用一个不限定方向只限定长度的向量来绘制。这样不考虑Z轴的情况下,在XY平面内由无数个相同长度的向量形成图形就是圆。向量的长度也就是圆的半径,如图所示:

v2-e8314e4a36601bd6107807df2ed7c86c_b.jpg


所以,只要在UV空间下算出到UV中心点相同距离的UV对应的像素,然后填充相应的颜色即可,例如以OpenGL坐标系为例,在屏幕中心绘制一个半径为0.09的圆形,如图所示:

v2-2009c22ee53d97b599f0aaf837bfa36a_b.jpg

代码如下

half3 day = half3(0.2, 0.3, 0.5);
if (length(pos) < 0.09) 
{
    return half3(1.2, 1.1, 0.9);
}

return day;//day就是除了圆以外的颜色值即天空的颜色

实现效果如下

v2-fb52a857c298b2cc94a7a541c0854ed5_b.jpg

2.然后绘制鼠标处的圆,我们可以从C#中获取鼠标在屏幕中的位置,转换到UV空间(0~1)范围内,因为我们都是以(0.5,0.5)为中心的坐标系来进行计算,为了对应(0,0)为中心坐标系的各点,往(-0.5,-0.5)方向做一次平移

v2-9af2adf8a7ae4ff021bf67d1255226e9_b.jpg

最后根据屏幕比例做适配,代码如下:

half2 gl_FragCoord = ((i.srcPos.xy / i.srcPos.w) );
half2 m = mouse.xy/ _ScreenParams.xy - 0.5;//(mouse是传进来鼠标在屏幕中的参数,_ScreenParams是unity内置取屏幕中的坐标的参数,两者都以pixel为单位)
float aspect = _ScreenParams.x / _ScreenParams.y;
half2 position = (gl_FragCoord.xy ) - 0.5;
position.x *= aspect;
m.x *= aspect;

绘制鼠标处的圆,算出像素和鼠标之间的距离在半径范围内则绘制成黑色

v2-585ccccc15e1038b5cf13c928532c7ee_b.jpg
	if (distance(pos, m) < 0.08)
                        {
				return half3(0,0.0,0);
			}

实现效果如下

v2-7fd6345f2a8b22a87a77fdda15782b30_b.gif
算出两个圆

3.绘制出白天和黑天的过渡

算出鼠标到中心的距离小于中心圆的半径,则判定太阳被遮挡,背景颜色往黑色转换,反之往白天颜色转换,但是这样的转换太过生硬,没有平缓过渡的过程,如图:

float lm = length(m);
if (lm < 0.09) {
		 return lerp(night, day, lm );
	        }

v2-47ecc4ec52b36e392ce26adb464b60f8_b.gif

所以我们把判定的半径范围加大,这样在靠近太阳的时候,天空就有颜色的变化,然后根据鼠标距离算插入值的时候将精度缩小一倍,精度缩小的值应该是经验值得出的,这样可以使月亮完全进入太阳范围内时,天空再完全变暗,修改后效果如下:

float lm = length(m);
if (lm < 0.5) {
		 return lerp(night, day, lm / 0.5);
	      }
v2-732045631d568eefda43bb5cdc49122b.jpg
平缓过度的效果https://www.zhihu.com/video/1128311552318832640

4.绘制出光晕效果

这个日食的精髓就在这里,想法很巧妙,简单的计算就能模拟出遮光的效果,核心原理和上面一样就是判断一个圆是否在另外一个圆内,只不过把之前的基础圆扩展成n个,从物理的角度来考虑,太阳肯定不仅仅是只有半径范围内发亮,肯定是中心最亮,随着距离亮度不断在衰减。所以我们按照中心距离绘制出不同半径的圆,然后进行叠加,但是直接叠加,肯定屏幕会爆掉,我们要根据叠加的次数对亮度进行衰减,代码如下:

half3 light = half3(1.0,1.0,1.0);
half iterations = 10;
half2 incr = position / float(iterations);
half2 p = half2(0.0, 0.0) + incr;
for (int i = 2; i < iterations; i++) 
{
   light += getColor(p, m);
   p += incr;
}
light /= float(iterations);//亮度衰减

v2-4cecd79962827eb70269d5e9f6916b4c_b.jpg
10次叠加的效果

v2-fadd0e25df59df0a5aec5ef797fd0ca4_b.jpg
50次叠加的效果

注意:大家应该很奇怪边缘黄色的效果是怎么出来的,大家应该发现前面绘制太阳的时候,为什么不直接给half3(1,1,1),而是一个溢出值

if (length(pos) < 0.09)
 {
	return half3(1.2, 1.1, 0.9);
 }

我们把溢出值单独提出来,转换成颜色值在ps查看对应的颜色

v2-720d2d241498b426ee608be7dd86a8af_b.jpg

发现这是一个暗黄色的值

经过不断叠加颜色会越来越偏黄色
n个太阳圆和n个月亮圆之间判断出遮挡关系

效果如下:

v2-6166fad59fc14cecc4b5aa757516697a.jpg
https://www.zhihu.com/video/1128962389680418816

5.绘制星空

利用正弦函数构造一个随机分布的函数绘制出星空

v2-130375dd070bbc3d3a4a707aa1d7883c_b.jpg

完整代码如下:

Shader "Unlit/RiShi"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fog
			
		#include "UnityCG.cginc"
	

		const int iterations = 50;

    	

	    uniform float time;
	    uniform half3 mouse;
		
	  //  uniform half2 resolution;


		half3 getColor(half2 pos, half2 m) {
			half3 day = half3(0.2, 0.3, 0.5);
			half3 night = half3(0.1, 0.1, 0.1);

			if (distance(pos, m) < 0.08) {
				return half3(0,0.0,0);
			}
			if (length(pos) < 0.09) {
				return half3(1.2, 1.1, 0.9);
			}

			float lm = length(m);
			if (lm < 0.5) {
				return lerp(night, day, lm / 0.5);
			}
			return day;

		}

		float rand(float x) {
			float res = 0.0;

			for (int i = 0; i < 5; i++) {
				res += 0.240 * float(i) * sin(x * 0.68171 * float(i));

			}
			return res;

		}


			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				UNITY_FOG_COORDS(1)
				float4 vertex : SV_POSITION;
				float4 srcPos : TEXCOORD2;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.srcPos = ComputeScreenPos(o.vertex);
				UNITY_TRANSFER_FOG(o,o.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{

			half2 gl_FragCoord = ((i.srcPos.xy / i.srcPos.w) * _ScreenParams.xy);

			half2 m = mouse.xy/ _ScreenParams.xy - 0.5;
			float aspect = _ScreenParams.x / _ScreenParams.y;
			half2 position = (gl_FragCoord.xy / _ScreenParams.xy) - 0.5;
			position.x *= aspect;
			m.x *= aspect;


			half3 color = getColor(position, m);
			half3 light = half3(1.0,1.0,1.0);
			half iterations = 50;
			half2 incr = position / float(iterations);
			half2 p = half2(0.0, 0.0) + incr;
			for (int i = 2; i < iterations; i++) {
				light += getColor(p, m);
				p += incr;
			}

			light /= float(iterations) * max(.01, dot(position, position)) * 40.0;
			half2 star = gl_FragCoord.xy;
			
			if (rand(star.y * star.x) >= 2.1 && rand(star.y + star.x) >= .7) {
				float lm = length(m);
				if (lm < 0.15) {
					color = lerp(half3(2.0,2,2), half3(0.2, 0.3, 0.5), lm / 0.15);
				}
			}

			if (distance(position, m) < 0.05) {
				color = half3(0,0,0);
			}

			//return half4(color, 1);
				return half4(color+ light, 1.0);
			}
			ENDCG
		}
	}
}

后续:这种方法过于暴力,本来打算写一版公式解析的方法,但是没想到知乎的大佬分分钟就给写了出来,我就直接把链接给贴过来了

杨超:日食效果优化--解析法godray​zhuanlan.zhihu.com
v2-5357d971a345717c6e31f586a3d5967b_180x120.jpg
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值