纯c++实现光线追踪渲染器

该博客介绍了一个自主研发的C++光线追踪渲染器,它利用CPU多线程而非GPU加速,成功渲染通用3D模型。文章探讨了实现反射、折射等效果,并对比传统渲染管线的优势。重点讲解了几种基本图元如球体、盒子、平面、三角形及公告板的渲染细节,以及如何解决公告板阴影对齐问题。此外,提供了三种优化光线追踪检测的策略,包括BSP树、经纬度空间分割和体素分割,分析了各自的优劣。

这是一个几年前用c++实现的光线追踪渲染器,使用cpu多线程计算,没有使用任何gpu加速。最后画面的呈现也是使用的gdi绘制,没有使用d3d或ogl。不同于某些基于shader的光线追踪限制性太大,对于通用模型的支持不好(只支持一些标准集合体,且模型都是通过算法产生的)。本渲染器可以支持通用模型的渲染。

光线追踪渲染框架下可以很容易的实现反射、折射、阴影、环境光遮蔽等。不同于传统的3D渲染管线,光线追踪逐个像素计算关照没有光栅化这一步,之前实现过一个软3D渲染管线,光栅化是主要的性能瓶颈。相信随着电脑算力的提高若干年后光线追踪渲染框架将会取代传统的3D管线,不止是渲染效果上更好,当模型面数达到足够数量级时,效率上也会超过传统管线。

本光线追踪渲染器实现了几种基本图元 sphere、box、quad、triangle、billboard。公告板图元是特有的,计算射线追踪时使用的是球形碰撞,取纹理时使用的是面向射线的公告板,阴影还有些问题(树根没对齐)。

三角形图元是最通用的图元,可以配合各类3d建模软件的导出模型。效率上还没优化到实时的地步。有可能目前的cpu算力还做不到实时。使用gpu可能会快一些,下一步可以试试cuda等通用gpu计算。

优化方案:

方案一, 使用bsp加速射线追踪检测,虽然提出效率是log的,但是面多了后还是不够快。

方案二, 以摄像机位置为原点,使用经纬度划分空间分割来加速检测,然后将屏幕分成小块绘制,一个小块只需要做一次剔除(仅初次追踪,镜面体反射后就不行了)可以极快的加速,缺点是当原点附近有大量三角形时效率急速变低。

方案三, 体素分割加速检测,由近到远遍历射线穿过的体素也方便即时返回,测试下来体素分割速度更快一些。

核心源码:

#pragma once
#include <windows.h>

#include "Mathlib.h"

//Phong shading。  光照=环境光+漫反射+高光

class TraceRes;
class Frustum;
class LaCone3Partion;
class UniformVoxelPartion;
class AnguleBoxPartion;
class RayTraceTexture;

//根据形状划分 保存在个位
enum PrimitiveType
{
	//PT_Point,
	//PT_Line,
	//PT_Circle, //圆面
	PT_Sphere = 0,
	PT_Ellipsoid,
	PT_Trigon,
	PT_Quad,
	PT_Sylinder,
	PT_Billboard,
};

//根据属性划分 保存在十位
enum PrimitiveType2
{
	PT_Mesh = 0,//面片
	PT_Light,   //光源
	PT_Stencil, //蒙版图元,射线击中后执行操作 比如skip ,或者未击中skip。
};

enum CullFace
{
	CF_None,
	CF_All,
	CF_Front,
	CF_Back,
};

enum FILTER
{
	Nearest=0,  
	Bilinear, 
	ANISOTROPIC,
};

//光线追踪没有光栅化,不好确定是放大滤波还是缩小滤波
//SAMP_MAGFILTER, 
//SAMP_MINFILTER, 

#define MaxPrimitive  4096
#define MaxThread 4
//不要虚函数 效率比较低
class Primitive
{
public:
	Primitive(PrimitiveType primitiveType);
	virtual ~Primitive(){};
	//仅拷贝
	void SetColorTexture(const vec3 &color, RayTraceTexture *texture);
	//计算了ODEta
	void SetRefleract(float reflection = 0.0f, float refraction = 0.0f, float Eta = 1.0f,RayTraceTexture* tex=NULL);

	//比switch(primitiveType)慢
	//virtual bool Intersect(vec3 &rayPos, const vec3 &rayDir, float maxDistance){return false;};

public:
	PrimitiveType primitiveType;
	RayTraceTexture *pTexture;
	//折射率保存在贴图中? 保存在普通的alpha通道一个char内容不下? 折射 反射 折射率
	RayTraceTexture *refleraTexture;
	bool  visible;
	CullFace  cullface;
	float reflection; //反射系数
	float refraction; //折射系数
	float Eta, ODEta; //折射率 及其倒数   简单的半透明图元或叠加高亮图元通过折射率0来模拟
	vec3  color;
	//
	//float lastDepth[18];  //缓存和上一个像素射线碰撞点的深度,很大的几率变化不大,用于快速排除 ?每个递归深度缓存一个  每线程单独缓存一份
	char  tag[MaxThread];

	//void* data;     //比如保存一个light光源指针?
	//包围球
	vec3  center;
	float radius;
	//包围盒
	AABB2 aabb;

	//todo 裁剪体 面 球 或box 限制交点在球或box的内或外才算碰撞,或在面的某一侧,?使用折射贴图跟快更灵活
	int laYIndexMax;
	int laYIndexMin;
	int laXIndexMax;
	int laXIndexMin;
	int laZIndexMax;
	int laZIndexMin;

	int loIndexMax1[6];
	int loIndexMin1[6];
	int loIndexMax2[6];
	int loIndexMin2[6];
};

class TrigonPrim:public Primitive
{
public:
	TrigonPrim();
	void SetPos(const vec3 &ptA, const vec3 &ptB, const vec3 &ptC);
	void SetPos(const vec3 &ptA, const vec3 &ptB, const vec3 &ptC,const vec3& nA,const vec3& nB,const vec3& nC);

	bool Inside(float x, float y, float z);
	bool Inside(const vec3 &point);

	//相交测试中进行alpha测试 缺点:不是最近点的图元没必要浪费alpha测试时间,最近图元不镂空部分也没必要alpha测试
	//                        优点:减少多加的追踪深度
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, TraceRes& traceRes);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance, vec3 &point);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance);

	void CalBound();

	//todo 多返回一个折射率贴图
	void SampleTexColor(const vec3&pos,vec3&texColor,vec3&refleraColor,FILTER filter);
	void SampleNormal(const vec3&pos,vec3& normal);


public:
	vec3  ptA, ptB, ptC;//顶点
	vec2  texA,texB,texC;//纹理坐标
	vec3  nA,nB,nC;      //法线 高洛德着色,不插值反射面会断开,插值的话正好平滑过渡
	vec3  dirAB, dirBC, dirCA; //边向量 注意后面两个有用到 dirAB指针++形式用到
	float lab, lbc, lca;

	vec3  N;          //面法线
	float D;
	vec3  N1, N2, N3; //边面法线
	float D1, D2, D3;

	/*
	*/
	vec3  fa, fb, fc; //投射到摄像机空间远裁剪面上的四个顶点

	//纹理插值
	float kVal[2];
	vec3  B_C,A_C; 
	bool  bLineXY;//xy 平面投影是否共线

	Polygon2D m_polygon;
};

class QuadPrim:public Primitive
{
public:
	QuadPrim();
	void SetPos(const vec3 &ptA, const vec3 &ptB, const vec3 &ptC, const vec3 &ptD);

	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, TraceRes& traceRes);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance, vec3 &point);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance);

	bool Inside(vec3 &point);
	void CalBound();

	void SampleTexColor(const vec3&pos,vec3&texColor,vec3&refleraColor,FILTER filter);

public:
	vec3  ptA, ptB, ptC, ptD; //顶点
	vec2  texA,texB,texC,texD;//纹理坐标
	vec3  B_A, D_A;     //边向量
	vec3  m;          //重心
	vec3  dirAB, B, O;
	float odDisAB,odDisAC;
	vec3  N;          //面法线
	float D;
	vec3  N1, N2, N3, N4;//边面法线
	float D1, D2, D3, D4;

	/*
	D C
	A B
	*/
	vec3  fa, fb, fc, fd; //投射到摄像机空间远裁剪面上的四个顶点

	Polygon2D m_polygon;

};

//
class SpherePrim:public Primitive
{
public:
	SpherePrim();
	void SetPos(const vec3 &Position, float Radius);

	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, TraceRes& traceRes);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance, vec3 &point);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance);
	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance);

	void CalBound();

public:
	float Radius2, ODRadius;

};

////
//class SylinderPrim:public Primitive
//{
//public:
//	SylinderPrim();
//	void SetPos(const vec3 &Position, float Radius);
//
//	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, TraceRes& traceRes);
//	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance, vec3 &point);
//	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance, float &distance);
//	bool Intersect(const vec3 &rayPos, const vec3 &rayDir, float maxDistance);
//	///** 检测是否与圆柱体碰撞 */
//	//int TestIntersionCylinder(const vec3& position,const vec3& direction, double& lamda, vec3& pNormal,vec3& newposition)
//	//{
//	//	vec3 RC;
//	//	double d;
//	//	double t,s;
//	//	vec3 n,D,O;
//	//	double ln;
//	//	double in,out;
//	//	vec3::subtract(position,cylinder._Position,RC);
//	//	vec3::cross(direction,cylinder._Axis,n);
//	//	ln = n.mag();
//	//	if ( (ln<ZERO)&&(ln>-ZERO) ) return 0;
//	//	n.unit();
//	//	d =  fabs( RC.dot(n) );
//	//	if (d <= cylinder._Radius)
//	//	{
//	//		vec3::cross(RC,cylinder._Axis,O);
//	//		t =  - O.dot(n)/ln;
//	//		vec3::cross(n,cylinder._Axis,O);
//	//		O.unit();
//	//		s =  fabs( sqrt(cylinder._Radius*cylinder._Radius - d*d) / direction.dot(O) );
//
//	//		in = t-s;
//	//		out = t+s;
//
//	//		if (in<-ZERO){
//	//			if (out<-ZERO) return 0;
//	//			else lamda = out;
//	//		}
//	//		else
//	//			if (out<-ZERO) {
//	//				lamda = in;
//	//			}
//	//			else
//	//				if (in<out) lamda = in;
//	//				else lamda = out;
//
//	//				newposition = position+direction*lamda;
//	//				vec3 HB = newposition-cylinder._Position;
//	//				pNormal = HB - cylinder._Axis*(HB.dot(cylinder._Axis));
//	//				pNormal.unit();
//
//	//				return 1;
//	//	}
//	//	return 0;
//	//}
//
//
//	void CalBound();
//
//public:
//	float Radius2, ODRadius;
//
//	vec3 _Axis;
//	//vec3 _Position;
//	//double _Radius;
//};


////
//class BillboardPrim:public QuadPrim
//{
//public:
//	BillboardPrim();
//public:
//	//没检测一次射线都生成新的四个顶点 法线等
//	QuadPrim m_originQuad;
//};

//
class BillboardPrim:public SpherePrim
{
public:
	BillboardPrim();
	//相交测试中进行alpha测试 缺点:不是最近点的图元没必要浪费alpha测试时间,最近图元不镂空部分也没必要alpha测试
	//                        优点:减少多加的追踪深度
public:
	//使用对称的球形碰撞,仅仅是纹理坐标计算有些许差别
};

//
class LightRT
{
public:
	LightRT();
	~LightRT();
	void Update();

public:
	float Ambient, Diffuse;
	float ConstantAttenuation, LinearAttenuation, QuadraticAttenuation;
	SpherePrim *Sphere;
	QuadPrim   *Quad;

	//平行光 分割使用体素或光柱分割
	//
	LaCone3Partion *m_laConePartion;

	////todo 异形发光体  ,光源颜色和图元颜色图元纹理混合作为最终的光源颜色,另外还具有灯光的方向衰减等普通属性
	//意义?:发光体表现可以通过叠加高亮模拟假象, 异形发光体的真正意义需要计算射线可追踪到的光源区域大小,来生成柔和的阴影边缘? 无影灯?
	//
	//int m_quadsCount;
	//int m_spheresCount;
	//int m_lightsCount;

	//灯光图元 可以和普通的场景各种类型图元混合一起划分在一个空间树中,这样碰撞检测效率提高2~3倍,但是带来逻辑混乱?

	//灯光图元 必须另外单独划分一颗空间树,以便阴影检测时提高效率
};

//
class TraceRes
{
public:
	TraceRes();
public:
	float distanceSofar;
	float testDistance;
	vec3  color, point, testPoint;
	int   traceDepth;
	Primitive *primitive;
	LightRT    *light;

	vec3  rayPos;
	vec3  rayDir;
};

class PrimitiveBspTree;
class RayTracerThread;

class Presenter
{
public:
	virtual void Present(int scrWidth,int scrHeight,BYTE* colorBuf)=0{};
protected:
	int  m_wndWidth;
	int  m_wndHeight;
	int  m_softBuffWidth;
	int  m_softBuffHeight;
};

//
class RayTraceRendDriver
{
public:
	RayTraceRendDriver();
	virtual ~RayTraceRendDriver();

	virtual bool Init();
	virtual void Close();
	virtual void OnSize(int Width, int Height);
	void        SetPresenter(Presenter* presenter);

	//!参数设置
	bool SetSamples(int Samples);
	int  GetSamples();

	bool SetQuality(float quality);

public:
	//!添加删除图元
	void CheckPrimitiveVolume(int addCount=1);
	void AddPrimitives(Primitive* pprimitives,int count);
	void AddPrimitives(Primitive** pprimitives,int count);
	
	void RemovePrimitives(Primitive* pprimitives,int count);
	void RemovePrimitives(Primitive** pprimitives,int count);

	//!绘制
	void ClearColorBuffer();
	void Render();
	void RenderRect(int x,int y,int xend,int yend,int threadIndex);
	void AfterEffectHDR();
	void Present();

	//
	//void		DrawPoint(int x, int y);
	//void		DrawLine(int x1, int y1, int x2, int y2);
	//void		DrawRect(const RectF& tar); 
	bool	    DrawTextureRect(const RectF& tar);

	//!分割准备
	//void GenFrustumCell(int frustumSize,int x,int y,int cellX,int cellY,int cellXEnd,int cellYEnd,Frustum&res);
	//void PreparePartionWithFrustum();
	void CalRat();

	//!分割图元
	void PartionWithBsp(PrimitiveBspTree* bspPartion);
	//void PartionWithLaLo(laLoPartion *laLoPartion);
	void PartionWithLaCone(LaCone3Partion *laConePartion);
	void PartionWithVoxel(UniformVoxelPartion* voxelPartion);
	void PartionWithAnguleBox(AnguleBoxPartion *anguleBoxPartion);

	//!标记图元  没有添加,const Primitive* except
	//void TagWithFrustumLaCone(int cellX,int cellY,int cellXEnd,int cellYEnd);
	//void TagWithFrustum(Frustum& frustum);
	void TagWithBsp(const vec3&pos,const vec3&dir);

	void DumpPrimitives(Primitive** pprimitives,int count);

//private:
	//!总光照计算
	void IlluminatePoint(const Primitive *except, const vec3 &point, const vec3 &normal, vec3 &color,int threadindex);
	//!单光源密度
	vec3 LightIntensity(const Primitive *except, const vec3 &point, const vec3 &normal, const vec3 &lightPos, LightRT *light, float AO,int threadindex);
	//!测试阴影
	bool TraceShadow(const Primitive *except, const vec3 &lightPos, const vec3 &lightDir, float lightDis,int threadindex);
	//!环境遮蔽
	float AmbientOcclusionFactor(const Primitive *except, const vec3 &point, const vec3 &normal,int threadindex);

	//主投射
	vec3  TraceRay(const vec3 &rayPos, const vec3 &rayDir, int depth, Primitive *except,int threadindex);

public:
	int MaxTraceDepth;//8 //2

//private:
	Presenter *m_presenter;

	//
	BYTE *m_colorBuffer;
	vec3 *m_hdrColorBuffer;
	//0.01s并行时间不值得? 输入卡,双缓存需延后处理 一样卡
	//双缓加速 gdi paint 的同时,追踪不打断,要注意使用双缓冲时可能在追踪过程中接到onsize类消息或者camera改变消息,都需延后处理?
	//BYTE *m_colorBuffer01;
	//vec3 *m_hdrColorBuffer01;
	//BYTE *m_colorBuffer02;
	//vec3 *m_hdrColorBuffer02;

	int m_backbufWidth;
	int m_backbufHeight;
	int m_wndWidth;
	int m_wndHeight;
	float FarPlainDis;

	int SamplesPerPixel;//1*1 2*2 3*3
	//全局光照采样次数 辐射度 环境遮蔽
	int GISampleNum;
	float ODGISamples;
	int   WidthMSamples, HeightMSamples, WidthXHeightXSamplesSq;
	float ODSamples,ODSamplesSq;
	float AmbientOcclusionIntensity, ODGISamplesXAmbientOcclusionIntensity;

//protected:
public:
	//不能单独分开primitive buff obj,这样不利于优化? 区分静态和动态buff也不利?
	LightRT     *m_lights;
	int m_lightsCount;

	Primitive **m_primitives;
	int m_primitivesCount;
	int m_primitivesVolume;

public:
	bool m_enableTexture2D;
	bool m_enableShadow;
	bool m_enableSoftShadow;
	bool m_enableAmbientOcclusion;
	bool m_enableHDR;
	bool m_enableReflection;
	bool m_enableRefraction;
	float  m_quality;

	bool m_sphereProject;

public:
	PrimitiveBspTree    *m_bspPartion;
	LaCone3Partion      *m_eyeLaConePartion;
	AnguleBoxPartion    *m_eyeAnguleBoxPartion;
	UniformVoxelPartion *m_voxelPartion;
	Primitive *m_primitivesTagged[MaxPrimitive];
	int m_primitivesTaggedCount;

	RayTraceTexture* m_curTexture;
	FILTER m_texFilter;

	//预插值的跨度表 因为不是传统的投影矩阵平均插值
	float WRat[2048];
	float HRat[2048];
	//
	//Primitive *m_primitivesInFrustum[MaxPrimitive];
	//int m_primitivesCountInFrustum;
	//int m_statisticPrimitivesCountInFrustumMax;
	//int m_statisticPrimitivesCountInFrustum[MaxPrimitive];
	char debugRegion;

	RayTracerThread*  m_traceThread;
	int               m_threadNum;

};

extern RayTraceRendDriver *G_RayTracer;

#include "Frustum.h"
#include "Camera.h"
#include "RayTraceRendDriver.h"
#include "RayTraceTexture.h"
#include "PrimitiveBspTree.h"
#include "LaCone3Partion.h"
#include "AnguleBoxPartion.h"
#include "UniformGridPartion.h"
#include <assert.h>

#define  WIN32APP
#include "General/Thread.h"
#include <crtdbg.h>

enum TraceThreadState
{
	TT_Waiting,
	TT_Tracing,
	TT_TracingCompleted,
};
class RayTracerThread: public Thread
{
public:
	RayTracerThread();
	//~RayTracerThread()
	//{}
	virtual bool Run(ThreadParm* pData);
	TraceThreadState m_state;
	int startLine;
	int endLine;
	int threadIndex;
};

RayTracerThread::RayTracerThread()
:m_state(TT_Waiting)
{

}

bool RayTracerThread::Run(ThreadParm* pData)
{
	while (1)
	{
		if(Thread::m_state== TS_RunningToEnd)
		{
			Thread::m_state = Thread::TS_End;
			break;
		}
		switch(m_state)
		{
		case TT_Waiting:
			{
				Sleep(1);
				break;
			}
		case TT_Tracing:
			{
				const int frustumSize = 16;
				const int h
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值