unity 协程_用unity写光线追踪1:背景和向量

f1c89a5a1669a83955a8d2c43ab15a93.png

看了闫令琪大佬的图形学公开课,和默然(这里我就不加“大佬”二字了,除非他也出一系列教学视频)推荐的Ray Tracing in One Weekend,手痒痒,决定用unity来写一个玩玩。

我是初学者,文章里肯定有错误之处,还请大家积极指出。

我这里假设你已经有基础的几何知识和足够的编程经验,也看过闫令琪大佬的公开课,所以不会花费字数在讲解原理上。

我们开始吧。

1.背景

本项目基于屏幕后处理,但不写shader,我们用cpu来计算像素颜色值。所以你只需要建一个脚本,挂载在Camera上就好。

这个脚本的关键代码是:

Graphics.Blit(tex, dest);

tex是一个texture2D格式全局变量。texture2D格式可以定义长宽,设置每个像素的颜色,也能导出为图片。在本脚本我们把它显示在屏幕上,这行语句的作用正是如此。

如果你还未定义tex,你运行一下,应该是一片黑。

接下来我们定义这张图片。我们让它宽度为w,高为h,因为是其含义是像素个数,所以都是整型。用两个for循环来给图片上色,如果你的w和h取的超级大,那么上色会非常耗时。我推荐把上色代码写在协程里,这样就可以实时地看见上色进度了。这样的效率不及一次性计算完,但能看着色进度和结果还是很值的。

IEnumerator Render(int w, int h)
    {
        tex = new Texture2D(w,h);   //初始化图片
        for(int i = 0; i < w; i++)
        {
            for(int j = 0; j < h; j++)
            {

                tex.SetPixel(i, j, Color.Lerp(Color.white, new Color(1,0,0.5f), j/(float)h));
                tex.Apply();    //让图片显示刚填充的颜色
                yield return null;  //下一帧接着执行
            }
        }
        Debug.Log("---渲染完毕---");
        StopCoroutine(cor);     //养成好习惯,最后关闭协程
    }

我这里填充了渐变颜色,从白色到樱色。你运行一下,别把w和h设太大,设个320*180就可。便能看到一张由上而下,樱色变白的图片。

2.向量

现在进入正题,光线追踪。但首先我们需要的不是光线,而是一个世界。

这个世界由一个直角坐标系表示,此坐标系有x、y、z三个坐标轴,分别表示右、上、前三个方向。世界里的每一个点的位置可由三个数表示,分别代表那个点在你右方多少距离,上方多少距离,前方多少距离。假定你在(0, 0, 0)的位置(你是一个点,没有身高,没有宽度),那么(0, 1, 0)就表示你上方1个单位距离的点。这个单位距离可以随意取,我们假定是1米。而(2, 0, 3)就代表这个点在你右边2米的地方再往前3米的位置。

点的表示有了,很自然的就有了方向的表示方法。一个方向可以由一点指向另一点来描述,相应的,这两点的差就可以确定这个方向(方向是个很抽象的概念,我也不敢说理解了它,只是会使用它罢了)。两点的差很好算,相应的坐标相减就可以了。你在(2, 0, 3)的位置,我在(-1, 0, 2)的位置,那么从我到你的方向,就是你的位置减去我的位置,为(3, 0, 1);而你到我的方向,则是(-3, 0, -1)。

现在就可以表示光线了:一个起点加上方向,从起点出发,沿方向延申。很容易就能写出光线的类,只包含两个变量,一个起点,一个方向,都是Vector3类型;为了方便,我写了个构造方法。

    class MyRay
    {
        public Vector3 point;
        public Vector3 direction;

        public MyRay(Vector3 p, Vector3 d)
        {
            point = p;
            direction = d;
        }
    }

这个类是内部类——我们约定过只写一个脚本的。

3.用光线来取值

在第1部分,我们虽然给图片上了色,但,不够“光线追踪”,因为它不是靠光线来获取的颜色。现在我们来用光线获取颜色吧。

我们把光线的起点放在(0, 1.65, 0)的位置上(因为我眼睛距地面就是1.65米左右,这样更有代入感),从此起点朝各个方向发射光线。那么第一个问题就是,发射光线的范围有多大?总不能在360°里无死角发射光线吧?当然你可以这么干,结果就是失真,除了有特殊目的,也没啥用。这里我们有个“视野角度”的概念,Field angle of View,表示你能看到以你正前方向为角平分线,能看到多少度角内的景物。这个是横向视野的角度,纵向视野的角度,用图片的h/w高除以宽就能算出来。表示三维空间中的特定方向,需要两个角度:横向的和纵向的,也就是左右和俯仰。有了这个概念,你拿起草稿纸,欻欻欻一通算,以正前方为左右俯仰的0°方向,俯仰角φ向上为正,左右角θ向右为正,马上得到表示在你正前方偏右θ角度,偏上φ角度的向量(而且是归一化的):

这样你就可以很容易地写出覆盖视野的光线的方向了:

    Vector3 GetVectorFromAngle(float theta, float phi)
    {
        return new Vector3(
Mathf.Sin(theta)*Mathf.Cos(phi),
 Mathf.Sin(phi), 
Mathf.Cos(theta)*Mathf.Cos(phi)
);
    }

然后在更新一下协程函数,求出图片每个像素所对应的光线的向量。现在就可以用它来取颜色值了。你可以根据y值也就是图片的上下方向来取,也可以根据x值,图片的左右来取。这里我们取一张纹理的颜色,记得把它的Read/Write Enable勾上。

我们需要知道,光线会和纹理交于何处。这个很简单了,你想让纹理在哪,咋摆,求出相应平面的方程,和光线的直线方程联立求解即得交点坐标;比较复杂的是得到此交点对应的纹理颜色,这个需要你计算交点在纹理上的位置,需要耐心编程。

在这里我们让纹理充当背景,你大可以找一张风景图,或是色图,我随便找了一张二次元图。既然是背景,它必是无限大,所以无法用交点坐标取值,只能用方向。光线方向我们是有的;求出图片的uv值,再取色就完事了(用光线方向取值的话,就和光线起点无关了)。uv值的求法是关键,我们假设有一个平面在前方0.5米处,也就是平面z = 0.5。我们让光线和其相交,会得到一系列交点,如果你的fov不超过90的话(且h<=w,宽度不小于高度),这些交点的xy值都在[-0.5, 0.5]之间。只要都加上0.5,便会得到[0,1]内的值,变为可用的uv坐标。这一步,就是把z = 0.5,x,y∈[-0.5,0.5]²这一平面区域上的点,映射到所选背景纹理上。

计算向量(x0,y0,z0)和z=0.5平面的交点P是非常非常令人愉悦的。过程你们自己享受,我这里给出我的结果:

然后你就能得到一张以你选的纹理为背景的图片了。这当然一点也不炫酷。炫酷的是,颜色值不是直接从纹理上取的,而是用光线求交取的。

ec852bea92046b0cd89a15c9b584f567.png
fov为60,像素320*180

今天就到这里吧,下面是代码~!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RayTracing : MonoBehaviour
{
    Texture2D tex;
    Coroutine cor;  //协程的引用,用来关闭协程
    public float fov;
    public Texture2D backTex;   //背景纹理
    Vector3 stand;  //光线起点
    // Start is called before the first frame update
    void Start()
    {
        if(fov<=0)
        {
            fov=60; //防止忘写
        }
        stand = new Vector3(0, 1.65f, 0);
        cor = StartCoroutine(Render(320, 180));
    }

    IEnumerator Render(int w, int h)
    {
        float theta = fov*Mathf.PI / 180f;
        float phi = theta*h/w;
        float angleStep = theta/w;  //光线角度取值的步长
        tex = new Texture2D(w,h);   //初始化图片
        for(int i = 0; i < w; i++)
        {
            for(int j = 0; j < h; j++)
            {
                Vector3 dir = GetVectorFromAngle(i*angleStep-theta/2, j*angleStep-phi/2);
                MyRay ray = new MyRay(stand, dir);
                int u = (int)(backTex.width * (dir.x/(2*dir.z) + 0.5f));
                int v = (int)(backTex.height * (dir.y/(2*dir.z) + 0.5f));
                tex.SetPixel(i, j, backTex.GetPixel(u, v));
                
                tex.Apply();    //让图片显示刚填充的颜色
                yield return null;  //下一帧接着执行
            }
        }
        Debug.Log("---渲染完毕---");
        StopCoroutine(cor);     //养成好习惯,最后关闭协程
    }

    Vector3 GetVectorFromAngle(float theta, float phi)
    {
        return new Vector3(Mathf.Sin(theta)*Mathf.Cos(phi), Mathf.Sin(phi), Mathf.Cos(theta)*Mathf.Cos(phi));
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        Graphics.Blit(tex, dest);
    }

    class MyRay
    {
        public Vector3 point;
        public Vector3 direction;

        public MyRay(Vector3 p, Vector3 d)
        {
            point = p;
            direction = d;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
For those that do not know me: My name is Jacco Bikker, also known as 'Phantom'. I work as '3D tech guy' at Overloaded, a company that develops and distributes games for mobile phones. I specialize at 3D Symbian games, which require highly optimized fixed-point, non-HW-accelerated 3D engines, crammed into 250Kb installers. So basically I'm having fun. As software rendering used to be my spare time activity, I was looking for something else. I tried some AI, which was great fun, and recently I dove into a huge pile of research papers on raytracing and related topics; such as global illumination, image based lighting, photon maps and so on. One document especially grabbed my attention. It's titled: "State-of-the-Art in Interactive Ray Tracing", and was written by Wald & Slusallek. I highly recommend this paper. Basically, it summarizes recent efforts to improve the speed of raytracing, and adds a couple of tricks too. But it starts with a list of benefits of raytracing over rasterization-based algorithms. And one of those benefits is that when you go to extremes, raytracing is actually faster than rasterizing. And they prove it: Imagine a huge scene, consisting of, say, 50 million triangles. Toss it at a recent GeForce with enough memory to store all those triangles, and write down the frame rate. It will be in the vicinity of 2-5. If it isn't, double the triangle count. Now, raytrace the same scene. These guys report 8 frames per second on a dual PIII/800. Make that a quad PIII/800 and the speed doubles. Raytracing scales linearly with processing power, but only logarithmically with scene complexity. Now that I got your attention, I would like to move on to the intended contents of this crash course in raytracing.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值