⚡ 프로그램 가능한 렌더링 파이프 라인 구현, 전체 중국 주석, 초보자가 렌더링 원칙을 배우도록 돕습니다.
mkdir build
cmake -S . -B ./build
cmake --build ./build --config Release실행 파일이있는 경로에 해상도 폴더를 복사하십시오.
sample_ 로 시작하는 예제 파일 만 찾아 GCC 단일 파일로 직접 컴파일하십시오.
gcc -O2 sample_07_specular.cpp -o sample_07_specular -lstdc++ Mac에 -std=c++17 추가 해야하는 것 같습니다. 링크 수학 라이브러리를 표시하고 선언하려면 -lm 추가해야 할 수도 있습니다.
달리다:
./sample_07_specular 그런 다음 이미지 파일 output.bmp 가져옵니다 .BMP :
이 프로젝트의 모델은 Tinyrender의 오픈 소스 모델을 사용합니다.
주로 vs-> ps 사이의 매개 변수를 전달하는 데 사용되는 ShaderContext 구조를 사용하며 다양한 유형의 변형으로 가득합니다.
// 着色器上下文,由 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 列表
}; 외부 층은 렌더러 대에 대한 함수 포인터를 제공하고 렌더러의 DrawPrimitive 함수가 초기화 될 때 순서대로 삼각형의 3 개의 정점을 호출해야합니다.
// 顶点着色器:因为是 C++ 编写,无需传递 attribute,传个 0-2 的顶点序号
// 着色器函数直接在外层根据序号读取响应数据即可,最后需要返回一个坐标 pos
// 各项 varying 设置到 output 里,由渲染器插值后传递给 PS
typedef std::function<Vec4f( int index, ShaderContext &output)> VertexShader; 호출이 호출 될 때마다 렌더러는 3 개의 정점 중 0 , 1 index 2 VS 프로그램으로 전달하여 외부에서 정점 데이터를 읽을 수 있습니다.
렌더러는 삼각형에 채워야하는 모든 지점의 픽셀 셰이더를 호출합니다.
// 像素着色器:输入 ShaderContext,需要返回 Vec4f 类型的颜色
// 三角形内每个点的 input 具体值会根据前面三个顶点的 output 插值得到
typedef std::function<Vec4f(ShaderContext &input)> PixelShader;픽셀 음영 프로그램에 의해 반환 된 색상은 프레임 버퍼의 해당 위치로 그려집니다.
다음 인터페이스를 호출하면 삼각형을 그릴 수 있습니다.
bool RenderHelp::DrawPrimitive ()이 기능은 먼저 렌더러의 핵심입니다. 먼저 VS를 호출하여 정점을 초기화하고 균질 한 공간 자르기를 수행 한 다음 정규화 후 삼각형의 스크린 좌표를 얻습니다.
그런 다음, 루프의 두 층이 화면의 삼각형 연결 사각형의 각 지점에 반복 한 다음 삼각형 범위 내에서 VS 프로그램을 호출하여 포인트의 색상을 계산합니다.
이제 D3d 12의 삼각형 그림을 작성하고 싶습니다. 천선 없이는 처리 할 수 없지만 이제는 다음 줄만 필요합니다.
# 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 ;
}실행 결과 :
| 파일 이름 | 설명 |
|---|---|
| RenderHelp.h | 렌더러 구현 파일, 사용하면 충분합니다. |
| model.h | 모델로드 |
| 샘플 _01_triangle.cpp | 삼각형 그리기의 예 |
| Sample_02_Texture.cpp | 텍스처 사용 방법, 카메라 매트릭스를 설정하는 방법 등 |
| 샘플 _03_box.cpp | 상자를 그리는 방법 |
| Sample_04_Gouraud.cpp | 상자의 간단한 갈라우드 색칠 |
| 샘플 _05_model.cpp | 모델을로드하고 그리는 방법 |
| 샘플 _06_normal.cpp | 일반 맵으로 모델 세부 사항을 향상시킵니다 |
| 샘플 _07_specular.cpp | 하이라이트를 그리십시오 |
10 년 전, 소프트 렌더러 튜토리얼 Mini3D를 썼는데, 이는 소프트웨어 렌더러의 핵심 원리를 명확하게 설명했습니다.
이 프로젝트의 구현 방법은 GPU의 Edge 방정식 구현 방법을 모델링합니다. 이 프로젝트의 구현 방법은 GPU를 비교적 간단하고 직관적이지만 계산은 매우 크고 실시간 CPU에 적합하지 않지만 GPU의 거친 병렬 처리에 적합합니다.
인터넷에서 프로그래밍 가능한 렌더링 파이프 라인을 구현하는 것은 많은 문제가 있습니다 , 텍스처 샘플링이있을 때, 정수 좌표 변환이 반올림되어야합니다. 그렇지 않으면 텍스처의 여러 정점의 위치는 충분히 안정적이지 않으며 일부 소프트웨어 렌더러는 올바른 관점으로 텍스처를 볼 수 없으며 여전히 모방 텍스처 매핑을 사용하고 있습니다. . . .
렌더러 구현의 매우 상세한 측면이 있습니다.
다른 프로젝트는 의도적으로 코드를 줄이기 위해 많은 세부 사항을 다루기위한 많은 세부 사항을 삭감했습니다.
이 프로젝트의 주요 파일 RenderHelp.h 는 총 1 천 줄 이상을 가지고 있으며, 그 중 3 분의 1은 모든 복잡한 작업을 확장했으며 일부 계산은 실제로 외부 레이어를 추출 할 수 있지만, 성능을 더 빨리 썼으므로 이해력이 뛰어납니다.
기본 원칙은 다음 기사에서 설명했습니다.
읽을 때,이 코드는 기본적으로 몇 개의 도구 라이브러리이며, 마지막 200 줄에서 읽을 수 있습니다.
코드를 이해하지 못하면이 문제에 대한 질문을 할 수 있습니다.