output怎么用_如何用 C++ 写一个可编程软件渲染器?

f6c6159a165776f7ff645cc8fd616f47.png

今天你想用最新的 D3D12 画一个三角形,少说也要上千行代码了,对于初学者来讲,这个门槛是非常高的,太多干扰了,而一千多行代码,已经足够你重头实现一个简易版 D3D 了,为什么不呢?比起从图形 API 入门,不如从画点开始,同样一千行代码,却能让你对 GPU 的工作原理有一个直观的了解。

因此,为了让希望学习渲染的人更快入门,我开源了一个 C++ 实现可编程渲染管线的教程:

skywind3000/RenderHelp

那么网上软件渲染器其实不少,这个 RenderHelp 和他们有什么区别么?区别有三:

  • 实现精简,没依赖,就是一个 RenderHelp.h 文件,单独 include 它就能编译了,不用复杂的工程,导入一堆源文件,vim/vscode 里设置个 gcc 命令行,F9 编译单文件即可。
  • 模型标准,计算精确,网上很多软渲染器实现有很多大大小小的问题:比如纹理不是透视正确的,比如邻接三角形的边没有处理正确,比如 Edge Equation 其实没用对,比如完全没有裁剪,比如到屏幕坐标的计算有误差,应该以像素点方框的中心对齐,结果他们对齐到左上角去了,导致模型动起来三角形边缘会有跳变的感觉,太多问题了,对于强迫症,画错个点都是难接受的,RenderHelp 采用标准模型,不画错一个点,不算错一处坐标。
  • 可读性高,全中文注释,一千多行代码 1/3 是注释,网上很多同类项目,属于作者自己的习作,重在实现,做完了事,注释量不足 5%,一串矩阵套矩阵的操作过去,连行说明都没有,你想搜索下相关概念,连个关键字都不知道。RenderHelp.h 是面向可读性编写的,虽然也比较小巧,但重点计算全部展开,每一处计算都有解释。某些代码其实可以提到外层运行更快些,但为了可读性,还是写到了相关位置上,便于理解。

渲染效果图片:

b541e8f4bc3b6c4a15cf1375d87249b8.png

使用很简单,include 项目内的 RenderHelp.h 即可,VS 和 PS 之间传参,主要使用一个 ShaderContext 的结构体,里面都是一堆各种类型的 varying。

// 着色器上下文,由 VS 设置,再由渲染器按像素逐点插值后,供 PS 读取
struct ShaderContext {std::map<int, float> varying_float;    // 浮点数 varying 列表std::map<int, Vec2f> varying_vec2f;    // 二维矢量 varying 列表std::map<int, Vec3f> varying_vec3f;    // 三维矢量 varying 列表std::map<int, Vec4f> varying_vec4f;    // 四维矢量 varying 列表
};

外层需要提供给渲染器 VS 的函数指针,并在渲染器的 DrawPrimitive 函数进行顶点初始化时对三角形的三个顶点依次调用:

// 顶点着色器:因为是 C++ 编写,无需传递 attribute,传个 0-2 的顶点序号
// 着色器函数直接在外层根据序号读取响应数据即可,最后需要返回一个坐标 pos
// 各项 varying 设置到 output 里,由渲染器插值后传递给 PS 
typedef std::function<Vec4f(int index, ShaderContext &output)> VertexShader;

每次调用时,渲染器会依次将三个顶点的编号 0, 1, 2 通过 index 字段传递给 VS 程序,方便从外部读取顶点数据。

渲染器对三角形内每个需要填充的点调用像素着色器:

// 像素着色器:输入 ShaderContext,需要返回 Vec4f 类型的颜色
// 三角形内每个点的 input 具体值会根据前面三个顶点的 output 插值得到
typedef std::function<Vec4f(ShaderContext &input)> PixelShader;

像素着色程序返回的颜色会被绘制到 Frame Buffer 的对应位置。

完整例子很简单,只需要下面几行代码就能工作了:

#include "RenderHelp.h"int main(void)
{// 初始化渲染器和帧缓存大小RenderHelp rh(800, 600);const int VARYING_COLOR = 0;    // 定义一个 varying 的 key// 顶点数据,由 VS 读取,如有多个三角形,可每次更新 vs_input 再绘制struct { Vec4f pos; Vec4f color; } vs_input[3] = {{ {  0.0,  0.7, 0.90, 1}, {1, 0, 0, 1} },{ { -0.6, -0.2, 0.01, 1}, {0, 1, 0, 1} },{ { +0.6, -0.2, 0.01, 1}, {0, 0, 1, 1} },};// 顶点着色器,初始化 varying 并返回坐标,// 参数 index 是渲染器传入的顶点序号,范围 [0, 2] 用于读取顶点数据rh.SetVertexShader([&] (int index, ShaderContext& output) -> Vec4f {output.varying_vec4f[VARYING_COLOR] = vs_input[index].color;return vs_input[index].pos;        // 直接返回坐标});// 像素着色器,返回颜色rh.SetPixelShader([&] (ShaderContext& input) -> Vec4f {return input.varying_vec4f[VARYING_COLOR];});// 渲染并保存rh.DrawPrimitive();rh.SaveFile("output.bmp");return 0;
}

运行后,生成一张 output.bmp 图片:

d21d17c8b85d28beea56fa94958f3461.png

由于 VS/PS 全部 C++ 编写,因此开发和调试都较方便,无需分开单独编译,能直接访问各种全局变量,能 printf 信息,还能断点观察问题。如果 Windows 的话,最后你可以加一行:

system("mspaint output.bmp");

这样每次运行后就能打开画板程序查看最新的渲染效果。

有了上面绘制三角形的代码,我们可以继续改写载入个模型绘制下:

3de8744f5934808a7055127e5c75173c.png

直接显示模型纹理,光秃秃的太丑,加个高洛德着色:

22d62a02db2c3b4a8e11911ea2389fa8.png

稍微能看一点,像十多年前的网游,再加个法向贴图,让细节更丰富些:

a792a71d4842a06032625a7277704b66.png

看着还行,再加层高光:

b541e8f4bc3b6c4a15cf1375d87249b8.png

看起来不错,主要用于验证渲染器,有兴趣你可以继续折腾 PBR/BRDF 等高级渲染技巧。

渲染器的主要实现原理很简单,我在下面这篇文章介绍过:

OpenGL 和 DirectX 是如何在只知道顶点的情况下得出像素位置的?​www.zhihu.com
3a70fe314957459f75531b7274b0929c.png

两种光栅化的实现方式,基于 Edge Walking 和扫描线绘制的适合 CPU 的传统实时软渲染方法,和现在这个基于 Edge Equation 的适合 GPU 的方法。前者复杂但是速度快,适合 CPU 实时渲染;后者简单,但是运算量大,适合 GPU 并行处理。

欢迎参考我十多年前做的另外一个软件渲染器:

  • mini3d:700 行 C 语言实现实时软件渲染

走的是传统 CPU 实时渲染方法,其他参考:

  • 计算机底层是如何访问显卡的?
  • 256字节3D程序是如何实现3D引擎的呢?
  • 如何进行三角形在齐次空间内的裁剪?

--

收藏比点赞多一倍,怎么回事?点个赞那么难么?

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

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

相关文章

java finereport_java报表工具FineReport常见的数据集报错错误代码和解释

在使用finereport制作报表&#xff0c;若预览发生错误&#xff0c;很多朋友便手忙脚乱不知所措了&#xff0c;其实没什么&#xff0c;只要看懂报错代码和含义&#xff0c;可以很快的排除错误&#xff0c;这里我就分享一下finereport的数据集报错错误代码和解释&#xff0c;如果…

python定义一个人类_Python类的定义、继承及类对象使用方法简明教程

Python编程中类的概念可以比作是某种类型集合的描述&#xff0c;如“人类”可以被看作一个类&#xff0c;然后用人类这个类定义出每个具体的人——你、我、他等作为其对象。类还拥有属性和功能&#xff0c;属性即类本身的一些特性&#xff0c;如人类有名字、身高和体重等属性&a…

android 反色 java_Android小米,魅族6.0状态栏不能反色解决方法

Android6.0及以上if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {Window window getWindow();window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);window.setStatusBarColor(getResources().getColor(android.R.color.white));window.…

mysql delete语句_MySQL ------ 触发器(TRIGGER)(二十七)

MySQL 语句在需要时被执行&#xff0c;存储过程也是&#xff0c;但是你要是想要某条&#xff08;或某些语句&#xff09;在事件发生时自动执行&#xff0c;该怎么办触发器由此而来触发器&#xff1a;某个表发生更改时自动处理。触发器是MySQL响应delete&#xff0c;insert&…

aws mysql价格_mysql – AWS RDS“转出”成本有多贵?

我在AWS上托管了一个社交网络网站.我是否会因RDS的“转移成本”而被收取费用(因为RDS将通过AWS连接到EC2)&#xff1f;这是否意味着我只需支付EC2’转账费用’&#xff1f;与“图像”带宽相比,“数据库”带宽有多贵&#xff1f;真的很感激任何输入.谢谢.解决方法:根据AWS文档,从…

python变量定义大全_详解python变量与数据类型

这篇文章我们学习 Python 变量与数据类型变量变量来源于数学&#xff0c;是计算机语言中能储存计算结果或能表示值抽象概念&#xff0c;变量可以通过变量名访问。在 Python 中 变量命名规定&#xff0c;必须是大小写英文&#xff0c;数字和 下划线(_)的组合&#xff0c;并且不能…

java 数据库改操作_数据库的插入、修改、删除操作(java实现)

import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;public class JDBCTest {// 定义数据库访问参数String url "jdbc:sqlserver://localhost:1433; DatabaseNamelihongchao&…

python3安装pywin32_Python3 pywin32模块安装的详细步骤

python新手一枚&#xff0c;操作系统Win10 64 bit,Python版本&#xff0c;3.7因为某个脚本需要用到win32con 和win32api模块&#xff0c;run -- cmd &#xff0c;使用easy_install pywin32 命令安装&#xff0c;提示错误&#xff0c;搜不到&#xff0c;网上搜了下教程&#x…

创建时间指定日期 java,Java避坑之如何创建指定时间Date对象

在翻看自己以前写的惨不忍睹的代码时&#xff0c;发现了自己曾经写的一个跟Date有关的坑。Date date new Date(2020, 1, 1);System.out.println(date);我的目的是想创建一个2020年1月1日的时间对象date&#xff0c;但是我们创建的date真的就代表2020年1月1日吗&#xff1f;错&…

pagerank数据集_从数据结构到算法:图网络方法初探

机器之心原创作者&#xff1a;朱梓豪编辑&#xff1a;Qing Lin如果说 2019 年机器学习领域什么方向最火&#xff0c;那么必然有图神经网络的一席之地。其实早在很多年前&#xff0c;图神经网络就以图嵌入、图表示学习、网络嵌入等别名呈现出来&#xff0c;其实所有的这些方法本…

php curl上传文件返回false,php curl上传文件$_FILES为空的问题

PHP 5.0~5.6 各版本兼容的cURL文件上传最近做的一个需求&#xff0c;使用PHP cURL上传文件。踩坑若干&#xff0c;整理如下。不同版本PHP之间cURL的区别PHP的cURL支持通过给CURL_POSTFIELDS传递关联数组(而不是字符串)来生成multipart/form-data的POST请求。传统上&#xff0c;…

python判断几个数最大最小_python 找出list中最大或者最小几个数的索引方法

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里技术人对外发布原创技术内容的最大平台&…

netty 客户端断开 异常处理_netty案例,netty4.1基础入门篇八《NettyClient半包粘包处理》发数据方式》...

小傅哥 | https://bugstack.cn 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获。专注于原创专题案例编写&#xff0c;目前已完成的专题有&#xff1b;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例、源码分析等。你…

Java Windows注销用户,中止Windows从Java注销

EDIT2对于那些跟随讨论的人&#xff0c;我留下了我的第一个答案&#xff0c;但似乎他们没有工作。首先找到我的真正解决方案好吧&#xff0c;所以我认为这实际上有效&#xff0c;但它并不完全可以接受&#xff0c;因为它使用了受限制的API部分(但它自Java 1.3以来就存在并且仍然…

python generator_Python Generator漫谈

作为一个Python初学者, Python的格式化语法让众多编程小白追捧, 它的语法糖让代码变得简洁易读&#xff0c;它的庞大开源库让它在各个领域都能发挥作用. 但我时常感受到这个门槛极低的语言远没有表面上看起来易懂易用. 在Python的学习之路上, 我也时常迷茫于自己是否真正掌握了…

php5.5 连接数据库,php5.5 session_set_save_handler 连接数据库问题

好久前忘了在什么地方抄来的&#xff0c;一直好用&#xff0c;但是升级到PHP5.5就不好用了 出现警告服务器无法修改PHP.ini 只好自己试着用mysqli写 但是一直写不出来 请高手指教&#xff01;&#xff01;谢谢回复讨论(解决方案)把 mysql_ 都改成 mysqli_>把 mysql_ 都改成 …

python获取屏幕文字_详解:四种方法教你对Python获取屏幕截图(PyQt , pyautogui)...

前言&#xff1a;今天为大家带来的内容是详解&#xff1a;四种方法教你对Python获取屏幕截图(PyQt , pyautogui)本文具有不错的参考意义&#xff0c;希望能够帮助到大家&#xff01;Python获取电脑截图有多种方式&#xff0c;具体如下&#xff1a;1. PIL中的ImageGrab模块2. wi…

支付宝php异步回调,支付宝支付成功之后异步回调处理

/*** alipay_notify.php.* User: lvfk* Date: 2017/10/26 0026* Time: 13:48* Desc: 支付宝支付成功异步通知*/include_once (__DIR__./../alipay-sdk-PHP-20171023143822/AopSdk.php);//验证签名$aop new \AopClient();$aop->alipayrsaPublicKey \Comm\Pay\Alipay::ALIPA…

python frame如何置顶_Python tkinter frame父窗口小部件排列列

我把滚动条放在一个框架里&#xff0c;框架放在一个小部件里。这个框架上面有一个标签。上面的标签有三列。带有滚动条的框架有三列。我无法让框架内和框架上方的三根柱子对齐。在如果您能帮我排好纵队&#xff0c;我们将不胜感激。谢谢。在以下是MWE&#xff1a;import tkinte…

MySQL和mq一致性,Mysql与Redis一致性问题

缓存一致性产生背景如果每次频繁的访问数据库的时候&#xff0c;虽然查询底层使用B树索引 但还会做磁盘的IO操作&#xff0c;可能会对数据库的压力非常大。所以为了能够减轻数据库的访问压力&#xff0c;会使用一些缓存实现减轻数据库的压力。比如 Redis、es、ehcache、oscache…